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