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 "prepend_prevdepth",
1547 lua.create_function(move |lua, (n, pd): (Value, Option<i64>)| {
1551 let Some(n) = to_ref(&n) else {
1552 return Ok(MultiValue::from_iter([Value::Nil]));
1553 };
1554 let pd = pd.unwrap_or(i64::from(retex_engine::maincontrol::IGNORE_DEPTH)) as i32;
1555 let (head, depth) = with_engine(lua, |e| {
1556 let depth = e.node_depth(n);
1557 match e.interline_glue(n, pd) {
1558 Some(g) => {
1559 e.nodes.link(g, n);
1560 (g, depth)
1561 }
1562 None => (n, depth),
1563 }
1564 })?;
1565 Ok(MultiValue::from_iter([
1566 wrap(lua, head, direct)?,
1567 Value::Integer(i64::from(depth)),
1568 ]))
1569 })?,
1570 )?;
1571 t.set(
1572 "remove",
1573 lua.create_function(move |lua, (h, c): (Value, Value)| {
1574 let (Some(h), Some(c)) = (to_ref(&h), to_ref(&c)) else {
1576 return Ok(MultiValue::from_iter([h, Value::Nil]));
1577 };
1578 let (head, next) = with_engine(lua, |e| {
1579 let pv = e.nodes.get(c).prev;
1580 let nx = e.nodes.next(c);
1581 let head = if c == h {
1582 nx
1583 } else {
1584 e.nodes.link(pv, nx);
1585 h
1586 };
1587 if !nx.is_null() && c == h {
1588 e.nodes.get_mut(nx).prev = NodeRef::NULL;
1589 }
1590 (head, nx)
1592 })?;
1593 Ok(MultiValue::from_iter([
1594 wrap(lua, head, direct)?,
1595 wrap(lua, next, direct)?,
1596 ]))
1597 })?,
1598 )?;
1599 t.set(
1600 "has_field",
1601 lua.create_function(|lua, (v, f): (Value, String)| match to_ref(&v) {
1602 Some(p) => {
1603 let t = with_engine(lua, |e| e.nodes.id(p))?;
1604 Ok(info()
1605 .fields
1606 .get(&t)
1607 .is_some_and(|fl| fl.iter().any(|(_, n)| *n == f)))
1608 }
1609 None => Ok(false),
1610 })?,
1611 )?;
1612 t.set(
1613 "getfield",
1614 lua.create_function(move |lua, (v, f): (Value, String)| match to_ref(&v) {
1615 Some(p) => get_field_impl(lua, p, &f, direct),
1616 None => Ok(Value::Nil),
1617 })?,
1618 )?;
1619 t.set(
1620 "setfield",
1621 lua.create_function(|lua, (v, f, x): (Value, String, Value)| match to_ref(&v) {
1622 Some(p) => set_field(lua, p, &f, x),
1623 None => Ok(()),
1624 })?,
1625 )?;
1626 for name in [
1628 "id", "subtype", "next", "prev", "char", "font", "width", "height", "depth", "list",
1629 "penalty", "kern", "leader",
1630 ] {
1631 let key = if name == "list" { "head" } else { name };
1632 if name != "id" && name != "subtype" {
1633 t.set(
1634 format!("get{name}"),
1635 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1636 Some(p) => get_field_impl(lua, p, key, direct),
1637 None => Ok(Value::Nil),
1638 })?,
1639 )?;
1640 } else {
1641 t.set(
1642 format!("get{name}"),
1643 lua.create_function(move |lua, v: Value| match to_ref(&v) {
1644 Some(p) => get_field_impl(lua, p, key, direct),
1645 None => Ok(Value::Nil),
1646 })?,
1647 )?;
1648 }
1649 t.set(
1650 format!("set{name}"),
1651 lua.create_function(move |lua, (v, x): (Value, Value)| match to_ref(&v) {
1652 Some(p) => set_field(lua, p, key, x),
1653 None => Ok(()),
1654 })?,
1655 )?;
1656 }
1657 install_accessors(lua, t, direct)?;
1658 t.set(
1662 "mlist_to_hlist",
1663 lua.create_function(move |lua, args: MultiValue| {
1664 let v: Vec<Value> = args.into_iter().collect();
1665 let head = v.first().and_then(to_ref).unwrap_or(NodeRef::NULL);
1666 with_engine(lua, |e| {
1669 let mut p = head;
1670 let mut steps = e.nodes.capacity();
1671 while !p.is_null() && steps > 0 {
1672 steps -= 1;
1673 match e.nodes.id(p) {
1674 id::GLYPH
1675 | id::MATH
1676 | id::MARGIN_KERN
1677 | id::LOCAL_PAR
1678 | id::DIR
1679 | id::SUB_BOX
1680 | id::UNSET
1681 | id::GLUE_SPEC
1682 | id::HLIST
1683 | id::VLIST
1684 | id::TEMP
1685 | id::DELIM
1686 | id::MATH_CHAR
1687 | id::SUB_MLIST
1688 | id::MATH_TEXT_CHAR
1689 | 38 => e.confusion("mlist1"),
1690 id::BOUNDARY => e.confusion("mlist3"),
1691 _ => {}
1692 }
1693 p = e.nodes.next(p);
1694 }
1695 })?;
1696 let style = match v.get(1) {
1697 Some(Value::String(s)) => {
1698 let s = s.to_str()?.to_string();
1699 crate::texlib::MATH_STYLE_NAMES
1700 .iter()
1701 .position(|n| *n == s)
1702 .unwrap_or(2) as i32
1703 }
1704 Some(Value::Integer(n)) => (*n as i32).clamp(0, 7),
1705 _ => 2,
1706 };
1707 let pen = matches!(v.get(2), Some(Value::Boolean(true)));
1708 if head.is_null() {
1709 return Ok(Value::Nil);
1710 }
1711 let r = with_engine(lua, |e| e.mlist_to_hlist(head, style, pen))?;
1712 if r.is_null() {
1713 return Ok(Value::Nil);
1714 }
1715 wrap(lua, r, direct)
1716 })?,
1717 )?;
1718 t.set(
1721 "rangedimensions",
1722 lua.create_function(|lua, args: MultiValue| {
1723 let first = args.get(1).and_then(to_ref).unwrap_or(NodeRef::NULL);
1724 let stop = args.get(2).and_then(to_ref).unwrap_or(NodeRef::NULL);
1725 if first.is_null() {
1726 return Ok((0, 0, 0));
1727 }
1728 with_engine(lua, |e| e.natural_dimensions(first, stop))
1729 })?,
1730 )?;
1731 for (name, pass) in [
1734 ("ligaturing", retex_engine::nodeproc::NodePass::Ligaturing),
1735 ("kerning", retex_engine::nodeproc::NodePass::Kerning),
1736 ] {
1737 t.set(
1738 name,
1739 lua.create_function(move |lua, args: MultiValue| {
1740 let Some(h) = args.front().and_then(to_ref) else {
1741 return Ok(MultiValue::from_iter([Value::Nil]));
1742 };
1743 let (h, tl) = with_engine(lua, |e| e.lua_node_pass(pass, h))?;
1744 Ok(MultiValue::from_iter([
1745 wrap(lua, h, direct)?,
1746 wrap(lua, tl, direct)?,
1747 Value::Boolean(true),
1748 ]))
1749 })?,
1750 )?;
1751 }
1752 for name in ["hyphenating", "flatten_discretionaries"] {
1753 t.set(
1754 name,
1755 lua.create_function(move |_, args: MultiValue| Ok(args))?,
1756 )?;
1757 }
1758 install_list_ops(lua, t, direct)?;
1759 t.set(
1761 "family_font",
1762 lua.create_function(|lua, fam: i64| {
1763 if !(0..retex_engine::eqtb::NUM_FAMILIES as i64).contains(&fam) {
1764 return Ok(0);
1765 }
1766 with_engine(lua, |e| i64::from(e.fam_fnt(fam as u32, 0)))
1767 })?,
1768 )?;
1769 t.set(
1770 "dimensions",
1771 lua.create_function(|lua, args: MultiValue| {
1772 let args: Vec<Value> = args.into_iter().collect();
1777 let (set, rest) = if args.len() >= 4 {
1778 let num = |v: &Value| match v {
1779 Value::Integer(i) => *i as f64,
1780 Value::Number(f) => *f,
1781 _ => 0.0,
1782 };
1783 (
1784 Some((num(&args[0]), num(&args[1]) as i32, num(&args[2]) as i32)),
1785 &args[3..],
1786 )
1787 } else {
1788 (None, &args[..])
1789 };
1790 let refs: Vec<NodeRef> = rest.iter().filter_map(to_ref).collect();
1791 let Some(&first) = refs.first() else {
1792 return Ok((0, 0, 0));
1793 };
1794 let stop = refs.get(1).copied().unwrap_or(NodeRef::NULL);
1795 with_engine(lua, |e| match set {
1796 Some((gs, sign, order)) => e.set_dimensions(first, stop, gs, sign, order),
1797 None => e.natural_dimensions(first, stop),
1798 })
1799 })?,
1800 )?;
1801 t.set(
1802 "has_attribute",
1803 lua.create_function(|lua, (v, n, val): (Value, i64, Option<i64>)| {
1804 let Some(p) = to_ref(&v) else {
1805 return Ok(None);
1806 };
1807 let a = with_engine(lua, |e| {
1808 e.nodes
1809 .get(p)
1810 .attr
1811 .as_ref()
1812 .and_then(|l| l.iter().find(|(k, _)| i64::from(*k) == n).map(|x| x.1))
1813 })?;
1814 Ok(match (a, val) {
1815 (Some(x), Some(w)) if i64::from(x) != w => None,
1816 (a, _) => a.map(i64::from),
1817 })
1818 })?,
1819 )?;
1820 t.set(
1821 "set_attribute",
1822 lua.create_function(|lua, (v, n, val): (Value, Value, Value)| {
1823 let (n, val) = (to_integer(&n), to_integer(&val));
1826 if let Some(p) = to_ref(&v) {
1827 with_engine(lua, |e| set_attr(e, p, n as u32, Some(val as i32)))?;
1828 }
1829 Ok(())
1830 })?,
1831 )?;
1832 t.set(
1833 "unset_attribute",
1834 lua.create_function(|lua, (v, n): (Value, i64)| {
1835 if let Some(p) = to_ref(&v) {
1836 with_engine(lua, |e| set_attr(e, p, n as u32, None))?;
1837 }
1838 Ok(())
1839 })?,
1840 )?;
1841 Ok(())
1842}
1843
1844fn to_integer(v: &Value) -> i64 {
1847 let float = |f: f64| {
1848 if f.fract() == 0.0 && f >= i64::MIN as f64 && f < i64::MAX as f64 {
1849 f as i64
1850 } else {
1851 0
1852 }
1853 };
1854 match v {
1855 Value::Integer(n) => *n,
1856 Value::Number(f) => float(*f),
1857 Value::String(s) => match s.to_str() {
1858 Ok(t) => {
1859 let t = t.trim();
1860 t.parse::<i64>()
1861 .ok()
1862 .or_else(|| t.parse::<f64>().ok().map(float))
1863 .unwrap_or(0)
1864 }
1865 Err(_) => 0,
1866 },
1867 _ => 0,
1868 }
1869}
1870
1871fn set_attr(e: &mut Engine, p: NodeRef, n: u32, v: Option<i32>) {
1872 let node = e.nodes.get_mut(p);
1873 let mut list: Vec<(u32, i32)> = node.attr.as_ref().map(|l| l.to_vec()).unwrap_or_default();
1874 list.retain(|(k, _)| *k != n);
1875 if let Some(v) = v {
1876 list.push((n, v));
1877 list.sort();
1878 }
1879 node.attr = if list.is_empty() {
1880 None
1881 } else {
1882 Some(std::rc::Rc::from(list))
1883 };
1884}
1885
1886fn impossible(lua: &Lua, v: &Value, what: &str) -> mlua::Result<()> {
1889 if what == "free"
1893 && let Some(p) = to_ref(v)
1894 && with_engine(lua, |e| p == e.nodes.par_temp)?
1895 {
1896 let msg = format!("attempt to free an impossible node {}", p.0);
1897 return match with_engine(lua, |e| e.luatex_fatal("nodes", &msg)) {
1898 Err(e) => Err(e),
1899 };
1900 }
1901 if let Value::Integer(n) = *v {
1902 let cap = with_engine(lua, |e| e.nodes.capacity())?;
1903 if n < 0 || n as u64 >= cap as u64 {
1904 let msg = format!("attempt to {what} an impossible node {n}");
1905 return match with_engine(lua, |e| e.luatex_fatal("nodes", &msg)) {
1906 Err(e) => Err(e),
1907 };
1908 }
1909 }
1910 Ok(())
1911}
1912
1913fn circular(e: &mut Engine) -> ! {
1916 e.luatex_fatal("nodes", "circular node list")
1917}
1918
1919fn tail(e: &mut Engine, p: NodeRef) -> NodeRef {
1921 match e.nodes.bounded_tail(p) {
1922 Some(t) => t,
1923 None => circular(e),
1924 }
1925}
1926
1927fn slide(e: &mut Engine, mut p: NodeRef) -> NodeRef {
1928 for _ in 0..e.nodes.capacity() {
1929 let n = e.nodes.next(p);
1930 if n.is_null() {
1931 return p;
1932 }
1933 e.nodes.get_mut(n).prev = p;
1934 p = n;
1935 }
1936 circular(e)
1937}
1938
1939fn count(e: &mut Engine, mut p: NodeRef, ty: Option<u8>) -> i64 {
1940 let mut n = 0;
1941 let mut left = e.nodes.capacity();
1942 while !p.is_null() {
1943 if left == 0 {
1944 circular(e);
1945 }
1946 left -= 1;
1947 if ty.is_none_or(|t| e.nodes.id(p) == t) {
1948 n += 1;
1949 }
1950 p = e.nodes.next(p);
1951 }
1952 n
1953}
1954
1955fn set_fn<A, R, F>(lua: &Lua, t: &Table, name: &str, f: F) -> mlua::Result<()>
1957where
1958 A: mlua::FromLuaMulti,
1959 R: mlua::IntoLuaMulti,
1960 F: Fn(&Lua, A) -> mlua::Result<R> + 'static,
1961{
1962 t.set(name, lua.create_function(f)?)
1963}
1964
1965fn glyph_of(e: &Engine, p: NodeRef) -> Option<(u32, u32, u16)> {
1967 match &e.nodes.get(p).data {
1968 NodeData::Glyph(g) => Some((g.chr, g.font, e.nodes.get(p).subtype)),
1969 _ => None,
1970 }
1971}
1972
1973type Pick = fn(&Engine, NodeRef) -> Option<Vec<Value>>;
1975
1976fn list_iterator(
1979 lua: &Lua,
1980 start: NodeRef,
1981 direct: bool,
1982 pick: Pick,
1983) -> mlua::Result<(mlua::Function, Value, Value)> {
1984 let iter = lua.create_function(move |lua, (_s, cur): (Value, Value)| {
1985 let mut p = match to_ref(&cur) {
1986 Some(c) => with_engine(lua, |e| e.nodes.next(c))?,
1987 None => start,
1988 };
1989 let found = with_engine(lua, |e| {
1990 while !p.is_null() {
1991 if let Some(v) = pick(e, p) {
1992 return Some((p, v));
1993 }
1994 p = e.nodes.next(p);
1995 }
1996 None
1997 })?;
1998 let Some((p, vals)) = found else {
1999 return Ok(MultiValue::new());
2000 };
2001 let mut out = vec![wrap(lua, p, direct)?];
2002 out.extend(vals);
2003 Ok(MultiValue::from_iter(out))
2004 })?;
2005 Ok((iter, Value::Nil, Value::Nil))
2006}
2007
2008fn install_accessors(lua: &Lua, t: &Table, direct: bool) -> mlua::Result<()> {
2011 for (name, key) in [
2013 ("data", "data"),
2014 ("lang", "lang"),
2015 ("fam", "fam"),
2016 ("sub", "sub"),
2017 ("sup", "sup"),
2018 ("nucleus", "nucleus"),
2019 ("shift", "shift"),
2020 ("dir", "dir"),
2021 ("direction", "direction"),
2022 ("components", "components"),
2023 ("expansion", "expansion_factor"),
2024 ] {
2025 set_fn(
2026 lua,
2027 t,
2028 &format!("get{name}"),
2029 move |lua, v: Value| match to_ref(&v) {
2030 Some(p) => get_field_impl(lua, p, key, direct),
2031 None => Ok(Value::Nil),
2032 },
2033 )?;
2034 set_fn(
2035 lua,
2036 t,
2037 &format!("set{name}"),
2038 move |lua, (v, x): (Value, Value)| match to_ref(&v) {
2039 Some(p) => set_field(lua, p, key, x),
2040 None => Ok(()),
2041 },
2042 )?;
2043 }
2044 if direct {
2045 fn has_sync(id: u8) -> bool {
2049 matches!(
2050 id,
2051 id::GLYPH | id::KERN | id::GLUE | id::MATH | id::HLIST | id::VLIST | id::RULE
2052 )
2053 }
2054 set_fn(lua, t, "get_synctex_fields", |lua, v: Value| {
2055 let Some(p) = to_ref(&v) else {
2056 return Ok(MultiValue::new());
2057 };
2058 let f = with_engine(lua, |e| {
2059 has_sync(e.nodes.id(p)).then(|| e.nodes.sync_fields(p))
2060 })?;
2061 Ok(match f {
2062 Some((tag, line)) => MultiValue::from_iter([
2063 Value::Integer(i64::from(tag)),
2064 Value::Integer(i64::from(line)),
2065 ]),
2066 None => MultiValue::new(),
2067 })
2068 })?;
2069 set_fn(
2070 lua,
2071 t,
2072 "set_synctex_fields",
2073 |lua, (v, tag, line): (Value, Option<i64>, Option<i64>)| {
2074 let Some(p) = to_ref(&v) else {
2075 return Ok(());
2076 };
2077 with_engine(lua, |e| {
2078 if !has_sync(e.nodes.id(p)) {
2079 return;
2080 }
2081 let (mut t0, mut l0) = e.nodes.sync_fields(p);
2082 if let Some(t) = tag.filter(|&t| t > 0) {
2083 t0 = t as u32;
2084 }
2085 if let Some(l) = line.filter(|&l| l > 0) {
2086 l0 = l as i32;
2087 }
2088 e.nodes.set_sync_fields(p, (t0, l0));
2089 })
2090 },
2091 )?;
2092 }
2093 set_fn(lua, t, "current_attr", |lua, ()| {
2094 match with_engine(lua, |e| e.cur_attr())? {
2095 Some(a) => Ok(Value::UserData(lua.create_userdata(LAttr(a))?)),
2096 None => Ok(Value::Nil),
2097 }
2098 })?;
2099 set_fn(lua, t, "getattributelist", |lua, v: Value| {
2100 match to_ref(&v) {
2101 Some(p) => attr_value(lua, p),
2102 None => Ok(Value::Nil),
2103 }
2104 })?;
2105 set_fn(
2106 lua,
2107 t,
2108 "setattributelist",
2109 |lua, (v, a): (Value, Value)| match to_ref(&v) {
2110 Some(p) => set_attr_value(lua, p, &a),
2111 None => Ok(()),
2112 },
2113 )?;
2114 set_fn(lua, t, "getboth", move |lua, v: Value| {
2115 let Some(p) = to_ref(&v) else {
2116 return Ok((Value::Nil, Value::Nil));
2117 };
2118 let (pv, nx) = with_engine(lua, |e| (e.nodes.get(p).prev, e.nodes.next(p)))?;
2119 Ok((wrap(lua, pv, direct)?, wrap(lua, nx, direct)?))
2120 })?;
2121 set_fn(
2122 lua,
2123 t,
2124 "setboth",
2125 |lua, (v, a, b): (Value, Value, Value)| {
2126 if let Some(p) = to_ref(&v) {
2127 let a = to_ref(&a).unwrap_or(NodeRef::NULL);
2128 let b = to_ref(&b).unwrap_or(NodeRef::NULL);
2129 with_engine(lua, |e| {
2130 let n = e.nodes.get_mut(p);
2131 n.prev = a;
2132 n.next = b;
2133 })?;
2134 }
2135 Ok(())
2136 },
2137 )?;
2138 set_fn(lua, t, "setlink", move |lua, args: MultiValue| {
2139 let refs: Vec<NodeRef> = args.iter().filter_map(to_ref).collect();
2140 with_engine(lua, |e| {
2141 for w in refs.windows(2) {
2142 e.nodes.link(w[0], w[1]);
2143 }
2144 })?;
2145 match refs.first() {
2146 Some(p) => wrap(lua, *p, direct),
2147 None => Ok(Value::Nil),
2148 }
2149 })?;
2150 set_fn(lua, t, "setsplit", |lua, (a, b): (Value, Value)| {
2151 let (a, b) = (to_ref(&a), to_ref(&b));
2152 with_engine(lua, |e| {
2153 if let Some(a) = a {
2154 e.nodes.get_mut(a).next = NodeRef::NULL;
2155 }
2156 if let Some(b) = b {
2157 e.nodes.get_mut(b).prev = NodeRef::NULL;
2158 }
2159 })
2160 })?;
2161 set_fn(
2162 lua,
2163 t,
2164 "getwhd",
2165 move |lua, (v, ex): (Value, Option<bool>)| {
2166 let Some(p) = to_ref(&v) else {
2167 return Ok(MultiValue::new());
2168 };
2169 let mut out = Vec::new();
2170 for k in ["width", "height", "depth"] {
2171 out.push(get_field_impl(lua, p, k, direct)?);
2172 }
2173 if ex == Some(true) {
2174 out.push(get_field_impl(lua, p, "expansion_factor", direct)?);
2175 }
2176 Ok(MultiValue::from_iter(out))
2177 },
2178 )?;
2179 set_fn(
2180 lua,
2181 t,
2182 "setwhd",
2183 |lua, (v, w, h, d): (Value, Value, Value, Value)| {
2184 if let Some(p) = to_ref(&v) {
2185 for (k, x) in [("width", w), ("height", h), ("depth", d)] {
2186 if !x.is_nil() {
2187 set_field(lua, p, k, x)?;
2188 }
2189 }
2190 }
2191 Ok(())
2192 },
2193 )?;
2194 set_fn(
2195 lua,
2196 t,
2197 "getdisc",
2198 move |lua, (v, tails): (Value, Option<bool>)| {
2199 let Some(p) = to_ref(&v) else {
2200 return Ok(MultiValue::new());
2201 };
2202 let lists = with_engine(lua, |e| match &e.nodes.get(p).data {
2203 NodeData::Disc {
2204 pre, post, replace, ..
2205 } => {
2206 let l = [*pre, *post, *replace];
2207 let t = l.map(|h| if h.is_null() { h } else { e.nodes.tail_of(h) });
2208 Some((l, t))
2209 }
2210 _ => None,
2211 })?;
2212 let Some((l, tl)) = lists else {
2213 return Ok(MultiValue::new());
2214 };
2215 let mut out = Vec::new();
2216 for h in l {
2217 out.push(wrap(lua, h, direct)?);
2218 }
2219 if tails == Some(true) {
2220 for h in tl {
2221 out.push(wrap(lua, h, direct)?);
2222 }
2223 }
2224 Ok(MultiValue::from_iter(out))
2225 },
2226 )?;
2227 set_fn(lua, t, "setdisc", |lua, args: MultiValue| {
2228 let v: Vec<Value> = args.into_iter().collect();
2229 let Some(p) = v.first().and_then(to_ref) else {
2230 return Ok(());
2231 };
2232 let get = |i: usize| v.get(i).and_then(to_ref).unwrap_or(NodeRef::NULL);
2233 let (pre, post, rep) = (get(1), get(2), get(3));
2234 let sub = v.get(4).filter(|x| !x.is_nil()).map(int);
2235 let pen = v.get(5).filter(|x| !x.is_nil()).map(int);
2236 with_engine(lua, |e| {
2237 if e.nodes.id(p) != id::DISC {
2238 return;
2239 }
2240 e.nodes.set_disc_lists(p, (pre, post, rep));
2241 if let Some(s) = sub {
2242 e.nodes.get_mut(p).subtype = s as u16;
2243 }
2244 if let (Some(x), NodeData::Disc { penalty, .. }) = (pen, &mut e.nodes.get_mut(p).data) {
2245 *penalty = x as i32;
2246 }
2247 })
2248 })?;
2249 set_fn(lua, t, "getglue", |lua, v: Value| {
2250 let Some(p) = to_ref(&v) else {
2251 return Ok(MultiValue::new());
2252 };
2253 let vals: Vec<Value> = with_engine(lua, |e| match &e.nodes.get(p).data {
2254 NodeData::Glue { spec: g, .. } | NodeData::Math { glue: g, .. } => vec![
2255 Value::Integer(g.width.into()),
2256 Value::Integer(g.stretch.into()),
2257 Value::Integer(g.shrink.into()),
2258 Value::Integer(g.stretch_order.into()),
2259 Value::Integer(g.shrink_order.into()),
2260 ],
2261 NodeData::Box(b) => vec![
2263 Value::Number(b.glue_set),
2264 Value::Integer(b.glue_order.into()),
2265 Value::Integer(b.glue_sign.into()),
2266 ],
2267 _ => Vec::new(),
2268 })?;
2269 Ok(MultiValue::from_iter(vals))
2270 })?;
2271 set_fn(lua, t, "setglue", |lua, args: MultiValue| {
2272 let v: Vec<Value> = args.into_iter().collect();
2273 let Some(p) = v.first().and_then(to_ref) else {
2274 return Ok(());
2275 };
2276 let n = |i: usize| v.get(i).map(int).unwrap_or(0);
2277 with_engine(lua, |e| {
2278 let g = match &mut e.nodes.get_mut(p).data {
2279 NodeData::Glue { spec, .. } => spec,
2280 NodeData::Math { glue, .. } => glue,
2281 _ => return,
2282 };
2283 g.width = n(1) as i32;
2284 g.stretch = n(2) as i32;
2285 g.shrink = n(3) as i32;
2286 g.stretch_order = n(4) as u8;
2287 g.shrink_order = n(5) as u8;
2288 })
2289 })?;
2290 set_fn(lua, t, "is_zero_glue", |lua, v: Value| {
2291 let Some(p) = to_ref(&v) else {
2292 return Ok(false);
2293 };
2294 with_engine(lua, |e| match &e.nodes.get(p).data {
2295 NodeData::Glue { spec: g, .. } | NodeData::Math { glue: g, .. } => {
2296 g.width == 0 && g.stretch == 0 && g.shrink == 0
2297 }
2298 _ => true,
2299 })
2300 })?;
2301 set_fn(lua, t, "getoffsets", |lua, v: Value| {
2302 let Some(p) = to_ref(&v) else {
2303 return Ok((Value::Nil, Value::Nil));
2304 };
2305 with_engine(lua, |e| match &e.nodes.get(p).data {
2306 NodeData::Glyph(g) => (
2307 Value::Integer(g.xoffset.into()),
2308 Value::Integer(g.yoffset.into()),
2309 ),
2310 _ => (Value::Nil, Value::Nil),
2311 })
2312 })?;
2313 set_fn(
2314 lua,
2315 t,
2316 "setoffsets",
2317 |lua, (v, x, y): (Value, Value, Value)| {
2318 if let Some(p) = to_ref(&v) {
2319 if !x.is_nil() {
2320 set_field(lua, p, "xoffset", x)?;
2321 }
2322 if !y.is_nil() {
2323 set_field(lua, p, "yoffset", y)?;
2324 }
2325 }
2326 Ok(())
2327 },
2328 )?;
2329 set_fn(lua, t, "is_char", |lua, (v, f): (Value, Option<i64>)| {
2332 let Some(p) = to_ref(&v) else {
2333 return Ok(Value::Nil);
2334 };
2335 Ok(match with_engine(lua, |e| glyph_of(e, p))? {
2336 Some((c, font, sub)) if sub < 256 && f.is_none_or(|f| f == i64::from(font)) => {
2337 Value::Integer(c.into())
2338 }
2339 Some(_) => Value::Boolean(false),
2340 None => Value::Nil,
2341 })
2342 })?;
2343 set_fn(lua, t, "is_glyph", |lua, v: Value| {
2344 let Some(p) = to_ref(&v) else {
2345 return Ok(MultiValue::from_iter([Value::Boolean(false)]));
2346 };
2347 Ok(match with_engine(lua, |e| glyph_of(e, p))? {
2348 Some((c, font, _)) => {
2349 MultiValue::from_iter([Value::Integer(c.into()), Value::Integer(font.into())])
2350 }
2351 None => MultiValue::from_iter([Value::Boolean(false)]),
2352 })
2353 })?;
2354 set_fn(lua, t, "traverse_char", move |lua, v: Value| {
2355 let start = to_ref(&v).unwrap_or(NodeRef::NULL);
2356 list_iterator(lua, start, direct, |e, p| match glyph_of(e, p) {
2357 Some((c, f, s)) if s < 256 => {
2358 Some(vec![Value::Integer(c.into()), Value::Integer(f.into())])
2359 }
2360 _ => None,
2361 })
2362 })?;
2363 set_fn(lua, t, "traverse_glyph", move |lua, v: Value| {
2364 let start = to_ref(&v).unwrap_or(NodeRef::NULL);
2365 list_iterator(lua, start, direct, |e, p| {
2366 glyph_of(e, p).map(|(c, f, _)| vec![Value::Integer(c.into()), Value::Integer(f.into())])
2367 })
2368 })?;
2369 set_fn(lua, t, "traverse_list", move |lua, v: Value| {
2370 let start = to_ref(&v).unwrap_or(NodeRef::NULL);
2371 let (iter, s, c) = list_iterator(lua, start, direct, |e, p| {
2372 let n = e.nodes.get(p);
2373 match &n.data {
2374 NodeData::Box(b) if n.id == id::HLIST || n.id == id::VLIST => Some(vec![
2375 Value::Integer(n.id.into()),
2376 Value::Integer(n.subtype.into()),
2377 Value::Integer(i64::from(b.head.0)),
2379 ]),
2380 _ => None,
2381 }
2382 })?;
2383 let wrapper = lua
2384 .load(
2385 "local iter, direct = ...
2386 return function(s, c)
2387 local n, i, st, l = iter(s, c)
2388 if n == nil then return nil end
2389 if l == 0 then l = nil elseif not direct then l = node.direct.tonode(l) end
2390 return n, i, st, l
2391 end",
2392 )
2393 .set_name("=[retex]")
2394 .call::<mlua::Function>((iter, direct))?;
2395 Ok((wrapper, s, c))
2396 })?;
2397 set_fn(lua, t, "has_glyph", move |lua, v: Value| {
2398 let mut p = to_ref(&v).unwrap_or(NodeRef::NULL);
2399 let found = with_engine(lua, |e| {
2400 while !p.is_null() {
2401 let i = e.nodes.id(p);
2402 if i == id::GLYPH || i == id::DISC {
2403 return p;
2404 }
2405 p = e.nodes.next(p);
2406 }
2407 NodeRef::NULL
2408 })?;
2409 wrap(lua, found, direct)
2410 })?;
2411 set_fn(
2412 lua,
2413 t,
2414 "first_glyph",
2415 move |lua, (v, stop): (Value, Value)| {
2416 let mut p = to_ref(&v).unwrap_or(NodeRef::NULL);
2417 let stop = to_ref(&stop);
2418 let found = with_engine(lua, |e| {
2421 while !p.is_null() {
2422 if glyph_of(e, p).is_some_and(|(_, _, s)| s & 0xFF == 1) {
2423 return p;
2424 }
2425 if Some(p) == stop {
2426 break;
2427 }
2428 p = e.nodes.next(p);
2429 }
2430 NodeRef::NULL
2431 })?;
2432 if direct && found.is_null() {
2433 return Ok(Value::Integer(0));
2434 }
2435 wrap(lua, found, direct)
2436 },
2437 )?;
2438 set_fn(lua, t, "get_attribute", |lua, (v, n): (Value, i64)| {
2439 let Some(p) = to_ref(&v) else {
2440 return Ok(None);
2441 };
2442 with_engine(lua, |e| {
2443 e.nodes.get(p).attr.as_ref().and_then(|l| {
2444 l.iter()
2445 .find(|(k, _)| i64::from(*k) == n)
2446 .map(|x| i64::from(x.1))
2447 })
2448 })
2449 })?;
2450 set_fn(
2451 lua,
2452 t,
2453 "find_attribute",
2454 move |lua, (v, n): (Value, i64)| {
2455 let mut p = to_ref(&v).unwrap_or(NodeRef::NULL);
2456 let found =
2457 with_engine(lua, |e| {
2458 while !p.is_null() {
2459 if let Some(x) =
2460 e.nodes.get(p).attr.as_ref().and_then(|l| {
2461 l.iter().find(|(k, _)| i64::from(*k) == n).map(|x| x.1)
2462 })
2463 {
2464 return Some((x, p));
2465 }
2466 p = e.nodes.next(p);
2467 }
2468 None
2469 })?;
2470 match found {
2471 Some((x, p)) => Ok(MultiValue::from_iter([
2472 Value::Integer(x.into()),
2473 wrap(lua, p, direct)?,
2474 ])),
2475 None => Ok(MultiValue::new()),
2476 }
2477 },
2478 )?;
2479 set_fn(
2480 lua,
2481 t,
2482 "effective_glue",
2483 |lua, (g, parent): (Value, Value)| {
2484 let (Some(g), Some(b)) = (to_ref(&g), to_ref(&parent)) else {
2485 return Ok(Value::Nil);
2486 };
2487 with_engine(lua, |e| {
2488 let spec = match &e.nodes.get(g).data {
2489 NodeData::Glue { spec, .. } => *spec,
2490 _ => return Value::Nil,
2491 };
2492 let Some(bx) = e.nodes.box_data(b) else {
2493 return Value::Number(spec.width.into());
2494 };
2495 let mut w = f64::from(spec.width);
2496 if bx.glue_sign == 1 && spec.stretch_order == bx.glue_order {
2497 w += bx.glue_set * f64::from(spec.stretch);
2498 } else if bx.glue_sign == 2 && spec.shrink_order == bx.glue_order {
2499 w -= bx.glue_set * f64::from(spec.shrink);
2500 }
2501 Value::Number(w.round())
2502 })
2503 },
2504 )?;
2505 for (name, protect) in [("protect_glyph", true), ("unprotect_glyph", false)] {
2506 set_fn(lua, t, name, move |lua, v: Value| {
2507 if let Some(p) = to_ref(&v) {
2508 with_engine(lua, |e| {
2509 if e.nodes.id(p) == id::GLYPH {
2513 let n = e.nodes.get_mut(p);
2514 let s = n.subtype;
2515 n.subtype = match (protect, s) {
2516 (true, 1) => 256,
2517 (true, s) if s <= 256 => s + 256,
2518 (false, s) if s > 256 => s - 256,
2519 (_, s) => s,
2520 };
2521 }
2522 })?;
2523 }
2524 Ok(())
2525 })?;
2526 }
2527 set_fn(lua, t, "uses_font", |lua, (v, f): (Value, i64)| {
2528 let Some(p) = to_ref(&v) else {
2529 return Ok(false);
2530 };
2531 with_engine(lua, |e| {
2532 let uses = |e: &Engine, mut q: NodeRef| {
2533 while !q.is_null() {
2534 if glyph_of(e, q).is_some_and(|(_, font, _)| i64::from(font) == f) {
2535 return true;
2536 }
2537 q = e.nodes.next(q);
2538 }
2539 false
2540 };
2541 match &e.nodes.get(p).data {
2542 NodeData::Glyph(g) => i64::from(g.font) == f,
2543 NodeData::Disc {
2544 pre, post, replace, ..
2545 } => uses(e, *pre) || uses(e, *post) || uses(e, *replace),
2546 _ => false,
2547 }
2548 })
2549 })?;
2550 set_fn(lua, t, "getbox", move |lua, n: i64| {
2551 let p = with_engine(lua, |e| {
2552 match e.eqtb.get(retex_engine::eqtb::Loc::Box(n as u32)) {
2553 retex_engine::eqtb::Val::Node(p) => p,
2554 _ => NodeRef::NULL,
2555 }
2556 })?;
2557 wrap(lua, p, direct)
2558 })?;
2559 set_fn(lua, t, "setbox", |lua, args: MultiValue| {
2560 let mut v: Vec<Value> = args.into_iter().collect();
2561 let global = matches!(v.first(), Some(Value::String(s)) if &*s.to_str()? == "global");
2562 if global {
2563 v.remove(0);
2564 }
2565 let n = v.first().map(int).unwrap_or(0) as u32;
2566 let p = v.get(1).and_then(to_ref).unwrap_or(NodeRef::NULL);
2567 with_engine(lua, |e| {
2568 e.define(
2569 global,
2570 retex_engine::eqtb::Loc::Box(n),
2571 retex_engine::eqtb::Val::Node(p),
2572 )
2573 })
2574 })?;
2575 if direct {
2576 set_fn(lua, t, "is_direct", |_, v: Value| {
2578 Ok(match v {
2579 Value::Integer(n) if n > 0 => Value::Integer(n),
2580 _ => Value::Boolean(false),
2581 })
2582 })?;
2583 } else {
2584 for (name, next) in [("next", true), ("prev", false)] {
2585 set_fn(lua, t, name, move |lua, v: Value| {
2586 let Some(p) = to_ref(&v) else {
2587 return Ok(Value::Nil);
2588 };
2589 let q = with_engine(lua, |e| {
2590 if next {
2591 e.nodes.next(p)
2592 } else {
2593 e.nodes.get(p).prev
2594 }
2595 })?;
2596 wrap(lua, q, false)
2597 })?;
2598 }
2599 }
2600 Ok(())
2601}
2602
2603const LUA_REFS_KEY: &str = "retex.node.luarefs";
2605
2606fn lua_refs(lua: &Lua) -> mlua::Result<Table> {
2607 match lua.named_registry_value::<Value>(LUA_REFS_KEY)? {
2608 Value::Table(t) => Ok(t),
2609 _ => {
2610 let t = lua.create_table()?;
2611 lua.set_named_registry_value(LUA_REFS_KEY, &t)?;
2612 Ok(t)
2613 }
2614 }
2615}
2616
2617pub(crate) fn lua_refs_synced(lua: &Lua) -> mlua::Result<Table> {
2620 let refs = lua_refs(lua)?;
2621 let copies = with_engine(lua, |e| std::mem::take(&mut e.nodes.lua_refs.copies))?;
2622 for (new, old) in copies {
2623 let v: Value = refs.raw_get(old)?;
2624 refs.raw_set(new, v)?;
2625 }
2626 Ok(refs)
2627}
2628
2629pub(crate) fn lua_ref(lua: &Lua, v: Value) -> mlua::Result<i32> {
2632 let refs = lua_refs_synced(lua)?;
2633 let n = with_engine(lua, |e| e.nodes.lua_refs.take())?;
2634 refs.raw_set(n, v)?;
2635 Ok(n)
2636}
2637
2638fn new_whatsit(sub: u16) -> retex_engine::nodes::Whatsit {
2640 use retex_engine::nodes::{LateData, UserValue, Whatsit, whatsit_sub as ws};
2641 match sub {
2642 ws::SPECIAL => Whatsit::Special { data: Vec::new() },
2643 ws::LATE_LUA => Whatsit::LateLua {
2644 data: LateData::Bytes(Vec::new()),
2645 name: Vec::new(),
2646 },
2647 ws::PDF_LITERAL | ws::PDF_LATE_LITERAL => Whatsit::PdfLiteral {
2648 mode: 0,
2649 data: LateData::Bytes(Vec::new()),
2650 late: sub == ws::PDF_LATE_LITERAL,
2651 },
2652 ws::PDF_COLORSTACK => Whatsit::PdfColorstack {
2653 stack: 0,
2654 cmd: 0,
2655 data: Vec::new(),
2656 },
2657 ws::PDF_SETMATRIX => Whatsit::PdfSetmatrix { data: Vec::new() },
2658 ws::PDF_SAVE => Whatsit::PdfSave,
2659 ws::PDF_RESTORE => Whatsit::PdfRestore,
2660 ws::PDF_REFOBJ => Whatsit::PdfRefobj { objnum: 0 },
2661 ws::PDF_END_LINK => Whatsit::PdfEndLink,
2662 ws::PDF_START_LINK => Whatsit::PdfStartLink {
2664 dims: [0; 3],
2665 objnum: 0,
2666 attr: Vec::new(),
2667 action: retex_engine::pdfext::PdfAction::User(Vec::new()),
2668 },
2669 ws::PDF_ANNOT => Whatsit::PdfAnnot {
2670 dims: [0; 3],
2671 objnum: 0,
2672 data: Vec::new(),
2673 },
2674 ws::SAVE_POS => Whatsit::SavePos,
2675 ws::PDF_LINK_STATE => Whatsit::PdfLinkState { state: 0 },
2676 ws::USER_DEFINED => Whatsit::UserDefined {
2677 user_id: 0,
2678 value: UserValue::Int(0),
2679 },
2680 _ => Whatsit::Other {
2681 fields: Vec::new(),
2682 data: Vec::new(),
2683 },
2684 }
2685}
2686
2687fn dim_index(key: &str) -> usize {
2689 match key {
2690 "width" => 0,
2691 "height" => 1,
2692 _ => 2,
2693 }
2694}
2695
2696fn whatsit_field(e: &mut Engine, w: &retex_engine::nodes::Whatsit, key: &str) -> R {
2698 use retex_engine::nodes::{LateData, UserValue, Whatsit};
2699 let mut late = |d: &LateData| match d {
2700 LateData::Bytes(b) | LateData::Lua { data: b, .. } => R::S(b.clone()),
2701 LateData::Tokens(t) => {
2702 R::S(e.print_to_string(|e| e.show_token_list(t, 0, t.len(), i64::MAX)))
2703 }
2704 LateData::Function { reference } => R::L(*reference),
2705 };
2706 match (w, key) {
2707 (Whatsit::Special { data }, "data") => R::S(data.clone()),
2708 (Whatsit::LateLua { data, .. }, "data" | "string") => late(data),
2709 (Whatsit::LateLua { name, .. }, "name") => R::S(name.clone()),
2710 (Whatsit::PdfLiteral { mode, .. }, "mode") => R::I((*mode).into()),
2711 (Whatsit::PdfLiteral { data, .. }, "data") => late(data),
2712 (Whatsit::PdfColorstack { stack, .. }, "stack") => R::I((*stack).into()),
2713 (Whatsit::PdfColorstack { cmd, .. }, "command") => R::I((*cmd).into()),
2714 (Whatsit::PdfColorstack { data, .. }, "data") => R::S(data.clone()),
2715 (Whatsit::PdfSetmatrix { data }, "data") => R::S(data.clone()),
2716 (Whatsit::PdfRefobj { objnum }, "objnum") => R::I((*objnum).into()),
2717 (Whatsit::PdfStartLink { objnum, .. } | Whatsit::PdfAnnot { objnum, .. }, "objnum") => {
2720 R::I((*objnum).into())
2721 }
2722 (Whatsit::PdfStartLink { attr, .. }, "link_attr") => R::S(attr.clone()),
2723 (Whatsit::PdfAnnot { data, .. }, "data") => R::S(data.clone()),
2724 (
2725 Whatsit::PdfStartLink { dims, .. } | Whatsit::PdfAnnot { dims, .. },
2726 "width" | "height" | "depth",
2727 ) => R::I(dims[dim_index(key)].into()),
2728 (Whatsit::PdfLinkState { state }, "objnum") => R::I((*state).into()),
2731 (Whatsit::UserDefined { user_id, .. }, "user_id") => R::I((*user_id).into()),
2732 (Whatsit::UserDefined { value, .. }, "type") => R::I(i64::from(user_type(value))),
2733 (Whatsit::UserDefined { value, .. }, "value") => match value {
2734 UserValue::Int(n) => R::I((*n).into()),
2735 UserValue::Str(s) => R::S(s.clone()),
2736 UserValue::Tokens(t) => {
2737 R::S(e.print_to_string(|e| e.show_token_list(t, 0, t.len(), i64::MAX)))
2738 }
2739 UserValue::Nodes(p) => R::N(*p),
2740 UserValue::Lua(n) => R::L(*n),
2741 UserValue::Attr => R::Nil,
2742 },
2743 _ => R::Nil,
2744 }
2745}
2746
2747fn user_type(v: &retex_engine::nodes::UserValue) -> u8 {
2749 use retex_engine::nodes::UserValue;
2750 match v {
2751 UserValue::Int(_) => b'd',
2752 UserValue::Str(_) => b's',
2753 UserValue::Tokens(_) => b't',
2754 UserValue::Nodes(_) => b'n',
2755 UserValue::Lua(_) => b'l',
2756 UserValue::Attr => b'a',
2757 }
2758}
2759
2760fn set_whatsit_field(lua: &Lua, p: NodeRef, key: &str, v: Value) -> mlua::Result<()> {
2762 use retex_engine::nodes::{LateData, Whatsit};
2763 let s: Option<Vec<u8>> = match &v {
2764 Value::String(s) => Some(s.as_bytes().to_vec()),
2765 Value::Integer(n) => Some(n.to_string().into_bytes()),
2766 Value::Number(n) => Some(crate::texlib::lua_number_string(*n).into_bytes()),
2767 _ => None,
2768 };
2769 let node = to_ref(&v);
2770 let i = int(&v);
2771 let (is_user, utype) = with_engine(lua, |e| match &e.nodes.get(p).data {
2773 NodeData::Whatsit(w) => match &**w {
2774 Whatsit::UserDefined { value, .. } => (true, user_type(value)),
2775 _ => (false, 0),
2776 },
2777 _ => (false, 0),
2778 })?;
2779 let lref = if is_user && key == "value" && utype == b'l' {
2780 Some(lua_ref(lua, v.clone())?)
2781 } else {
2782 None
2783 };
2784 let ini = with_engine(lua, |e| e.ini_version)?;
2787 let lua_string = |s: Vec<u8>| -> mlua::Result<LateData> {
2788 if ini {
2789 Ok(LateData::Bytes(s))
2790 } else {
2791 let reference = lua_ref(lua, Value::String(lua.create_string(&s)?))?;
2792 Ok(LateData::Lua { data: s, reference })
2793 }
2794 };
2795 let late = match (&s, key) {
2796 (Some(s), "data") => Some(lua_string(s.clone())?),
2797 (None, "data" | "string") if matches!(v, Value::Function(_)) && late_lua(lua, p)? => {
2800 Some(if ini {
2801 LateData::Bytes(Vec::new())
2802 } else {
2803 LateData::Function {
2804 reference: lua_ref(lua, v.clone())?,
2805 }
2806 })
2807 }
2808 _ => None,
2809 };
2810 with_engine(lua, |e| {
2811 let tokens = |e: &Engine, s: &[u8]| -> retex_engine::token::TokenList {
2812 let _ = e;
2813 let toks: Vec<retex_engine::token::Token> = String::from_utf8_lossy(s)
2814 .chars()
2815 .map(|c| {
2816 if c == ' ' {
2817 retex_engine::token::Token::SPACE
2818 } else {
2819 retex_engine::token::Token::other(c as u32)
2820 }
2821 })
2822 .collect();
2823 std::rc::Rc::from(toks)
2824 };
2825 let toks = s.as_ref().map(|s| tokens(e, s));
2826 let old_ref = e.nodes.lua_ref_of(p);
2829 set_whatsit_data(e, p, key, &s, &late, &toks, node, i, lref);
2830 if let Some(r) = old_ref
2831 && e.nodes.lua_ref_of(p) != Some(r)
2832 {
2833 e.nodes.lua_refs.release(r);
2834 }
2835 })
2836}
2837
2838fn late_lua(lua: &Lua, p: NodeRef) -> mlua::Result<bool> {
2840 with_engine(lua, |e| match &e.nodes.get(p).data {
2841 NodeData::Whatsit(w) => matches!(&**w, retex_engine::nodes::Whatsit::LateLua { .. }),
2842 _ => false,
2843 })
2844}
2845
2846#[allow(clippy::too_many_arguments)]
2848fn set_whatsit_data(
2849 e: &mut Engine,
2850 p: NodeRef,
2851 key: &str,
2852 s: &Option<Vec<u8>>,
2853 late: &Option<retex_engine::nodes::LateData>,
2854 toks: &Option<retex_engine::token::TokenList>,
2855 node: Option<NodeRef>,
2856 i: i64,
2857 lref: Option<i32>,
2858) {
2859 use retex_engine::nodes::{LateData, UserValue, Whatsit};
2860 {
2861 let NodeData::Whatsit(w) = &mut e.nodes.get_mut(p).data else {
2862 return;
2863 };
2864 match (&mut **w, key) {
2865 (Whatsit::Special { data }, "data") => {
2866 if let Some(s) = &s {
2867 *data = s.clone();
2868 }
2869 }
2870 (Whatsit::LateLua { data, .. }, "data" | "string") => {
2871 if let Some(l) = late.clone().or_else(|| s.clone().map(LateData::Bytes)) {
2872 *data = l;
2873 }
2874 }
2875 (Whatsit::LateLua { name, .. }, "name") => {
2876 if let Some(s) = &s {
2877 *name = s.clone();
2878 }
2879 }
2880 (Whatsit::PdfLiteral { mode, .. }, "mode") => *mode = i as u8,
2881 (Whatsit::PdfLiteral { data, .. }, "data") => {
2882 if let Some(l) = late.clone() {
2883 *data = l;
2884 }
2885 }
2886 (Whatsit::PdfColorstack { stack, .. }, "stack") => *stack = i as i32,
2887 (Whatsit::PdfColorstack { cmd, .. }, "command") => *cmd = i as u8,
2888 (Whatsit::PdfColorstack { data, .. }, "data")
2889 | (Whatsit::PdfSetmatrix { data }, "data") => {
2890 if let Some(s) = &s {
2891 *data = s.clone();
2892 }
2893 }
2894 (Whatsit::PdfRefobj { objnum }, "objnum")
2895 | (Whatsit::PdfStartLink { objnum, .. }, "objnum")
2896 | (Whatsit::PdfAnnot { objnum, .. }, "objnum") => *objnum = i as i32,
2897 (Whatsit::PdfStartLink { attr: data, .. }, "link_attr")
2898 | (Whatsit::PdfAnnot { data, .. }, "data") => {
2899 if let Some(s) = &s {
2900 *data = s.clone();
2901 }
2902 }
2903 (
2904 Whatsit::PdfStartLink { dims, .. } | Whatsit::PdfAnnot { dims, .. },
2905 "width" | "height" | "depth",
2906 ) => dims[dim_index(key)] = i as i32,
2907 (Whatsit::PdfLinkState { state }, "value" | "objnum") => *state = i as i32,
2908 (Whatsit::UserDefined { user_id, .. }, "user_id") => *user_id = i as i32,
2909 (Whatsit::UserDefined { value, .. }, "type") => {
2910 *value = match i as u8 {
2911 b's' => UserValue::Str(Vec::new()),
2912 b't' => UserValue::Tokens(retex_engine::token::empty_list()),
2913 b'n' => UserValue::Nodes(NodeRef::NULL),
2914 b'l' => UserValue::Lua(0),
2915 b'a' => UserValue::Attr,
2916 _ => UserValue::Int(0),
2917 }
2918 }
2919 (Whatsit::UserDefined { value, .. }, "value") => {
2920 *value = match user_type(value) {
2921 b's' => UserValue::Str(s.clone().unwrap_or_default()),
2922 b't' => UserValue::Tokens(
2923 toks.clone().unwrap_or_else(retex_engine::token::empty_list),
2924 ),
2925 b'n' => UserValue::Nodes(node.unwrap_or(NodeRef::NULL)),
2926 b'l' => UserValue::Lua(lref.unwrap_or(0)),
2927 b'a' => UserValue::Attr,
2928 _ => UserValue::Int(i as i32),
2929 }
2930 }
2931 _ => {}
2932 }
2933 }
2934}
2935
2936fn install_list_ops(lua: &Lua, t: &Table, direct: bool) -> mlua::Result<()> {
2939 use retex_engine::build::PackSpec;
2940 for (name, horizontal) in [("hpack", true), ("vpack", false)] {
2941 set_fn(lua, t, name, move |lua, args: MultiValue| {
2942 let v: Vec<Value> = args.into_iter().collect();
2943 let head = v.first().and_then(to_ref).unwrap_or(NodeRef::NULL);
2944 let w = v.get(1).map(int).unwrap_or(0) as i32;
2945 let exactly = matches!(v.get(2), Some(Value::String(s)) if &*s.to_str()? == "exactly");
2946 let spec = if exactly {
2947 PackSpec::Exactly(w)
2948 } else {
2949 PackSpec::Additional(w)
2950 };
2951 let dir = match v.get(3) {
2952 Some(Value::String(s)) => match &*s.to_str()? {
2953 "TLT" => 0,
2954 "TRT" => 1,
2955 "LTL" => 2,
2956 "RTT" => 3,
2957 _ => -1,
2958 },
2959 Some(Value::Integer(n)) => *n as i8,
2960 _ => -1,
2961 };
2962 let (b, badness) = with_engine(lua, |e| {
2963 e.pack_dir = dir;
2964 let b = if horizontal {
2965 e.hpack(head, spec)
2966 } else {
2967 e.vpack(head, spec)
2968 };
2969 e.pack_dir = -1;
2970 (b, e.last_badness)
2971 })?;
2972 Ok((wrap(lua, b, direct)?, Value::Integer(badness.into())))
2973 })?;
2974 }
2975 set_fn(lua, t, "write", |lua, v: Value| {
2977 if let Some(p) = to_ref(&v) {
2978 with_engine(lua, |e| {
2979 let tail = tail(e, p);
2980 e.tail_append(p);
2981 e.cur_list_mut().tail = tail;
2982 })?;
2983 }
2984 Ok(())
2985 })?;
2986 set_fn(lua, t, "last_node", move |lua, ()| {
2988 let p = with_engine(lua, |e| {
2989 let l = e.cur_list();
2990 let (head, tail) = (l.head, l.tail);
2991 if tail == head {
2992 return NodeRef::NULL;
2993 }
2994 let mut q = head;
2995 while e.nodes.next(q) != tail {
2996 q = e.nodes.next(q);
2997 }
2998 e.nodes.get_mut(q).next = NodeRef::NULL;
2999 let n = e.nodes.get_mut(tail);
3000 n.prev = NodeRef::NULL;
3001 e.cur_list_mut().tail = q;
3002 tail
3003 })?;
3004 wrap(lua, p, direct)
3005 })?;
3006 set_fn(lua, t, "end_of_math", move |lua, v: Value| {
3008 let Some(mut p) = to_ref(&v) else {
3009 return Ok(Value::Nil);
3010 };
3011 let found = with_engine(lua, |e| {
3012 if e.nodes.id(p) == id::MATH && e.nodes.subtype(p) == 0 {
3013 p = e.nodes.next(p);
3014 }
3015 while !p.is_null() {
3016 if e.nodes.id(p) == id::MATH && e.nodes.subtype(p) == 1 {
3017 return p;
3018 }
3019 p = e.nodes.next(p);
3020 }
3021 NodeRef::NULL
3022 })?;
3023 wrap(lua, found, direct)
3024 })?;
3025 for (name, protect) in [("protect_glyphs", true), ("unprotect_glyphs", false)] {
3026 set_fn(lua, t, name, move |lua, (h, stop): (Value, Value)| {
3027 let mut p = to_ref(&h).unwrap_or(NodeRef::NULL);
3028 let stop = to_ref(&stop);
3029 with_engine(lua, |e| {
3030 while !p.is_null() {
3031 if e.nodes.id(p) == id::GLYPH {
3032 let n = e.nodes.get_mut(p);
3033 let s = n.subtype;
3034 n.subtype = match (protect, s) {
3035 (true, 1) => 256,
3036 (true, s) if s <= 256 => s + 256,
3037 (false, s) if s > 256 => s - 256,
3038 (_, s) => s,
3039 };
3040 }
3041 if Some(p) == stop {
3042 break;
3043 }
3044 p = e.nodes.next(p);
3045 }
3046 })
3047 })?;
3048 }
3049 Ok(())
3050}