1use std::collections::HashMap;
9use std::sync::OnceLock;
10
11use mlua::{Lua, MetaMethod, MultiValue, Table, UserData, UserDataMethods, Value};
12use retex_engine::Engine;
13use retex_engine::nodes::{NodeData, NodeRef, id, noad_opt};
14
15use crate::with_engine;
16
17struct TypeInfo {
19 names: HashMap<u8, String>,
20 ids: HashMap<String, u8>,
21 fields: HashMap<u8, Vec<(i32, String)>>,
22 subtypes: HashMap<u8, Vec<(i32, String)>>,
23 whatsits: Vec<(u16, String)>,
24 whatsit_fields: HashMap<u16, Vec<(i32, String)>>,
25}
26
27fn info() -> &'static TypeInfo {
28 static T: OnceLock<TypeInfo> = OnceLock::new();
29 T.get_or_init(|| {
30 let data = retex_engine::nodes::LUATEX_NODE_TYPES;
31 let mut ti = TypeInfo {
32 names: HashMap::new(),
33 ids: HashMap::new(),
34 fields: HashMap::new(),
35 subtypes: HashMap::new(),
36 whatsits: Vec::new(),
37 whatsit_fields: HashMap::new(),
38 };
39 let pairs = |s: &str| -> Vec<(i32, String)> {
40 s.split_whitespace()
41 .filter_map(|kv| {
42 let (k, v) = kv.split_once('=')?;
43 Some((k.parse().ok()?, v.to_owned()))
44 })
45 .collect()
46 };
47 let mut cur: u8 = 0;
48 for line in data.lines() {
49 let l = line.trim_start();
50 if let Some(rest) = line.strip_prefix("type ") {
51 let mut it = rest.split_whitespace();
52 let i: u8 = it.next().and_then(|x| x.parse().ok()).unwrap_or(0);
53 let name = it.next().unwrap_or("").to_owned();
54 ti.names.insert(i, name.clone());
55 ti.ids.insert(name, i);
56 cur = i;
57 } else if let Some(rest) = l.strip_prefix("fields ") {
58 ti.fields.insert(cur, pairs(rest));
59 } else if let Some(rest) = l.strip_prefix("subtypes ") {
60 ti.subtypes.insert(cur, pairs(rest));
61 } else if let Some(rest) = line.strip_prefix("whatsits ") {
62 ti.whatsits = pairs(rest)
63 .into_iter()
64 .map(|(k, v)| (k as u16, v))
65 .collect();
66 } else if let Some(rest) = l.strip_prefix("whatsit ") {
67 let mut it = rest.splitn(4, ' ');
68 let _name = it.next();
69 let n: u16 = it.next().and_then(|x| x.parse().ok()).unwrap_or(0);
70 let _ = it.next();
71 ti.whatsit_fields.insert(n, pairs(it.next().unwrap_or("")));
72 }
73 }
74 ti
75 })
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub struct LNode(pub u32);
81
82impl UserData for LNode {
83 fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
84 m.add_meta_method(MetaMethod::Index, |lua, this, key: String| {
85 get_field(lua, NodeRef(this.0), &key)
86 });
87 m.add_meta_method(
88 MetaMethod::NewIndex,
89 |lua, this, (key, v): (String, Value)| set_field(lua, NodeRef(this.0), &key, v),
90 );
91 m.add_meta_method(MetaMethod::Eq, |_, this, other: mlua::AnyUserData| {
92 Ok(other
93 .borrow::<LNode>()
94 .map(|o| o.0 == this.0)
95 .unwrap_or(false))
96 });
97 m.add_meta_method(MetaMethod::ToString, |lua, this, ()| {
98 let t = with_engine(lua, |e| e.nodes.id(NodeRef(this.0)))?;
99 let name = info().names.get(&t).cloned().unwrap_or_default();
100 Ok(format!("<node {} < {} > nil : {} 0>", 0, this.0, name))
101 });
102 }
103}
104
105pub(crate) fn type_name(t: u8) -> String {
107 info().names.get(&t).cloned().unwrap_or_default()
108}
109
110pub(crate) fn to_ref(v: &Value) -> Option<NodeRef> {
111 match v {
112 Value::UserData(u) => u.borrow::<LNode>().ok().map(|n| NodeRef(n.0)),
113 Value::Integer(n) if *n > 0 => Some(NodeRef(*n as u32)),
114 _ => None,
115 }
116}
117
118pub(crate) fn wrap(lua: &Lua, p: NodeRef, direct: bool) -> mlua::Result<Value> {
119 if p.is_null() {
120 return Ok(Value::Nil);
121 }
122 if direct {
123 Ok(Value::Integer(i64::from(p.0)))
124 } else {
125 Ok(Value::UserData(lua.create_userdata(LNode(p.0))?))
126 }
127}
128
129#[derive(Clone, Debug)]
133pub struct LAttr(pub retex_engine::nodes::AttrList);
134
135impl UserData for LAttr {
136 fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
137 m.add_meta_method(MetaMethod::Eq, |_, this, other: mlua::AnyUserData| {
138 Ok(other
139 .borrow::<LAttr>()
140 .map(|o| o.0 == this.0)
141 .unwrap_or(false))
142 });
143 m.add_meta_method(MetaMethod::Index, |_, _, _key: Value| Ok(Value::Nil));
144 m.add_meta_method(MetaMethod::ToString, |_, _, ()| {
145 Ok("<node attribute_list>".to_string())
146 });
147 }
148}
149
150fn attr_value(lua: &Lua, p: NodeRef) -> mlua::Result<Value> {
152 match with_engine(lua, |e| e.nodes.attr(p).cloned())? {
153 Some(a) => Ok(Value::UserData(lua.create_userdata(LAttr(a))?)),
154 None => Ok(Value::Nil),
155 }
156}
157
158fn set_attr_value(lua: &Lua, p: NodeRef, v: &Value) -> mlua::Result<()> {
160 let a = match v {
161 Value::UserData(u) => match u.borrow::<LAttr>() {
162 Ok(a) => Some(a.0.clone()),
163 Err(_) => return Ok(()),
164 },
165 Value::Nil => None,
166 _ => return Ok(()),
167 };
168 with_engine(lua, |e| e.nodes.set_attr(p, a))
169}
170
171fn new_data(t: u8) -> NodeData {
173 use retex_engine::glue::Glue;
174 match t {
175 id::HLIST | id::VLIST => NodeData::Box(Box::default()),
176 id::RULE => NodeData::Rule {
177 width: retex_engine::nodes::RUNNING,
178 depth: retex_engine::nodes::RUNNING,
179 height: retex_engine::nodes::RUNNING,
180 dir: 0,
181 index: 0,
182 left: 0,
183 right: 0,
184 },
185 id::GLUE => NodeData::Glue {
186 spec: Glue::ZERO,
187 leader: NodeRef::NULL,
188 },
189 id::KERN => NodeData::Kern {
190 kern: 0,
191 expansion_factor: 0,
192 },
193 id::PENALTY => NodeData::Penalty { penalty: 0 },
194 id::MARGIN_KERN => NodeData::MarginKern {
195 width: 0,
196 glyph: NodeRef::NULL,
197 },
198 id::GLYPH => NodeData::Glyph(Default::default()),
199 id::DISC => NodeData::Disc {
200 pre: NodeRef::NULL,
201 post: NodeRef::NULL,
202 replace: NodeRef::NULL,
203 penalty: 0,
204 },
205 id::BOUNDARY => NodeData::Boundary { value: 0 },
206 id::MATH => NodeData::Math {
207 surround: 0,
208 glue: Glue::ZERO,
209 },
210 id::ADJUST => NodeData::Adjust {
211 head: NodeRef::NULL,
212 },
213 id::MARK => NodeData::Mark {
214 class: 0,
215 mark: retex_engine::token::empty_list(),
216 },
217 id::DIR => NodeData::Dir { dir: 0, level: 0 },
218 id::STYLE => NodeData::Style { style: 0 },
219 id::LOCAL_PAR => NodeData::LocalPar {
220 pen_inter: 0,
221 pen_broken: 0,
222 dir: 0,
223 box_left: NodeRef::NULL,
224 box_left_width: 0,
225 box_right: NodeRef::NULL,
226 box_right_width: 0,
227 },
228 id::WHATSIT => NodeData::Whatsit(Box::new(retex_engine::nodes::Whatsit::Other {
229 fields: Vec::new(),
230 data: Vec::new(),
231 })),
232 _ => NodeData::Temp,
233 }
234}
235
236fn int(v: &Value) -> i64 {
237 match v {
238 Value::Integer(n) => *n,
239 Value::Number(n) => n.round() as i64,
240 Value::Boolean(b) => i64::from(*b),
241 _ => 0,
242 }
243}
244
245fn get_field(lua: &Lua, p: NodeRef, key: &str) -> mlua::Result<Value> {
247 if key == "subtype"
250 && matches!(with_engine(lua, |e| e.nodes.id(p))?, 9 | 30..=32 | 35..=37 | 39 | 41..=49)
251 {
252 return Ok(Value::Nil);
253 }
254 get_field_impl(lua, p, key, false)
255}
256
257enum R {
259 I(i64),
260 N(NodeRef),
261 F(f64),
262 S(Vec<u8>),
263 L(i32),
265 Nil,
266}
267
268fn get_field_impl(lua: &Lua, p: NodeRef, key: &str, direct: bool) -> mlua::Result<Value> {
269 if key == "attr" {
270 return attr_value(lua, p);
271 }
272 let r = with_engine(lua, |e| -> R {
273 let n = e.nodes.get(p);
274 match key {
275 "id" => return R::I(i64::from(n.id)),
276 "subtype" => return R::I(i64::from(n.subtype)),
277 "next" => return R::N(n.next),
278 "prev" => return R::N(n.prev),
279 _ => {}
280 }
281 match &n.data {
282 NodeData::Box(b) | NodeData::Unset { b, .. } => match key {
283 "width" => R::I(b.width.into()),
284 "height" => R::I(b.height.into()),
285 "depth" => R::I(b.depth.into()),
286 "shift" => R::I(b.shift.into()),
287 "dir" => R::I(b.dir.into()),
288 "glue_order" => R::I(b.glue_order.into()),
289 "glue_sign" => R::I(b.glue_sign.into()),
290 "glue_set" => R::F(b.glue_set),
291 "head" | "list" => R::N(b.head),
292 _ => R::Nil,
293 },
294 NodeData::Glyph(g) => match key {
295 "char" => R::I(g.chr.into()),
296 "font" => R::I(g.font.into()),
297 "lang" => R::I(g.lang.into()),
298 "left" => R::I(g.left.into()),
299 "right" => R::I(g.right.into()),
300 "uchyph" => R::I(i64::from(g.uchyph)),
301 "components" => R::N(g.components),
302 "xoffset" => R::I(g.xoffset.into()),
303 "yoffset" => R::I(g.yoffset.into()),
304 "expansion_factor" => R::I(g.expansion_factor.into()),
305 "data" => R::I(g.data.into()),
306 "width" => R::I(e.font_char_dimen(g.font, g.chr, 0).into()),
307 "height" => R::I(e.font_char_dimen(g.font, g.chr, 1).into()),
308 "depth" => R::I(e.font_char_dimen(g.font, g.chr, 2).into()),
309 _ => R::Nil,
310 },
311 NodeData::Glue { spec, leader } => match key {
312 "width" => R::I(spec.width.into()),
313 "stretch" => R::I(spec.stretch.into()),
314 "shrink" => R::I(spec.shrink.into()),
315 "stretch_order" => R::I(spec.stretch_order.into()),
316 "shrink_order" => R::I(spec.shrink_order.into()),
317 "leader" => R::N(*leader),
318 _ => R::Nil,
319 },
320 NodeData::Kern {
321 kern,
322 expansion_factor,
323 } => match key {
324 "kern" | "width" => R::I((*kern).into()),
325 "expansion_factor" => R::I((*expansion_factor).into()),
326 _ => R::Nil,
327 },
328 NodeData::Penalty { penalty } => match key {
329 "penalty" => R::I((*penalty).into()),
330 _ => R::Nil,
331 },
332 NodeData::MarginKern { width, glyph } => match key {
333 "width" => R::I((*width).into()),
334 "glyph" => R::N(*glyph),
335 _ => R::Nil,
336 },
337 NodeData::Rule {
338 width,
339 depth,
340 height,
341 dir,
342 index,
343 left,
344 right,
345 } => match key {
346 "width" => R::I((*width).into()),
347 "depth" => R::I((*depth).into()),
348 "height" => R::I((*height).into()),
349 "dir" => R::I((*dir).into()),
350 "index" => R::I((*index).into()),
351 "left" => R::I((*left).into()),
352 "right" => R::I((*right).into()),
353 _ => R::Nil,
354 },
355 NodeData::Disc {
356 pre,
357 post,
358 replace,
359 penalty,
360 } => match key {
361 "pre" => R::N(*pre),
362 "post" => R::N(*post),
363 "replace" => R::N(*replace),
364 "penalty" => R::I((*penalty).into()),
365 _ => R::Nil,
366 },
367 NodeData::Boundary { value } => match key {
368 "value" => R::I((*value).into()),
369 _ => R::Nil,
370 },
371 NodeData::Whatsit(w) => {
372 let w = (**w).clone();
373 whatsit_field(e, &w, key)
374 }
375 NodeData::Mark { class, mark } => match key {
376 "class" => R::I((*class).into()),
377 "mark" => {
378 let mark = mark.clone();
379 R::S(e.print_to_string(|e| e.show_token_list(&mark, 0, mark.len(), i64::MAX)))
380 }
381 _ => R::Nil,
382 },
383 NodeData::Ins {
384 cost,
385 depth,
386 height,
387 head,
388 ..
389 } => match key {
390 "cost" => R::I((*cost).into()),
391 "depth" => R::I((*depth).into()),
392 "height" => R::I((*height).into()),
393 "head" | "list" => R::N(*head),
394 _ => R::Nil,
395 },
396 NodeData::Adjust { head } => match key {
397 "head" | "list" => R::N(*head),
398 _ => R::Nil,
399 },
400 NodeData::Dir { dir, level } => match key {
401 "dir" => R::I((*dir).into()),
402 "level" => R::I((*level).into()),
403 _ => R::Nil,
404 },
405 NodeData::Math { surround, glue } => match key {
406 "surround" => R::I((*surround).into()),
407 "width" => R::I(glue.width.into()),
408 "stretch" => R::I(glue.stretch.into()),
409 "shrink" => R::I(glue.shrink.into()),
410 "stretch_order" => R::I(glue.stretch_order.into()),
411 "shrink_order" => R::I(glue.shrink_order.into()),
412 _ => R::Nil,
413 },
414 NodeData::Noad(d) => match key {
415 "nucleus" => R::N(d.nucleus),
416 "sub" => R::N(d.sub),
417 "sup" => R::N(d.sup),
418 "options" => R::I(d.options.into()),
419 "left" => R::N(d.left),
420 "degree" => R::N(d.degree),
421 "width" => R::I(d.width.into()),
422 "accent" | "top_accent" if d.options & noad_opt::OVERLAY == 0 => R::N(d.accent),
425 "overlay_accent" if d.options & noad_opt::OVERLAY != 0 => R::N(d.accent),
426 "bot_accent" => R::N(d.bot_accent),
427 "fraction" => R::I(d.fraction.into()),
428 _ => R::Nil,
429 },
430 NodeData::Fraction(d) => match key {
431 "width" => R::I(d.thickness.into()),
432 "num" => R::N(d.num),
433 "denom" => R::N(d.denom),
434 "left" => R::N(d.left),
435 "right" => R::N(d.right),
436 "middle" => R::N(d.middle),
437 _ => R::Nil,
438 },
439 NodeData::Fence(d) => match key {
440 "delim" => R::N(d.delim),
441 "italic" => R::I(d.italic.into()),
442 "height" => R::I(d.height.into()),
443 "depth" => R::I(d.depth.into()),
444 "options" => R::I(d.options.into()),
445 "class" => R::I(d.class.into()),
446 _ => R::Nil,
447 },
448 NodeData::Style { style } => match key {
450 "style" => R::S(
451 crate::texlib::MATH_STYLE_NAMES
452 .get(usize::from(*style))
453 .copied()
454 .unwrap_or("display")
455 .as_bytes()
456 .to_vec(),
457 ),
458 _ => R::Nil,
459 },
460 NodeData::Choice(c) => match key {
461 "display" => R::N(c[0]),
462 "text" => R::N(c[1]),
463 "script" => R::N(c[2]),
464 "scriptscript" => R::N(c[3]),
465 _ => R::Nil,
466 },
467 NodeData::MathChar { fam, chr } => match key {
468 "fam" => R::I((*fam).into()),
469 "char" => R::I((*chr).into()),
470 _ => R::Nil,
471 },
472 NodeData::SubList { head } => match key {
473 "head" | "list" => R::N(*head),
474 _ => R::Nil,
475 },
476 NodeData::Delim {
477 small_fam,
478 small_char,
479 large_fam,
480 large_char,
481 } => match key {
482 "small_fam" => R::I((*small_fam).into()),
483 "small_char" => R::I((*small_char).into()),
484 "large_fam" => R::I((*large_fam).into()),
485 "large_char" => R::I((*large_char).into()),
486 _ => R::Nil,
487 },
488 _ => R::Nil,
489 }
490 })?;
491 Ok(match r {
492 R::I(i) => Value::Integer(i),
493 R::N(n) => wrap(lua, n, direct)?,
494 R::F(f) => Value::Number(f),
495 R::S(s) => Value::String(lua.create_string(&s)?),
496 R::L(n) => lua_refs_synced(lua)?.raw_get(n)?,
497 R::Nil => Value::Nil,
498 })
499}
500
501fn set_field(lua: &Lua, p: NodeRef, key: &str, v: Value) -> mlua::Result<()> {
503 if !matches!(key, "next" | "prev" | "subtype" | "attr")
504 && with_engine(lua, |e| e.nodes.id(p) == id::WHATSIT)?
505 {
506 return set_whatsit_field(lua, p, key, v);
507 }
508 if key == "attr" {
509 return set_attr_value(lua, p, &v);
510 }
511 let r = to_ref(&v).unwrap_or(NodeRef::NULL);
512 let i = int(&v);
513 let style_value = match &v {
515 Value::String(name) => crate::texlib::MATH_STYLE_NAMES
516 .iter()
517 .position(|n| n.as_bytes() == name.as_bytes().as_ref())
518 .unwrap_or(0) as i64,
519 _ => i.clamp(0, 7),
520 };
521 let f = match &v {
522 Value::Number(x) => *x,
523 Value::Integer(x) => *x as f64,
524 _ => 0.0,
525 };
526 with_engine(lua, |e| {
527 let n = e.nodes.get_mut(p);
528 match key {
529 "next" => {
530 n.next = r;
531 return;
532 }
533 "prev" => {
534 n.prev = r;
535 return;
536 }
537 "subtype" if n.id == id::STYLE => return,
540 "subtype" => {
541 n.subtype = i as u16;
542 return;
543 }
544 _ => {}
545 }
546 match &mut n.data {
547 NodeData::Box(b) | NodeData::Unset { b, .. } => match key {
548 "width" => b.width = i as i32,
549 "height" => b.height = i as i32,
550 "depth" => b.depth = i as i32,
551 "shift" => b.shift = i as i32,
552 "dir" => b.dir = i as i8,
553 "glue_order" => b.glue_order = i as u8,
554 "glue_sign" => b.glue_sign = i as u8,
555 "glue_set" => b.glue_set = f64::from(f as f32),
556 "head" | "list" => b.head = r,
557 _ => {}
558 },
559 NodeData::Glyph(g) => match key {
560 "char" => g.chr = i as u32,
561 "font" => g.font = i as u32,
562 "lang" => g.lang = i as u16,
563 "left" => g.left = i as u8,
564 "right" => g.right = i as u8,
565 "uchyph" => g.uchyph = i != 0,
566 "components" => g.components = r,
567 "xoffset" => g.xoffset = i as i32,
568 "yoffset" => g.yoffset = i as i32,
569 "expansion_factor" => g.expansion_factor = i as i32,
570 "data" => g.data = i as i32,
571 _ => {}
572 },
573 NodeData::Glue { spec, leader } => match key {
574 "width" => spec.width = i as i32,
575 "stretch" => spec.stretch = i as i32,
576 "shrink" => spec.shrink = i as i32,
577 "stretch_order" => spec.stretch_order = i as u8,
578 "shrink_order" => spec.shrink_order = i as u8,
579 "leader" => *leader = r,
580 _ => {}
581 },
582 NodeData::Kern {
583 kern,
584 expansion_factor,
585 } => {
586 if key == "kern" || key == "width" {
587 *kern = i as i32;
588 } else if key == "expansion_factor" {
589 *expansion_factor = i as i32;
590 }
591 }
592 NodeData::MarginKern { width, glyph } => match key {
593 "width" => *width = i as i32,
594 "glyph" => *glyph = r,
595 _ => {}
596 },
597 NodeData::Penalty { penalty } => {
598 if key == "penalty" {
599 *penalty = i as i32;
600 }
601 }
602 NodeData::Rule {
603 width,
604 depth,
605 height,
606 ..
607 } => match key {
608 "width" => *width = i as i32,
609 "depth" => *depth = i as i32,
610 "height" => *height = i as i32,
611 _ => {}
612 },
613 NodeData::Disc {
614 pre,
615 post,
616 replace,
617 penalty,
618 } => match key {
619 "pre" => *pre = r,
620 "post" => *post = r,
621 "replace" => *replace = r,
622 "penalty" => *penalty = i as i32,
623 _ => {}
624 },
625 NodeData::Boundary { value } => {
626 if key == "value" {
627 *value = i as i32;
628 }
629 }
630 NodeData::Mark { class, .. } => {
631 if key == "class" {
632 *class = i as u32;
633 }
634 }
635 NodeData::Noad(d) => match key {
636 "nucleus" => d.nucleus = r,
637 "sub" => d.sub = r,
638 "sup" => d.sup = r,
639 "options" => d.options = i as u32,
640 "left" => d.left = r,
641 "degree" => d.degree = r,
642 "width" => d.width = i as i32,
643 "accent" | "top_accent" => {
644 d.accent = r;
645 d.options &= !noad_opt::OVERLAY;
646 }
647 "overlay_accent" => {
648 d.accent = r;
649 d.options |= noad_opt::OVERLAY;
650 }
651 "bot_accent" => d.bot_accent = r,
652 "fraction" => d.fraction = i as i32,
653 _ => {}
654 },
655 NodeData::Fraction(d) => match key {
656 "width" => d.thickness = i as i32,
657 "num" => d.num = r,
658 "denom" => d.denom = r,
659 "left" => d.left = r,
660 "right" => d.right = r,
661 "middle" => d.middle = r,
662 _ => {}
663 },
664 NodeData::Fence(d) => match key {
665 "delim" => d.delim = r,
666 "italic" => d.italic = i as i32,
667 "height" => d.height = i as i32,
668 "depth" => d.depth = i as i32,
669 "options" => d.options = i as u32,
670 "class" => d.class = i as i32,
671 _ => {}
672 },
673 NodeData::Style { style } => {
674 if key == "style" {
675 *style = style_value as u8;
676 n.subtype = u16::from(*style);
677 }
678 }
679 NodeData::Choice(c) => match key {
680 "display" => c[0] = r,
681 "text" => c[1] = r,
682 "script" => c[2] = r,
683 "scriptscript" => c[3] = r,
684 _ => {}
685 },
686 NodeData::MathChar { fam, chr } => match key {
687 "fam" => *fam = i as u32,
688 "char" => *chr = i as u32,
689 _ => {}
690 },
691 NodeData::SubList { head } => {
692 if key == "head" || key == "list" {
693 *head = r;
694 }
695 }
696 NodeData::Delim {
697 small_fam,
698 small_char,
699 large_fam,
700 large_char,
701 } => match key {
702 "small_fam" => *small_fam = i as u32,
703 "small_char" => *small_char = i as u32,
704 "large_fam" => *large_fam = i as u32,
705 "large_char" => *large_char = i as u32,
706 _ => {}
707 },
708 NodeData::Ins { head, .. } | NodeData::Adjust { head } => {
709 if key == "head" || key == "list" {
710 *head = r;
711 }
712 }
713 NodeData::Math { surround, glue } => match key {
714 "surround" => *surround = i as i32,
715 "width" => glue.width = i as i32,
716 "stretch" => glue.stretch = i as i32,
717 "shrink" => glue.shrink = i as i32,
718 "stretch_order" => glue.stretch_order = i as u8,
719 "shrink_order" => glue.shrink_order = i as u8,
720 _ => {}
721 },
722 _ => {}
723 }
724 })
725}
726
727pub fn install(lua: &Lua) -> mlua::Result<()> {
729 let node = lua.create_table()?;
730 let direct = lua.create_table()?;
731 for (t, is_direct) in [(&node, false), (&direct, true)] {
732 install_common(lua, t, is_direct)?;
733 }
734 direct.set(
735 "todirect",
736 lua.create_function(|_, v: Value| Ok(to_ref(&v).map(|p| i64::from(p.0))))?,
737 )?;
738 direct.set(
739 "tonode",
740 lua.create_function(|lua, v: Value| match to_ref(&v) {
741 Some(p) => wrap(lua, p, false),
742 None => Ok(Value::Nil),
743 })?,
744 )?;
745 install_properties(lua, &node, &direct)?;
746 require_nodes(lua, &node)?;
747 node.set("direct", direct)?;
748 lua.globals().set("node", node)?;
749 Ok(())
750}
751
752const NODE_ARGUMENT: &[(&str, bool)] = &[
758 ("hpack", false),
759 ("vpack", false),
760 ("write", false),
761 ("remove", false),
762 ("kerning", false),
763 ("ligaturing", false),
764 ("mlist_to_hlist", false),
765 ("has_attribute", false),
766 ("set_attribute", false),
767 ("unset_attribute", false),
768 ("first_glyph", false),
769 ("protect_glyphs", false),
770 ("flatten_discretionaries", false),
771 ("rangedimensions", false),
772 ("is_char", false),
773 ("copy_list", true),
774];
775
776fn require_nodes(lua: &Lua, node: &Table) -> mlua::Result<()> {
778 for &(name, nil_ok) in NODE_ARGUMENT {
779 let Ok(f) = node.get::<mlua::Function>(name) else {
780 continue;
781 };
782 let checked = lua.create_function(move |lua, args: MultiValue| {
783 let first = args.front().cloned().unwrap_or(Value::Nil);
784 let is_node = matches!(&first, Value::UserData(u) if u.is::<LNode>());
785 let fine = is_node || (nil_ok && first.is_nil());
786 if !fine {
787 return Err(crate::texlib::not_a_node(lua, &first));
788 }
789 f.call::<MultiValue>(args)
790 })?;
791 node.set(name, checked)?;
792 }
793 if let Ok(f) = node.get::<mlua::Function>("dimensions") {
796 let checked = lua.create_function(move |lua, args: MultiValue| {
797 if args.front().is_none_or(Value::is_nil) {
798 return Err(crate::texlib::not_a_node(lua, &Value::Nil));
799 }
800 f.call::<MultiValue>(args)
801 })?;
802 node.set("dimensions", checked)?;
803 }
804 Ok(())
805}
806
807const PROPERTIES_KEY: &str = "retex.node.properties";
809
810fn properties(lua: &Lua) -> mlua::Result<Table> {
813 let store: Table = match lua.named_registry_value::<Value>(PROPERTIES_KEY)? {
814 Value::Table(t) => t,
815 _ => {
816 let t = lua.create_table()?;
817 lua.set_named_registry_value(PROPERTIES_KEY, &t)?;
818 t
819 }
820 };
821 let events = with_engine(lua, |e| std::mem::take(&mut e.nodes.props.events))?;
822 for ev in events {
823 match ev {
824 retex_engine::nodes::PropEvent::Flush(n) => store.raw_set(n, Value::Nil)?,
825 retex_engine::nodes::PropEvent::Copy(from, to) => {
826 let v = match store.raw_get::<Value>(from)? {
829 Value::Table(t) => {
830 let copy = lua.create_table()?;
831 let mt = lua.create_table()?;
832 mt.set("__index", t)?;
833 copy.set_metatable(Some(mt))?;
834 Value::Table(copy)
835 }
836 _ => Value::Nil,
837 };
838 if v.is_nil() {
839 with_engine(lua, |e| e.nodes.props.nodes.remove(&to))?;
840 }
841 store.raw_set(to, v)?;
842 }
843 }
844 }
845 Ok(store)
846}
847
848fn set_property(lua: &Lua, p: NodeRef, v: Value) -> mlua::Result<()> {
849 let store = properties(lua)?;
850 with_engine(lua, |e| {
851 let props = &mut e.nodes.props;
852 if v.is_nil() {
853 props.nodes.remove(&p.0);
854 } else if props.flush || props.copy {
855 props.nodes.insert(p.0);
856 }
857 })?;
858 store.raw_set(p.0, v)
859}
860
861fn install_properties(lua: &Lua, node: &Table, direct: &Table) -> mlua::Result<()> {
867 for t in [node, direct] {
868 let proxy = lua.create_table()?;
869 let mt = lua.create_table()?;
870 mt.set(
871 "__index",
872 lua.create_function(|lua, (_t, k): (Value, Value)| match to_ref(&k) {
873 Some(p) => properties(lua)?.raw_get::<Value>(p.0),
874 None => Ok(Value::Nil),
875 })?,
876 )?;
877 mt.set(
878 "__newindex",
879 lua.create_function(|lua, (_t, k, v): (Value, Value, Value)| match to_ref(&k) {
880 Some(p) => set_property(lua, p, v),
881 None => Ok(()),
882 })?,
883 )?;
884 proxy.set_metatable(Some(mt))?;
885 t.set(
886 "get_properties_table",
887 lua.create_function(move |_, ()| Ok(proxy.clone()))?,
888 )?;
889 t.set(
890 "getproperty",
891 lua.create_function(|lua, v: Value| match to_ref(&v) {
892 Some(p) => properties(lua)?.raw_get::<Value>(p.0),
893 None => Ok(Value::Nil),
894 })?,
895 )?;
896 t.set(
897 "setproperty",
898 lua.create_function(|lua, (v, x): (Value, Value)| match to_ref(&v) {
899 Some(p) => set_property(lua, p, x),
900 None => Ok(()),
901 })?,
902 )?;
903 t.set(
904 "flush_properties_table",
905 lua.create_function(|lua, ()| {
906 let store = lua.create_table()?;
907 lua.set_named_registry_value(PROPERTIES_KEY, &store)?;
908 with_engine(lua, |e| {
909 e.nodes.props.nodes.clear();
910 e.nodes.props.events.clear();
911 })
912 })?,
913 )?;
914 t.set(
915 "set_properties_mode",
916 lua.create_function(|lua, (flush, copy): (Option<bool>, Option<bool>)| {
917 with_engine(lua, |e| {
918 e.nodes.props.flush = flush.unwrap_or(false);
919 e.nodes.props.copy = copy.unwrap_or(false);
920 })
921 })?,
922 )?;
923 }
924 Ok(())
925}
926
927fn install_common(lua: &Lua, t: &Table, direct: bool) -> mlua::Result<()> {
928 t.set(
929 "types",
930 lua.create_function(|lua, ()| {
931 let out = lua.create_table()?;
932 for (k, v) in &info().names {
933 out.set(*k, v.as_str())?;
934 }
935 Ok(out)
936 })?,
937 )?;
938 t.set(
939 "whatsits",
940 lua.create_function(|lua, ()| {
941 let out = lua.create_table()?;
942 for (k, v) in &info().whatsits {
943 out.set(*k, v.as_str())?;
944 }
945 Ok(out)
946 })?,
947 )?;
948 t.set(
949 "id",
950 lua.create_function(|_, name: String| Ok(info().ids.get(&name).copied()))?,
951 )?;
952 t.set(
953 "subtype",
954 lua.create_function(|_, name: String| {
955 Ok(info()
956 .whatsits
957 .iter()
958 .find(|(_, n)| *n == name)
959 .map(|(k, _)| *k))
960 })?,
961 )?;
962 t.set(
963 "type",
964 lua.create_function(|lua, v: Value| match &v {
965 Value::Integer(n) if !matches!(v, Value::UserData(_)) => {
966 Ok(info().names.get(&(*n as u8)).cloned())
967 }
968 _ => match to_ref(&v) {
969 Some(p) => {
970 let t = with_engine(lua, |e| e.nodes.id(p))?;
971 Ok(info().names.get(&t).cloned())
972 }
973 None => Ok(None),
974 },
975 })?,
976 )?;
977 t.set(
978 "subtypes",
979 lua.create_function(|lua, v: Value| {
980 let idn = match &v {
981 Value::Integer(n) => *n as u8,
982 Value::String(s) => match info().ids.get(&*s.to_str()?) {
983 Some(i) => *i,
984 None => return Ok(Value::Nil),
985 },
986 _ => return Ok(Value::Nil),
987 };
988 let out = lua.create_table()?;
989 if let Some(st) = info().subtypes.get(&idn) {
990 for (k, n) in st {
991 out.set(*k, n.as_str())?;
992 }
993 }
994 Ok(Value::Table(out))
995 })?,
996 )?;
997 t.set(
998 "fields",
999 lua.create_function(|lua, (v, sub): (Value, Option<i64>)| {
1000 let idn = match &v {
1001 Value::Integer(n) => *n as u8,
1002 Value::String(s) => match info().ids.get(&*s.to_str()?) {
1003 Some(i) => *i,
1004 None => return Ok(Value::Nil),
1005 },
1006 _ => return Ok(Value::Nil),
1007 };
1008 let list = if idn == id::WHATSIT {
1009 sub.and_then(|s| info().whatsit_fields.get(&(s as u16)))
1010 } else {
1011 info().fields.get(&idn)
1012 };
1013 let out = lua.create_table()?;
1014 if let Some(fl) = list {
1015 for (k, n) in fl {
1016 out.set(*k, n.as_str())?;
1017 }
1018 }
1019 Ok(Value::Table(out))
1020 })?,
1021 )?;
1022 t.set(
1023 "is_node",
1024 lua.create_function(move |_, v: Value| {
1025 Ok(match &v {
1026 Value::UserData(u) => u.borrow::<LNode>().is_ok(),
1027 Value::Integer(n) => direct && *n > 0,
1028 _ => false,
1029 })
1030 })?,
1031 )?;
1032 t.set(
1033 "new",
1034 lua.create_function(move |lua, (ty, sub): (Value, Option<Value>)| {
1035 let t = match &ty {
1036 Value::Integer(n) => *n as u8,
1037 Value::String(s) => *info()
1038 .ids
1039 .get(&*s.to_str()?)
1040 .ok_or_else(|| mlua::Error::runtime("unknown node type"))?,
1041 _ => return Err(mlua::Error::runtime("node type expected")),
1042 };
1043 let st = match &sub {
1044 Some(Value::Integer(n)) => *n as u16,
1045 Some(Value::String(s)) => {
1046 let s = s.to_str()?.to_owned();
1047 info()
1048 .whatsits
1049 .iter()
1050 .find(|(_, n)| *n == s)
1051 .map(|(k, _)| *k)
1052 .unwrap_or(0)
1053 }
1054 _ => 0,
1055 };
1056 let data = if t == id::WHATSIT {
1057 NodeData::Whatsit(Box::new(new_whatsit(st)))
1058 } else if t == id::STYLE {
1059 NodeData::Style {
1061 style: st.min(7) as u8,
1062 }
1063 } else {
1064 new_data(t)
1065 };
1066 let p = with_engine(lua, |e| e.new_node(t, st, data))?;
1068 wrap(lua, p, direct)
1069 })?,
1070 )?;
1071 t.set(
1073 "free",
1074 lua.create_function(move |lua, v: Value| {
1075 let Some(p) = to_ref(&v) else {
1076 return Ok(Value::Nil);
1077 };
1078 let next = with_engine(lua, |e| {
1079 let n = e.nodes.next(p);
1080 e.nodes.free(p);
1081 n
1082 })?;
1083 if next.is_null() {
1084 Ok(Value::Nil)
1085 } else {
1086 wrap(lua, next, direct)
1087 }
1088 })?,
1089 )?;
1090 t.set(
1091 "flush_node",
1092 lua.create_function(|lua, v: Value| {
1093 if let Some(p) = to_ref(&v) {
1094 with_engine(lua, |e| e.nodes.free(p))?;
1095 }
1096 Ok(())
1097 })?,
1098 )?;
1099 t.set(
1100 "flush_list",
1101 lua.create_function(|lua, v: Value| {
1102 if let Some(p) = to_ref(&v) {
1103 with_engine(lua, |e| e.nodes.flush_list(p))?;
1104 }
1105 Ok(())
1106 })?,
1107 )?;
1108 t.set(
1109 "copy",
1110 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1111 Some(p) => {
1112 let q = with_engine(lua, |e| e.nodes.copy_node(p))?;
1113 wrap(lua, q, direct)
1114 }
1115 None => Ok(Value::Nil),
1116 })?,
1117 )?;
1118 t.set(
1119 "copy_list",
1120 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1121 Some(p) => {
1122 let q = with_engine(lua, |e| e.nodes.copy_list(p))?;
1123 wrap(lua, q, direct)
1124 }
1125 None => Ok(Value::Nil),
1126 })?,
1127 )?;
1128 t.set(
1129 "tail",
1130 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1131 Some(p) => {
1132 let q = with_engine(lua, |e| e.nodes.tail_of(p))?;
1133 wrap(lua, q, direct)
1134 }
1135 None => Ok(Value::Nil),
1136 })?,
1137 )?;
1138 t.set(
1139 "slide",
1140 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1141 Some(p) => {
1142 let q = with_engine(lua, |e| slide(e, p))?;
1143 wrap(lua, q, direct)
1144 }
1145 None => Ok(Value::Nil),
1146 })?,
1147 )?;
1148 t.set(
1149 "length",
1150 lua.create_function(|lua, v: Value| match to_ref(&v) {
1151 Some(p) => with_engine(lua, |e| count(e, p, None)),
1152 None => Ok(0),
1153 })?,
1154 )?;
1155 t.set(
1156 "count",
1157 lua.create_function(|lua, (ty, v): (i64, Value)| match to_ref(&v) {
1158 Some(p) => with_engine(lua, |e| count(e, p, Some(ty as u8))),
1159 None => Ok(0),
1160 })?,
1161 )?;
1162 for (name, by_id) in [("traverse", false), ("traverse_id", true)] {
1163 t.set(
1164 name,
1165 lua.create_function(move |lua, args: MultiValue| {
1166 let v: Vec<Value> = args.into_iter().collect();
1167 let (filter, start) = if by_id {
1168 (
1169 Some(int(v.first().unwrap_or(&Value::Nil)) as u8),
1170 v.get(1).cloned(),
1171 )
1172 } else {
1173 (None, v.first().cloned())
1174 };
1175 let start = start.and_then(|s| to_ref(&s)).unwrap_or(NodeRef::NULL);
1176 let iter = lua.create_function(move |lua, (_s, cur): (Value, Value)| {
1177 let next = match to_ref(&cur) {
1178 Some(c) => with_engine(lua, |e| e.nodes.next(c))?,
1179 None => start,
1180 };
1181 let mut p = next;
1182 let found = with_engine(lua, |e| {
1183 while !p.is_null() {
1184 if filter.is_none_or(|f| e.nodes.id(p) == f) {
1185 return (p, e.nodes.id(p), e.nodes.get(p).subtype);
1186 }
1187 p = e.nodes.next(p);
1188 }
1189 (NodeRef::NULL, 0, 0)
1190 })?;
1191 if found.0.is_null() {
1192 return Ok(MultiValue::new());
1193 }
1194 let n = wrap(lua, found.0, direct)?;
1197 let sub = Value::Integer(i64::from(found.2));
1198 Ok(if by_id {
1199 MultiValue::from_iter([n, sub])
1200 } else {
1201 MultiValue::from_iter([n, Value::Integer(i64::from(found.1)), sub])
1202 })
1203 })?;
1204 Ok((iter, Value::Nil, Value::Nil))
1205 })?,
1206 )?;
1207 }
1208 t.set(
1209 "insert_after",
1210 lua.create_function(move |lua, (h, c, n): (Value, Value, Value)| {
1211 let (Some(n), head) = (to_ref(&n), to_ref(&h)) else {
1212 return Ok(MultiValue::new());
1213 };
1214 let cur = to_ref(&c);
1215 let head = with_engine(lua, |e| match (head, cur) {
1216 (None, _) | (_, None) => n,
1217 (Some(h), Some(c)) => {
1218 let nx = e.nodes.next(c);
1219 e.nodes.link(c, n);
1220 let tail = e.nodes.tail_of(n);
1221 e.nodes.link(tail, nx);
1222 h
1223 }
1224 })?;
1225 Ok(MultiValue::from_iter([
1226 wrap(lua, head, direct)?,
1227 wrap(lua, n, direct)?,
1228 ]))
1229 })?,
1230 )?;
1231 t.set(
1232 "insert_before",
1233 lua.create_function(move |lua, (h, c, n): (Value, Value, Value)| {
1234 let Some(n) = to_ref(&n) else {
1235 return Ok(MultiValue::new());
1236 };
1237 let head = to_ref(&h);
1238 let cur = to_ref(&c);
1239 let head = with_engine(lua, |e| match (head, cur) {
1240 (None, _) => n,
1241 (Some(h), None) => {
1242 let t = e.nodes.tail_of(h);
1243 e.nodes.link(t, n);
1244 h
1245 }
1246 (Some(h), Some(c)) => {
1247 let pv = e.nodes.get(c).prev;
1248 let tail = e.nodes.tail_of(n);
1249 e.nodes.link(tail, c);
1250 if c == h || pv.is_null() {
1251 e.nodes.get_mut(n).prev = NodeRef::NULL;
1252 n
1253 } else {
1254 e.nodes.link(pv, n);
1255 h
1256 }
1257 }
1258 })?;
1259 Ok(MultiValue::from_iter([
1260 wrap(lua, head, direct)?,
1261 wrap(lua, n, direct)?,
1262 ]))
1263 })?,
1264 )?;
1265 t.set(
1266 "remove",
1267 lua.create_function(move |lua, (h, c): (Value, Value)| {
1268 let (Some(h), Some(c)) = (to_ref(&h), to_ref(&c)) else {
1269 return Ok(MultiValue::new());
1270 };
1271 let (head, next) = with_engine(lua, |e| {
1272 let pv = e.nodes.get(c).prev;
1273 let nx = e.nodes.next(c);
1274 let head = if c == h {
1275 nx
1276 } else {
1277 e.nodes.link(pv, nx);
1278 h
1279 };
1280 if !nx.is_null() && c == h {
1281 e.nodes.get_mut(nx).prev = NodeRef::NULL;
1282 }
1283 (head, nx)
1285 })?;
1286 Ok(MultiValue::from_iter([
1287 wrap(lua, head, direct)?,
1288 wrap(lua, next, direct)?,
1289 ]))
1290 })?,
1291 )?;
1292 t.set(
1293 "has_field",
1294 lua.create_function(|lua, (v, f): (Value, String)| match to_ref(&v) {
1295 Some(p) => {
1296 let t = with_engine(lua, |e| e.nodes.id(p))?;
1297 Ok(info()
1298 .fields
1299 .get(&t)
1300 .is_some_and(|fl| fl.iter().any(|(_, n)| *n == f)))
1301 }
1302 None => Ok(false),
1303 })?,
1304 )?;
1305 t.set(
1306 "getfield",
1307 lua.create_function(move |lua, (v, f): (Value, String)| match to_ref(&v) {
1308 Some(p) => get_field_impl(lua, p, &f, direct),
1309 None => Ok(Value::Nil),
1310 })?,
1311 )?;
1312 t.set(
1313 "setfield",
1314 lua.create_function(|lua, (v, f, x): (Value, String, Value)| match to_ref(&v) {
1315 Some(p) => set_field(lua, p, &f, x),
1316 None => Ok(()),
1317 })?,
1318 )?;
1319 for name in [
1321 "id", "subtype", "next", "prev", "char", "font", "width", "height", "depth", "list",
1322 "penalty", "kern", "leader",
1323 ] {
1324 let key = if name == "list" { "head" } else { name };
1325 if name != "id" && name != "subtype" {
1326 t.set(
1327 format!("get{name}"),
1328 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1329 Some(p) => get_field_impl(lua, p, key, direct),
1330 None => Ok(Value::Nil),
1331 })?,
1332 )?;
1333 } else {
1334 t.set(
1335 format!("get{name}"),
1336 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1337 Some(p) => get_field_impl(lua, p, key, direct),
1338 None => Ok(Value::Nil),
1339 })?,
1340 )?;
1341 }
1342 t.set(
1343 format!("set{name}"),
1344 lua.create_function(move |lua, (v, x): (Value, Value)| match to_ref(&v) {
1345 Some(p) => set_field(lua, p, key, x),
1346 None => Ok(()),
1347 })?,
1348 )?;
1349 }
1350 install_accessors(lua, t, direct)?;
1351 t.set(
1355 "mlist_to_hlist",
1356 lua.create_function(move |lua, args: MultiValue| {
1357 let v: Vec<Value> = args.into_iter().collect();
1358 let head = v.first().and_then(to_ref).unwrap_or(NodeRef::NULL);
1359 let style = match v.get(1) {
1360 Some(Value::String(s)) => {
1361 let s = s.to_str()?.to_string();
1362 crate::texlib::MATH_STYLE_NAMES
1363 .iter()
1364 .position(|n| *n == s)
1365 .unwrap_or(2) as i32
1366 }
1367 Some(Value::Integer(n)) => (*n as i32).clamp(0, 7),
1368 _ => 2,
1369 };
1370 let pen = matches!(v.get(2), Some(Value::Boolean(true)));
1371 if head.is_null() {
1372 return Ok(Value::Nil);
1373 }
1374 let r = with_engine(lua, |e| e.mlist_to_hlist(head, style, pen))?;
1375 if r.is_null() {
1376 return Ok(Value::Nil);
1377 }
1378 wrap(lua, r, direct)
1379 })?,
1380 )?;
1381 for name in [
1382 "rangedimensions",
1383 "kerning",
1384 "ligaturing",
1385 "hyphenating",
1386 "flatten_discretionaries",
1387 ] {
1388 t.set(
1389 name,
1390 lua.create_function(move |_, args: MultiValue| Ok(args))?,
1391 )?;
1392 }
1393 install_list_ops(lua, t, direct)?;
1394 t.set(
1396 "family_font",
1397 lua.create_function(|lua, fam: i64| {
1398 if !(0..retex_engine::eqtb::NUM_FAMILIES as i64).contains(&fam) {
1399 return Ok(0);
1400 }
1401 with_engine(lua, |e| i64::from(e.fam_fnt(fam as u32, 0)))
1402 })?,
1403 )?;
1404 t.set(
1405 "dimensions",
1406 lua.create_function(|lua, args: MultiValue| {
1407 let refs: Vec<NodeRef> = args.iter().filter_map(to_ref).collect();
1410 let Some(&first) = refs.first() else {
1411 return Ok((0, 0, 0));
1412 };
1413 let stop = refs.get(1).copied().unwrap_or(NodeRef::NULL);
1414 with_engine(lua, |e| e.natural_dimensions(first, stop))
1415 })?,
1416 )?;
1417 t.set(
1418 "has_attribute",
1419 lua.create_function(|lua, (v, n, val): (Value, i64, Option<i64>)| {
1420 let Some(p) = to_ref(&v) else {
1421 return Ok(None);
1422 };
1423 let a = with_engine(lua, |e| {
1424 e.nodes
1425 .get(p)
1426 .attr
1427 .as_ref()
1428 .and_then(|l| l.iter().find(|(k, _)| i64::from(*k) == n).map(|x| x.1))
1429 })?;
1430 Ok(match (a, val) {
1431 (Some(x), Some(w)) if i64::from(x) != w => None,
1432 (a, _) => a.map(i64::from),
1433 })
1434 })?,
1435 )?;
1436 t.set(
1437 "set_attribute",
1438 lua.create_function(|lua, (v, n, val): (Value, Value, Value)| {
1439 let (n, val) = (to_integer(&n), to_integer(&val));
1442 if let Some(p) = to_ref(&v) {
1443 with_engine(lua, |e| set_attr(e, p, n as u32, Some(val as i32)))?;
1444 }
1445 Ok(())
1446 })?,
1447 )?;
1448 t.set(
1449 "unset_attribute",
1450 lua.create_function(|lua, (v, n): (Value, i64)| {
1451 if let Some(p) = to_ref(&v) {
1452 with_engine(lua, |e| set_attr(e, p, n as u32, None))?;
1453 }
1454 Ok(())
1455 })?,
1456 )?;
1457 Ok(())
1458}
1459
1460fn to_integer(v: &Value) -> i64 {
1463 let float = |f: f64| {
1464 if f.fract() == 0.0 && f >= i64::MIN as f64 && f < i64::MAX as f64 {
1465 f as i64
1466 } else {
1467 0
1468 }
1469 };
1470 match v {
1471 Value::Integer(n) => *n,
1472 Value::Number(f) => float(*f),
1473 Value::String(s) => match s.to_str() {
1474 Ok(t) => {
1475 let t = t.trim();
1476 t.parse::<i64>()
1477 .ok()
1478 .or_else(|| t.parse::<f64>().ok().map(float))
1479 .unwrap_or(0)
1480 }
1481 Err(_) => 0,
1482 },
1483 _ => 0,
1484 }
1485}
1486
1487fn set_attr(e: &mut Engine, p: NodeRef, n: u32, v: Option<i32>) {
1488 let node = e.nodes.get_mut(p);
1489 let mut list: Vec<(u32, i32)> = node.attr.as_ref().map(|l| l.to_vec()).unwrap_or_default();
1490 list.retain(|(k, _)| *k != n);
1491 if let Some(v) = v {
1492 list.push((n, v));
1493 list.sort();
1494 }
1495 node.attr = if list.is_empty() {
1496 None
1497 } else {
1498 Some(std::rc::Rc::from(list))
1499 };
1500}
1501
1502fn slide(e: &mut Engine, mut p: NodeRef) -> NodeRef {
1503 loop {
1504 let n = e.nodes.next(p);
1505 if n.is_null() {
1506 return p;
1507 }
1508 e.nodes.get_mut(n).prev = p;
1509 p = n;
1510 }
1511}
1512
1513fn count(e: &Engine, mut p: NodeRef, ty: Option<u8>) -> i64 {
1514 let mut n = 0;
1515 while !p.is_null() {
1516 if ty.is_none_or(|t| e.nodes.id(p) == t) {
1517 n += 1;
1518 }
1519 p = e.nodes.next(p);
1520 }
1521 n
1522}
1523
1524fn set_fn<A, R, F>(lua: &Lua, t: &Table, name: &str, f: F) -> mlua::Result<()>
1526where
1527 A: mlua::FromLuaMulti,
1528 R: mlua::IntoLuaMulti,
1529 F: Fn(&Lua, A) -> mlua::Result<R> + 'static,
1530{
1531 t.set(name, lua.create_function(f)?)
1532}
1533
1534fn glyph_of(e: &Engine, p: NodeRef) -> Option<(u32, u32, u16)> {
1536 match &e.nodes.get(p).data {
1537 NodeData::Glyph(g) => Some((g.chr, g.font, e.nodes.get(p).subtype)),
1538 _ => None,
1539 }
1540}
1541
1542type Pick = fn(&Engine, NodeRef) -> Option<Vec<Value>>;
1544
1545fn list_iterator(
1548 lua: &Lua,
1549 start: NodeRef,
1550 direct: bool,
1551 pick: Pick,
1552) -> mlua::Result<(mlua::Function, Value, Value)> {
1553 let iter = lua.create_function(move |lua, (_s, cur): (Value, Value)| {
1554 let mut p = match to_ref(&cur) {
1555 Some(c) => with_engine(lua, |e| e.nodes.next(c))?,
1556 None => start,
1557 };
1558 let found = with_engine(lua, |e| {
1559 while !p.is_null() {
1560 if let Some(v) = pick(e, p) {
1561 return Some((p, v));
1562 }
1563 p = e.nodes.next(p);
1564 }
1565 None
1566 })?;
1567 let Some((p, vals)) = found else {
1568 return Ok(MultiValue::new());
1569 };
1570 let mut out = vec![wrap(lua, p, direct)?];
1571 out.extend(vals);
1572 Ok(MultiValue::from_iter(out))
1573 })?;
1574 Ok((iter, Value::Nil, Value::Nil))
1575}
1576
1577fn install_accessors(lua: &Lua, t: &Table, direct: bool) -> mlua::Result<()> {
1580 for (name, key) in [
1582 ("data", "data"),
1583 ("lang", "lang"),
1584 ("fam", "fam"),
1585 ("sub", "sub"),
1586 ("sup", "sup"),
1587 ("nucleus", "nucleus"),
1588 ("shift", "shift"),
1589 ("dir", "dir"),
1590 ("direction", "dir"),
1591 ("components", "components"),
1592 ("expansion", "expansion_factor"),
1593 ] {
1594 set_fn(
1595 lua,
1596 t,
1597 &format!("get{name}"),
1598 move |lua, v: Value| match to_ref(&v) {
1599 Some(p) => get_field_impl(lua, p, key, direct),
1600 None => Ok(Value::Nil),
1601 },
1602 )?;
1603 set_fn(
1604 lua,
1605 t,
1606 &format!("set{name}"),
1607 move |lua, (v, x): (Value, Value)| match to_ref(&v) {
1608 Some(p) => set_field(lua, p, key, x),
1609 None => Ok(()),
1610 },
1611 )?;
1612 }
1613 if direct {
1614 fn has_sync(id: u8) -> bool {
1618 matches!(
1619 id,
1620 id::GLYPH | id::KERN | id::GLUE | id::MATH | id::HLIST | id::VLIST | id::RULE
1621 )
1622 }
1623 set_fn(lua, t, "get_synctex_fields", |lua, v: Value| {
1624 let Some(p) = to_ref(&v) else {
1625 return Ok(MultiValue::new());
1626 };
1627 let f = with_engine(lua, |e| {
1628 has_sync(e.nodes.id(p)).then(|| e.nodes.sync_fields(p))
1629 })?;
1630 Ok(match f {
1631 Some((tag, line)) => MultiValue::from_iter([
1632 Value::Integer(i64::from(tag)),
1633 Value::Integer(i64::from(line)),
1634 ]),
1635 None => MultiValue::new(),
1636 })
1637 })?;
1638 set_fn(
1639 lua,
1640 t,
1641 "set_synctex_fields",
1642 |lua, (v, tag, line): (Value, Option<i64>, Option<i64>)| {
1643 let Some(p) = to_ref(&v) else {
1644 return Ok(());
1645 };
1646 with_engine(lua, |e| {
1647 if !has_sync(e.nodes.id(p)) {
1648 return;
1649 }
1650 let (mut t0, mut l0) = e.nodes.sync_fields(p);
1651 if let Some(t) = tag.filter(|&t| t > 0) {
1652 t0 = t as u32;
1653 }
1654 if let Some(l) = line.filter(|&l| l > 0) {
1655 l0 = l as i32;
1656 }
1657 e.nodes.set_sync_fields(p, (t0, l0));
1658 })
1659 },
1660 )?;
1661 }
1662 set_fn(lua, t, "getattributelist", |lua, v: Value| {
1663 match to_ref(&v) {
1664 Some(p) => attr_value(lua, p),
1665 None => Ok(Value::Nil),
1666 }
1667 })?;
1668 set_fn(
1669 lua,
1670 t,
1671 "setattributelist",
1672 |lua, (v, a): (Value, Value)| match to_ref(&v) {
1673 Some(p) => set_attr_value(lua, p, &a),
1674 None => Ok(()),
1675 },
1676 )?;
1677 set_fn(lua, t, "getboth", move |lua, v: Value| {
1678 let Some(p) = to_ref(&v) else {
1679 return Ok((Value::Nil, Value::Nil));
1680 };
1681 let (pv, nx) = with_engine(lua, |e| (e.nodes.get(p).prev, e.nodes.next(p)))?;
1682 Ok((wrap(lua, pv, direct)?, wrap(lua, nx, direct)?))
1683 })?;
1684 set_fn(
1685 lua,
1686 t,
1687 "setboth",
1688 |lua, (v, a, b): (Value, Value, Value)| {
1689 if let Some(p) = to_ref(&v) {
1690 let a = to_ref(&a).unwrap_or(NodeRef::NULL);
1691 let b = to_ref(&b).unwrap_or(NodeRef::NULL);
1692 with_engine(lua, |e| {
1693 let n = e.nodes.get_mut(p);
1694 n.prev = a;
1695 n.next = b;
1696 })?;
1697 }
1698 Ok(())
1699 },
1700 )?;
1701 set_fn(lua, t, "setlink", move |lua, args: MultiValue| {
1702 let refs: Vec<NodeRef> = args.iter().filter_map(to_ref).collect();
1703 with_engine(lua, |e| {
1704 for w in refs.windows(2) {
1705 e.nodes.link(w[0], w[1]);
1706 }
1707 })?;
1708 match refs.first() {
1709 Some(p) => wrap(lua, *p, direct),
1710 None => Ok(Value::Nil),
1711 }
1712 })?;
1713 set_fn(lua, t, "setsplit", |lua, (a, b): (Value, Value)| {
1714 let (a, b) = (to_ref(&a), to_ref(&b));
1715 with_engine(lua, |e| {
1716 if let Some(a) = a {
1717 e.nodes.get_mut(a).next = NodeRef::NULL;
1718 }
1719 if let Some(b) = b {
1720 e.nodes.get_mut(b).prev = NodeRef::NULL;
1721 }
1722 })
1723 })?;
1724 set_fn(
1725 lua,
1726 t,
1727 "getwhd",
1728 move |lua, (v, ex): (Value, Option<bool>)| {
1729 let Some(p) = to_ref(&v) else {
1730 return Ok(MultiValue::new());
1731 };
1732 let mut out = Vec::new();
1733 for k in ["width", "height", "depth"] {
1734 out.push(get_field_impl(lua, p, k, direct)?);
1735 }
1736 if ex == Some(true) {
1737 out.push(get_field_impl(lua, p, "expansion_factor", direct)?);
1738 }
1739 Ok(MultiValue::from_iter(out))
1740 },
1741 )?;
1742 set_fn(
1743 lua,
1744 t,
1745 "setwhd",
1746 |lua, (v, w, h, d): (Value, Value, Value, Value)| {
1747 if let Some(p) = to_ref(&v) {
1748 for (k, x) in [("width", w), ("height", h), ("depth", d)] {
1749 if !x.is_nil() {
1750 set_field(lua, p, k, x)?;
1751 }
1752 }
1753 }
1754 Ok(())
1755 },
1756 )?;
1757 set_fn(
1758 lua,
1759 t,
1760 "getdisc",
1761 move |lua, (v, tails): (Value, Option<bool>)| {
1762 let Some(p) = to_ref(&v) else {
1763 return Ok(MultiValue::new());
1764 };
1765 let lists = with_engine(lua, |e| match &e.nodes.get(p).data {
1766 NodeData::Disc {
1767 pre, post, replace, ..
1768 } => {
1769 let l = [*pre, *post, *replace];
1770 let t = l.map(|h| if h.is_null() { h } else { e.nodes.tail_of(h) });
1771 Some((l, t))
1772 }
1773 _ => None,
1774 })?;
1775 let Some((l, tl)) = lists else {
1776 return Ok(MultiValue::new());
1777 };
1778 let mut out = Vec::new();
1779 for h in l {
1780 out.push(wrap(lua, h, direct)?);
1781 }
1782 if tails == Some(true) {
1783 for h in tl {
1784 out.push(wrap(lua, h, direct)?);
1785 }
1786 }
1787 Ok(MultiValue::from_iter(out))
1788 },
1789 )?;
1790 set_fn(lua, t, "setdisc", |lua, args: MultiValue| {
1791 let v: Vec<Value> = args.into_iter().collect();
1792 let Some(p) = v.first().and_then(to_ref) else {
1793 return Ok(());
1794 };
1795 let get = |i: usize| v.get(i).and_then(to_ref).unwrap_or(NodeRef::NULL);
1796 let (pre, post, rep) = (get(1), get(2), get(3));
1797 let sub = v.get(4).filter(|x| !x.is_nil()).map(int);
1798 let pen = v.get(5).filter(|x| !x.is_nil()).map(int);
1799 with_engine(lua, |e| {
1800 if e.nodes.id(p) != id::DISC {
1801 return;
1802 }
1803 e.nodes.set_disc_lists(p, (pre, post, rep));
1804 if let Some(s) = sub {
1805 e.nodes.get_mut(p).subtype = s as u16;
1806 }
1807 if let (Some(x), NodeData::Disc { penalty, .. }) = (pen, &mut e.nodes.get_mut(p).data) {
1808 *penalty = x as i32;
1809 }
1810 })
1811 })?;
1812 set_fn(lua, t, "getglue", |lua, v: Value| {
1813 let Some(p) = to_ref(&v) else {
1814 return Ok(MultiValue::new());
1815 };
1816 let vals: Vec<Value> = with_engine(lua, |e| match &e.nodes.get(p).data {
1817 NodeData::Glue { spec: g, .. } | NodeData::Math { glue: g, .. } => vec![
1818 Value::Integer(g.width.into()),
1819 Value::Integer(g.stretch.into()),
1820 Value::Integer(g.shrink.into()),
1821 Value::Integer(g.stretch_order.into()),
1822 Value::Integer(g.shrink_order.into()),
1823 ],
1824 NodeData::Box(b) => vec![
1826 Value::Number(b.glue_set),
1827 Value::Integer(b.glue_order.into()),
1828 Value::Integer(b.glue_sign.into()),
1829 ],
1830 _ => Vec::new(),
1831 })?;
1832 Ok(MultiValue::from_iter(vals))
1833 })?;
1834 set_fn(lua, t, "setglue", |lua, args: MultiValue| {
1835 let v: Vec<Value> = args.into_iter().collect();
1836 let Some(p) = v.first().and_then(to_ref) else {
1837 return Ok(());
1838 };
1839 let n = |i: usize| v.get(i).map(int).unwrap_or(0);
1840 with_engine(lua, |e| {
1841 let g = match &mut e.nodes.get_mut(p).data {
1842 NodeData::Glue { spec, .. } => spec,
1843 NodeData::Math { glue, .. } => glue,
1844 _ => return,
1845 };
1846 g.width = n(1) as i32;
1847 g.stretch = n(2) as i32;
1848 g.shrink = n(3) as i32;
1849 g.stretch_order = n(4) as u8;
1850 g.shrink_order = n(5) as u8;
1851 })
1852 })?;
1853 set_fn(lua, t, "is_zero_glue", |lua, v: Value| {
1854 let Some(p) = to_ref(&v) else {
1855 return Ok(false);
1856 };
1857 with_engine(lua, |e| match &e.nodes.get(p).data {
1858 NodeData::Glue { spec: g, .. } | NodeData::Math { glue: g, .. } => {
1859 g.width == 0 && g.stretch == 0 && g.shrink == 0
1860 }
1861 _ => true,
1862 })
1863 })?;
1864 set_fn(lua, t, "getoffsets", |lua, v: Value| {
1865 let Some(p) = to_ref(&v) else {
1866 return Ok((Value::Nil, Value::Nil));
1867 };
1868 with_engine(lua, |e| match &e.nodes.get(p).data {
1869 NodeData::Glyph(g) => (
1870 Value::Integer(g.xoffset.into()),
1871 Value::Integer(g.yoffset.into()),
1872 ),
1873 _ => (Value::Nil, Value::Nil),
1874 })
1875 })?;
1876 set_fn(
1877 lua,
1878 t,
1879 "setoffsets",
1880 |lua, (v, x, y): (Value, Value, Value)| {
1881 if let Some(p) = to_ref(&v) {
1882 if !x.is_nil() {
1883 set_field(lua, p, "xoffset", x)?;
1884 }
1885 if !y.is_nil() {
1886 set_field(lua, p, "yoffset", y)?;
1887 }
1888 }
1889 Ok(())
1890 },
1891 )?;
1892 set_fn(lua, t, "is_char", |lua, (v, f): (Value, Option<i64>)| {
1895 let Some(p) = to_ref(&v) else {
1896 return Ok(Value::Nil);
1897 };
1898 Ok(match with_engine(lua, |e| glyph_of(e, p))? {
1899 Some((c, font, sub)) if sub < 256 && f.is_none_or(|f| f == i64::from(font)) => {
1900 Value::Integer(c.into())
1901 }
1902 Some(_) => Value::Boolean(false),
1903 None => Value::Nil,
1904 })
1905 })?;
1906 set_fn(lua, t, "is_glyph", |lua, v: Value| {
1907 let Some(p) = to_ref(&v) else {
1908 return Ok(MultiValue::from_iter([Value::Boolean(false)]));
1909 };
1910 Ok(match with_engine(lua, |e| glyph_of(e, p))? {
1911 Some((c, font, _)) => {
1912 MultiValue::from_iter([Value::Integer(c.into()), Value::Integer(font.into())])
1913 }
1914 None => MultiValue::from_iter([Value::Boolean(false)]),
1915 })
1916 })?;
1917 set_fn(lua, t, "traverse_char", move |lua, v: Value| {
1918 let start = to_ref(&v).unwrap_or(NodeRef::NULL);
1919 list_iterator(lua, start, direct, |e, p| match glyph_of(e, p) {
1920 Some((c, f, s)) if s < 256 => {
1921 Some(vec![Value::Integer(c.into()), Value::Integer(f.into())])
1922 }
1923 _ => None,
1924 })
1925 })?;
1926 set_fn(lua, t, "traverse_glyph", move |lua, v: Value| {
1927 let start = to_ref(&v).unwrap_or(NodeRef::NULL);
1928 list_iterator(lua, start, direct, |e, p| {
1929 glyph_of(e, p).map(|(c, f, _)| vec![Value::Integer(c.into()), Value::Integer(f.into())])
1930 })
1931 })?;
1932 set_fn(lua, t, "traverse_list", move |lua, v: Value| {
1933 let start = to_ref(&v).unwrap_or(NodeRef::NULL);
1934 let (iter, s, c) = list_iterator(lua, start, direct, |e, p| {
1935 let n = e.nodes.get(p);
1936 match &n.data {
1937 NodeData::Box(b) if n.id == id::HLIST || n.id == id::VLIST => Some(vec![
1938 Value::Integer(n.id.into()),
1939 Value::Integer(n.subtype.into()),
1940 Value::Integer(i64::from(b.head.0)),
1942 ]),
1943 _ => None,
1944 }
1945 })?;
1946 let wrapper = lua
1947 .load(
1948 "local iter, direct = ...
1949 return function(s, c)
1950 local n, i, st, l = iter(s, c)
1951 if n == nil then return nil end
1952 if l == 0 then l = nil elseif not direct then l = node.direct.tonode(l) end
1953 return n, i, st, l
1954 end",
1955 )
1956 .set_name("=[retex]")
1957 .call::<mlua::Function>((iter, direct))?;
1958 Ok((wrapper, s, c))
1959 })?;
1960 set_fn(lua, t, "has_glyph", move |lua, v: Value| {
1961 let mut p = to_ref(&v).unwrap_or(NodeRef::NULL);
1962 let found = with_engine(lua, |e| {
1963 while !p.is_null() {
1964 let i = e.nodes.id(p);
1965 if i == id::GLYPH || i == id::DISC {
1966 return p;
1967 }
1968 p = e.nodes.next(p);
1969 }
1970 NodeRef::NULL
1971 })?;
1972 wrap(lua, found, direct)
1973 })?;
1974 set_fn(
1975 lua,
1976 t,
1977 "first_glyph",
1978 move |lua, (v, stop): (Value, Value)| {
1979 let mut p = to_ref(&v).unwrap_or(NodeRef::NULL);
1980 let stop = to_ref(&stop);
1981 let found = with_engine(lua, |e| {
1984 while !p.is_null() {
1985 if glyph_of(e, p).is_some_and(|(_, _, s)| s & 0xFF == 1) {
1986 return p;
1987 }
1988 if Some(p) == stop {
1989 break;
1990 }
1991 p = e.nodes.next(p);
1992 }
1993 NodeRef::NULL
1994 })?;
1995 if direct && found.is_null() {
1996 return Ok(Value::Integer(0));
1997 }
1998 wrap(lua, found, direct)
1999 },
2000 )?;
2001 set_fn(lua, t, "get_attribute", |lua, (v, n): (Value, i64)| {
2002 let Some(p) = to_ref(&v) else {
2003 return Ok(None);
2004 };
2005 with_engine(lua, |e| {
2006 e.nodes.get(p).attr.as_ref().and_then(|l| {
2007 l.iter()
2008 .find(|(k, _)| i64::from(*k) == n)
2009 .map(|x| i64::from(x.1))
2010 })
2011 })
2012 })?;
2013 set_fn(
2014 lua,
2015 t,
2016 "find_attribute",
2017 move |lua, (v, n): (Value, i64)| {
2018 let mut p = to_ref(&v).unwrap_or(NodeRef::NULL);
2019 let found =
2020 with_engine(lua, |e| {
2021 while !p.is_null() {
2022 if let Some(x) =
2023 e.nodes.get(p).attr.as_ref().and_then(|l| {
2024 l.iter().find(|(k, _)| i64::from(*k) == n).map(|x| x.1)
2025 })
2026 {
2027 return Some((x, p));
2028 }
2029 p = e.nodes.next(p);
2030 }
2031 None
2032 })?;
2033 match found {
2034 Some((x, p)) => Ok(MultiValue::from_iter([
2035 Value::Integer(x.into()),
2036 wrap(lua, p, direct)?,
2037 ])),
2038 None => Ok(MultiValue::new()),
2039 }
2040 },
2041 )?;
2042 set_fn(
2043 lua,
2044 t,
2045 "effective_glue",
2046 |lua, (g, parent): (Value, Value)| {
2047 let (Some(g), Some(b)) = (to_ref(&g), to_ref(&parent)) else {
2048 return Ok(Value::Nil);
2049 };
2050 with_engine(lua, |e| {
2051 let spec = match &e.nodes.get(g).data {
2052 NodeData::Glue { spec, .. } => *spec,
2053 _ => return Value::Nil,
2054 };
2055 let Some(bx) = e.nodes.box_data(b) else {
2056 return Value::Number(spec.width.into());
2057 };
2058 let mut w = f64::from(spec.width);
2059 if bx.glue_sign == 1 && spec.stretch_order == bx.glue_order {
2060 w += bx.glue_set * f64::from(spec.stretch);
2061 } else if bx.glue_sign == 2 && spec.shrink_order == bx.glue_order {
2062 w -= bx.glue_set * f64::from(spec.shrink);
2063 }
2064 Value::Number(w.round())
2065 })
2066 },
2067 )?;
2068 for (name, protect) in [("protect_glyph", true), ("unprotect_glyph", false)] {
2069 set_fn(lua, t, name, move |lua, v: Value| {
2070 if let Some(p) = to_ref(&v) {
2071 with_engine(lua, |e| {
2072 if e.nodes.id(p) == id::GLYPH {
2076 let n = e.nodes.get_mut(p);
2077 let s = n.subtype;
2078 n.subtype = match (protect, s) {
2079 (true, 1) => 256,
2080 (true, s) if s <= 256 => s + 256,
2081 (false, s) if s > 256 => s - 256,
2082 (_, s) => s,
2083 };
2084 }
2085 })?;
2086 }
2087 Ok(())
2088 })?;
2089 }
2090 set_fn(lua, t, "uses_font", |lua, (v, f): (Value, i64)| {
2091 let Some(p) = to_ref(&v) else {
2092 return Ok(false);
2093 };
2094 with_engine(lua, |e| {
2095 let uses = |e: &Engine, mut q: NodeRef| {
2096 while !q.is_null() {
2097 if glyph_of(e, q).is_some_and(|(_, font, _)| i64::from(font) == f) {
2098 return true;
2099 }
2100 q = e.nodes.next(q);
2101 }
2102 false
2103 };
2104 match &e.nodes.get(p).data {
2105 NodeData::Glyph(g) => i64::from(g.font) == f,
2106 NodeData::Disc {
2107 pre, post, replace, ..
2108 } => uses(e, *pre) || uses(e, *post) || uses(e, *replace),
2109 _ => false,
2110 }
2111 })
2112 })?;
2113 set_fn(lua, t, "getbox", move |lua, n: i64| {
2114 let p = with_engine(lua, |e| {
2115 match e.eqtb.get(retex_engine::eqtb::Loc::Box(n as u32)) {
2116 retex_engine::eqtb::Val::Node(p) => p,
2117 _ => NodeRef::NULL,
2118 }
2119 })?;
2120 wrap(lua, p, direct)
2121 })?;
2122 set_fn(lua, t, "setbox", |lua, args: MultiValue| {
2123 let mut v: Vec<Value> = args.into_iter().collect();
2124 let global = matches!(v.first(), Some(Value::String(s)) if &*s.to_str()? == "global");
2125 if global {
2126 v.remove(0);
2127 }
2128 let n = v.first().map(int).unwrap_or(0) as u32;
2129 let p = v.get(1).and_then(to_ref).unwrap_or(NodeRef::NULL);
2130 with_engine(lua, |e| {
2131 e.define(
2132 global,
2133 retex_engine::eqtb::Loc::Box(n),
2134 retex_engine::eqtb::Val::Node(p),
2135 )
2136 })
2137 })?;
2138 if direct {
2139 set_fn(lua, t, "is_direct", |_, v: Value| {
2141 Ok(match v {
2142 Value::Integer(n) if n > 0 => Value::Integer(n),
2143 _ => Value::Boolean(false),
2144 })
2145 })?;
2146 } else {
2147 for (name, next) in [("next", true), ("prev", false)] {
2148 set_fn(lua, t, name, move |lua, v: Value| {
2149 let Some(p) = to_ref(&v) else {
2150 return Ok(Value::Nil);
2151 };
2152 let q = with_engine(lua, |e| {
2153 if next {
2154 e.nodes.next(p)
2155 } else {
2156 e.nodes.get(p).prev
2157 }
2158 })?;
2159 wrap(lua, q, false)
2160 })?;
2161 }
2162 }
2163 Ok(())
2164}
2165
2166const LUA_REFS_KEY: &str = "retex.node.luarefs";
2168
2169fn lua_refs(lua: &Lua) -> mlua::Result<Table> {
2170 match lua.named_registry_value::<Value>(LUA_REFS_KEY)? {
2171 Value::Table(t) => Ok(t),
2172 _ => {
2173 let t = lua.create_table()?;
2174 lua.set_named_registry_value(LUA_REFS_KEY, &t)?;
2175 Ok(t)
2176 }
2177 }
2178}
2179
2180fn lua_refs_synced(lua: &Lua) -> mlua::Result<Table> {
2183 let refs = lua_refs(lua)?;
2184 let copies = with_engine(lua, |e| std::mem::take(&mut e.nodes.lua_refs.copies))?;
2185 for (new, old) in copies {
2186 let v: Value = refs.raw_get(old)?;
2187 refs.raw_set(new, v)?;
2188 }
2189 Ok(refs)
2190}
2191
2192fn lua_ref(lua: &Lua, v: Value) -> mlua::Result<i32> {
2195 let refs = lua_refs_synced(lua)?;
2196 let n = with_engine(lua, |e| e.nodes.lua_refs.take())?;
2197 refs.raw_set(n, v)?;
2198 Ok(n)
2199}
2200
2201fn new_whatsit(sub: u16) -> retex_engine::nodes::Whatsit {
2203 use retex_engine::nodes::{LateData, UserValue, Whatsit, whatsit_sub as ws};
2204 match sub {
2205 ws::SPECIAL => Whatsit::Special { data: Vec::new() },
2206 ws::LATE_LUA => Whatsit::LateLua {
2207 data: LateData::Bytes(Vec::new()),
2208 name: Vec::new(),
2209 },
2210 ws::PDF_LITERAL | ws::PDF_LATE_LITERAL => Whatsit::PdfLiteral {
2211 mode: 0,
2212 data: LateData::Bytes(Vec::new()),
2213 late: sub == ws::PDF_LATE_LITERAL,
2214 },
2215 ws::PDF_COLORSTACK => Whatsit::PdfColorstack {
2216 stack: 0,
2217 cmd: 0,
2218 data: Vec::new(),
2219 },
2220 ws::PDF_SETMATRIX => Whatsit::PdfSetmatrix { data: Vec::new() },
2221 ws::PDF_SAVE => Whatsit::PdfSave,
2222 ws::PDF_RESTORE => Whatsit::PdfRestore,
2223 ws::PDF_REFOBJ => Whatsit::PdfRefobj { objnum: 0 },
2224 ws::PDF_END_LINK => Whatsit::PdfEndLink,
2225 ws::PDF_START_LINK => Whatsit::PdfStartLink {
2227 dims: [0; 3],
2228 objnum: 0,
2229 attr: Vec::new(),
2230 action: retex_engine::pdfext::PdfAction::User(Vec::new()),
2231 },
2232 ws::PDF_ANNOT => Whatsit::PdfAnnot {
2233 dims: [0; 3],
2234 objnum: 0,
2235 data: Vec::new(),
2236 },
2237 ws::SAVE_POS => Whatsit::SavePos,
2238 ws::PDF_LINK_STATE => Whatsit::PdfLinkState { state: 0 },
2239 ws::USER_DEFINED => Whatsit::UserDefined {
2240 user_id: 0,
2241 value: UserValue::Int(0),
2242 },
2243 _ => Whatsit::Other {
2244 fields: Vec::new(),
2245 data: Vec::new(),
2246 },
2247 }
2248}
2249
2250fn dim_index(key: &str) -> usize {
2252 match key {
2253 "width" => 0,
2254 "height" => 1,
2255 _ => 2,
2256 }
2257}
2258
2259fn whatsit_field(e: &mut Engine, w: &retex_engine::nodes::Whatsit, key: &str) -> R {
2261 use retex_engine::nodes::{LateData, UserValue, Whatsit};
2262 let mut late = |d: &LateData| match d {
2263 LateData::Bytes(b) | LateData::Lua { data: b, .. } => R::S(b.clone()),
2264 LateData::Tokens(t) => {
2265 R::S(e.print_to_string(|e| e.show_token_list(t, 0, t.len(), i64::MAX)))
2266 }
2267 };
2268 match (w, key) {
2269 (Whatsit::Special { data }, "data") => R::S(data.clone()),
2270 (Whatsit::LateLua { data, .. }, "data" | "string") => late(data),
2271 (Whatsit::LateLua { name, .. }, "name") => R::S(name.clone()),
2272 (Whatsit::PdfLiteral { mode, .. }, "mode") => R::I((*mode).into()),
2273 (Whatsit::PdfLiteral { data, .. }, "data") => late(data),
2274 (Whatsit::PdfColorstack { stack, .. }, "stack") => R::I((*stack).into()),
2275 (Whatsit::PdfColorstack { cmd, .. }, "command") => R::I((*cmd).into()),
2276 (Whatsit::PdfColorstack { data, .. }, "data") => R::S(data.clone()),
2277 (Whatsit::PdfSetmatrix { data }, "data") => R::S(data.clone()),
2278 (Whatsit::PdfRefobj { objnum }, "objnum") => R::I((*objnum).into()),
2279 (Whatsit::PdfStartLink { objnum, .. } | Whatsit::PdfAnnot { objnum, .. }, "objnum") => {
2282 R::I((*objnum).into())
2283 }
2284 (Whatsit::PdfStartLink { attr, .. }, "link_attr") => R::S(attr.clone()),
2285 (Whatsit::PdfAnnot { data, .. }, "data") => R::S(data.clone()),
2286 (
2287 Whatsit::PdfStartLink { dims, .. } | Whatsit::PdfAnnot { dims, .. },
2288 "width" | "height" | "depth",
2289 ) => R::I(dims[dim_index(key)].into()),
2290 (Whatsit::PdfLinkState { state }, "objnum") => R::I((*state).into()),
2293 (Whatsit::UserDefined { user_id, .. }, "user_id") => R::I((*user_id).into()),
2294 (Whatsit::UserDefined { value, .. }, "type") => R::I(i64::from(user_type(value))),
2295 (Whatsit::UserDefined { value, .. }, "value") => match value {
2296 UserValue::Int(n) => R::I((*n).into()),
2297 UserValue::Str(s) => R::S(s.clone()),
2298 UserValue::Tokens(t) => {
2299 R::S(e.print_to_string(|e| e.show_token_list(t, 0, t.len(), i64::MAX)))
2300 }
2301 UserValue::Nodes(p) => R::N(*p),
2302 UserValue::Lua(n) => R::L(*n),
2303 UserValue::Attr => R::Nil,
2304 },
2305 _ => R::Nil,
2306 }
2307}
2308
2309fn user_type(v: &retex_engine::nodes::UserValue) -> u8 {
2311 use retex_engine::nodes::UserValue;
2312 match v {
2313 UserValue::Int(_) => b'd',
2314 UserValue::Str(_) => b's',
2315 UserValue::Tokens(_) => b't',
2316 UserValue::Nodes(_) => b'n',
2317 UserValue::Lua(_) => b'l',
2318 UserValue::Attr => b'a',
2319 }
2320}
2321
2322fn set_whatsit_field(lua: &Lua, p: NodeRef, key: &str, v: Value) -> mlua::Result<()> {
2324 use retex_engine::nodes::{LateData, Whatsit};
2325 let s: Option<Vec<u8>> = match &v {
2326 Value::String(s) => Some(s.as_bytes().to_vec()),
2327 Value::Integer(n) => Some(n.to_string().into_bytes()),
2328 Value::Number(n) => Some(crate::texlib::lua_number_string(*n).into_bytes()),
2329 _ => None,
2330 };
2331 let node = to_ref(&v);
2332 let i = int(&v);
2333 let (is_user, utype) = with_engine(lua, |e| match &e.nodes.get(p).data {
2335 NodeData::Whatsit(w) => match &**w {
2336 Whatsit::UserDefined { value, .. } => (true, user_type(value)),
2337 _ => (false, 0),
2338 },
2339 _ => (false, 0),
2340 })?;
2341 let lref = if is_user && key == "value" && utype == b'l' {
2342 Some(lua_ref(lua, v.clone())?)
2343 } else {
2344 None
2345 };
2346 let ini = with_engine(lua, |e| e.ini_version)?;
2349 let lua_string = |s: Vec<u8>| -> mlua::Result<LateData> {
2350 if ini {
2351 Ok(LateData::Bytes(s))
2352 } else {
2353 let reference = lua_ref(lua, Value::String(lua.create_string(&s)?))?;
2354 Ok(LateData::Lua { data: s, reference })
2355 }
2356 };
2357 let late = match (&s, key) {
2358 (Some(s), "data") => Some(lua_string(s.clone())?),
2359 _ => None,
2360 };
2361 with_engine(lua, |e| {
2362 let tokens = |e: &Engine, s: &[u8]| -> retex_engine::token::TokenList {
2363 let _ = e;
2364 let toks: Vec<retex_engine::token::Token> = String::from_utf8_lossy(s)
2365 .chars()
2366 .map(|c| {
2367 if c == ' ' {
2368 retex_engine::token::Token::SPACE
2369 } else {
2370 retex_engine::token::Token::other(c as u32)
2371 }
2372 })
2373 .collect();
2374 std::rc::Rc::from(toks)
2375 };
2376 let toks = s.as_ref().map(|s| tokens(e, s));
2377 let old_ref = e.nodes.lua_ref_of(p);
2380 set_whatsit_data(e, p, key, &s, &late, &toks, node, i, lref);
2381 if let Some(r) = old_ref
2382 && e.nodes.lua_ref_of(p) != Some(r)
2383 {
2384 e.nodes.lua_refs.release(r);
2385 }
2386 })
2387}
2388
2389#[allow(clippy::too_many_arguments)]
2391fn set_whatsit_data(
2392 e: &mut Engine,
2393 p: NodeRef,
2394 key: &str,
2395 s: &Option<Vec<u8>>,
2396 late: &Option<retex_engine::nodes::LateData>,
2397 toks: &Option<retex_engine::token::TokenList>,
2398 node: Option<NodeRef>,
2399 i: i64,
2400 lref: Option<i32>,
2401) {
2402 use retex_engine::nodes::{LateData, UserValue, Whatsit};
2403 {
2404 let NodeData::Whatsit(w) = &mut e.nodes.get_mut(p).data else {
2405 return;
2406 };
2407 match (&mut **w, key) {
2408 (Whatsit::Special { data }, "data") => {
2409 if let Some(s) = &s {
2410 *data = s.clone();
2411 }
2412 }
2413 (Whatsit::LateLua { data, .. }, "data" | "string") => {
2414 if let Some(l) = late.clone().or_else(|| s.clone().map(LateData::Bytes)) {
2415 *data = l;
2416 }
2417 }
2418 (Whatsit::LateLua { name, .. }, "name") => {
2419 if let Some(s) = &s {
2420 *name = s.clone();
2421 }
2422 }
2423 (Whatsit::PdfLiteral { mode, .. }, "mode") => *mode = i as u8,
2424 (Whatsit::PdfLiteral { data, .. }, "data") => {
2425 if let Some(l) = late.clone() {
2426 *data = l;
2427 }
2428 }
2429 (Whatsit::PdfColorstack { stack, .. }, "stack") => *stack = i as i32,
2430 (Whatsit::PdfColorstack { cmd, .. }, "command") => *cmd = i as u8,
2431 (Whatsit::PdfColorstack { data, .. }, "data")
2432 | (Whatsit::PdfSetmatrix { data }, "data") => {
2433 if let Some(s) = &s {
2434 *data = s.clone();
2435 }
2436 }
2437 (Whatsit::PdfRefobj { objnum }, "objnum")
2438 | (Whatsit::PdfStartLink { objnum, .. }, "objnum")
2439 | (Whatsit::PdfAnnot { objnum, .. }, "objnum") => *objnum = i as i32,
2440 (Whatsit::PdfStartLink { attr: data, .. }, "link_attr")
2441 | (Whatsit::PdfAnnot { data, .. }, "data") => {
2442 if let Some(s) = &s {
2443 *data = s.clone();
2444 }
2445 }
2446 (
2447 Whatsit::PdfStartLink { dims, .. } | Whatsit::PdfAnnot { dims, .. },
2448 "width" | "height" | "depth",
2449 ) => dims[dim_index(key)] = i as i32,
2450 (Whatsit::PdfLinkState { state }, "value" | "objnum") => *state = i as i32,
2451 (Whatsit::UserDefined { user_id, .. }, "user_id") => *user_id = i as i32,
2452 (Whatsit::UserDefined { value, .. }, "type") => {
2453 *value = match i as u8 {
2454 b's' => UserValue::Str(Vec::new()),
2455 b't' => UserValue::Tokens(retex_engine::token::empty_list()),
2456 b'n' => UserValue::Nodes(NodeRef::NULL),
2457 b'l' => UserValue::Lua(0),
2458 b'a' => UserValue::Attr,
2459 _ => UserValue::Int(0),
2460 }
2461 }
2462 (Whatsit::UserDefined { value, .. }, "value") => {
2463 *value = match user_type(value) {
2464 b's' => UserValue::Str(s.clone().unwrap_or_default()),
2465 b't' => UserValue::Tokens(
2466 toks.clone().unwrap_or_else(retex_engine::token::empty_list),
2467 ),
2468 b'n' => UserValue::Nodes(node.unwrap_or(NodeRef::NULL)),
2469 b'l' => UserValue::Lua(lref.unwrap_or(0)),
2470 b'a' => UserValue::Attr,
2471 _ => UserValue::Int(i as i32),
2472 }
2473 }
2474 _ => {}
2475 }
2476 }
2477}
2478
2479fn install_list_ops(lua: &Lua, t: &Table, direct: bool) -> mlua::Result<()> {
2482 use retex_engine::build::PackSpec;
2483 for (name, horizontal) in [("hpack", true), ("vpack", false)] {
2484 set_fn(lua, t, name, move |lua, args: MultiValue| {
2485 let v: Vec<Value> = args.into_iter().collect();
2486 let head = v.first().and_then(to_ref).unwrap_or(NodeRef::NULL);
2487 let w = v.get(1).map(int).unwrap_or(0) as i32;
2488 let exactly = matches!(v.get(2), Some(Value::String(s)) if &*s.to_str()? == "exactly");
2489 let spec = if exactly {
2490 PackSpec::Exactly(w)
2491 } else {
2492 PackSpec::Additional(w)
2493 };
2494 let dir = match v.get(3) {
2495 Some(Value::String(s)) => match &*s.to_str()? {
2496 "TLT" => 0,
2497 "TRT" => 1,
2498 "LTL" => 2,
2499 "RTT" => 3,
2500 _ => -1,
2501 },
2502 Some(Value::Integer(n)) => *n as i8,
2503 _ => -1,
2504 };
2505 let (b, badness) = with_engine(lua, |e| {
2506 e.pack_dir = dir;
2507 let b = if horizontal {
2508 e.hpack(head, spec)
2509 } else {
2510 e.vpack(head, spec)
2511 };
2512 e.pack_dir = -1;
2513 (b, e.last_badness)
2514 })?;
2515 Ok((wrap(lua, b, direct)?, Value::Integer(badness.into())))
2516 })?;
2517 }
2518 set_fn(lua, t, "write", |lua, v: Value| {
2520 if let Some(p) = to_ref(&v) {
2521 with_engine(lua, |e| {
2522 let tail = e.nodes.tail_of(p);
2523 e.tail_append(p);
2524 e.cur_list_mut().tail = tail;
2525 })?;
2526 }
2527 Ok(())
2528 })?;
2529 set_fn(lua, t, "last_node", move |lua, ()| {
2531 let p = with_engine(lua, |e| {
2532 let l = e.cur_list();
2533 let (head, tail) = (l.head, l.tail);
2534 if tail == head {
2535 return NodeRef::NULL;
2536 }
2537 let mut q = head;
2538 while e.nodes.next(q) != tail {
2539 q = e.nodes.next(q);
2540 }
2541 e.nodes.get_mut(q).next = NodeRef::NULL;
2542 let n = e.nodes.get_mut(tail);
2543 n.prev = NodeRef::NULL;
2544 e.cur_list_mut().tail = q;
2545 tail
2546 })?;
2547 wrap(lua, p, direct)
2548 })?;
2549 set_fn(lua, t, "end_of_math", move |lua, v: Value| {
2551 let Some(mut p) = to_ref(&v) else {
2552 return Ok(Value::Nil);
2553 };
2554 let found = with_engine(lua, |e| {
2555 if e.nodes.id(p) == id::MATH && e.nodes.subtype(p) == 0 {
2556 p = e.nodes.next(p);
2557 }
2558 while !p.is_null() {
2559 if e.nodes.id(p) == id::MATH && e.nodes.subtype(p) == 1 {
2560 return p;
2561 }
2562 p = e.nodes.next(p);
2563 }
2564 NodeRef::NULL
2565 })?;
2566 wrap(lua, found, direct)
2567 })?;
2568 for (name, protect) in [("protect_glyphs", true), ("unprotect_glyphs", false)] {
2569 set_fn(lua, t, name, move |lua, (h, stop): (Value, Value)| {
2570 let mut p = to_ref(&h).unwrap_or(NodeRef::NULL);
2571 let stop = to_ref(&stop);
2572 with_engine(lua, |e| {
2573 while !p.is_null() {
2574 if e.nodes.id(p) == id::GLYPH {
2575 let n = e.nodes.get_mut(p);
2576 let s = n.subtype;
2577 n.subtype = match (protect, s) {
2578 (true, 1) => 256,
2579 (true, s) if s <= 256 => s + 256,
2580 (false, s) if s > 256 => s - 256,
2581 (_, s) => s,
2582 };
2583 }
2584 if Some(p) == stop {
2585 break;
2586 }
2587 p = e.nodes.next(p);
2588 }
2589 })
2590 })?;
2591 }
2592 Ok(())
2593}