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