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