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};
14
15use crate::with_engine;
16
17struct TypeInfo {
19 names: HashMap<u8, String>,
20 ids: HashMap<String, u8>,
21 fields: HashMap<u8, Vec<(i32, String)>>,
22 subtypes: HashMap<u8, Vec<(i32, String)>>,
23 whatsits: Vec<(u16, String)>,
24 whatsit_fields: HashMap<u16, Vec<(i32, String)>>,
25}
26
27fn info() -> &'static TypeInfo {
28 static T: OnceLock<TypeInfo> = OnceLock::new();
29 T.get_or_init(|| {
30 let data = retex_engine::nodes::LUATEX_NODE_TYPES;
31 let mut ti = TypeInfo {
32 names: HashMap::new(),
33 ids: HashMap::new(),
34 fields: HashMap::new(),
35 subtypes: HashMap::new(),
36 whatsits: Vec::new(),
37 whatsit_fields: HashMap::new(),
38 };
39 let pairs = |s: &str| -> Vec<(i32, String)> {
40 s.split_whitespace()
41 .filter_map(|kv| {
42 let (k, v) = kv.split_once('=')?;
43 Some((k.parse().ok()?, v.to_owned()))
44 })
45 .collect()
46 };
47 let mut cur: u8 = 0;
48 for line in data.lines() {
49 let l = line.trim_start();
50 if let Some(rest) = line.strip_prefix("type ") {
51 let mut it = rest.split_whitespace();
52 let i: u8 = it.next().and_then(|x| x.parse().ok()).unwrap_or(0);
53 let name = it.next().unwrap_or("").to_owned();
54 ti.names.insert(i, name.clone());
55 ti.ids.insert(name, i);
56 cur = i;
57 } else if let Some(rest) = l.strip_prefix("fields ") {
58 ti.fields.insert(cur, pairs(rest));
59 } else if let Some(rest) = l.strip_prefix("subtypes ") {
60 ti.subtypes.insert(cur, pairs(rest));
61 } else if let Some(rest) = line.strip_prefix("whatsits ") {
62 ti.whatsits = pairs(rest)
63 .into_iter()
64 .map(|(k, v)| (k as u16, v))
65 .collect();
66 } else if let Some(rest) = l.strip_prefix("whatsit ") {
67 let mut it = rest.splitn(4, ' ');
68 let _name = it.next();
69 let n: u16 = it.next().and_then(|x| x.parse().ok()).unwrap_or(0);
70 let _ = it.next();
71 ti.whatsit_fields.insert(n, pairs(it.next().unwrap_or("")));
72 }
73 }
74 ti
75 })
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub struct LNode(pub u32);
81
82impl UserData for LNode {
83 fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
84 m.add_meta_method(MetaMethod::Index, |lua, this, key: String| {
85 get_field(lua, NodeRef(this.0), &key)
86 });
87 m.add_meta_method(
88 MetaMethod::NewIndex,
89 |lua, this, (key, v): (String, Value)| set_field(lua, NodeRef(this.0), &key, v),
90 );
91 m.add_meta_method(MetaMethod::Eq, |_, this, other: mlua::AnyUserData| {
92 Ok(other
93 .borrow::<LNode>()
94 .map(|o| o.0 == this.0)
95 .unwrap_or(false))
96 });
97 m.add_meta_method(MetaMethod::ToString, |lua, this, ()| {
98 let t = with_engine(lua, |e| e.nodes.id(NodeRef(this.0)))?;
99 let name = info().names.get(&t).cloned().unwrap_or_default();
100 Ok(format!("<node {} < {} > nil : {} 0>", 0, this.0, name))
101 });
102 }
103}
104
105fn to_ref(v: &Value) -> Option<NodeRef> {
106 match v {
107 Value::UserData(u) => u.borrow::<LNode>().ok().map(|n| NodeRef(n.0)),
108 Value::Integer(n) if *n > 0 => Some(NodeRef(*n as u32)),
109 _ => None,
110 }
111}
112
113fn wrap(lua: &Lua, p: NodeRef, direct: bool) -> mlua::Result<Value> {
114 if p.is_null() {
115 return Ok(Value::Nil);
116 }
117 if direct {
118 Ok(Value::Integer(i64::from(p.0)))
119 } else {
120 Ok(Value::UserData(lua.create_userdata(LNode(p.0))?))
121 }
122}
123
124fn new_data(t: u8) -> NodeData {
126 use retex_engine::glue::Glue;
127 match t {
128 id::HLIST | id::VLIST => NodeData::Box(Box::default()),
129 id::RULE => NodeData::Rule {
130 width: retex_engine::nodes::RUNNING,
131 depth: retex_engine::nodes::RUNNING,
132 height: retex_engine::nodes::RUNNING,
133 dir: 0,
134 index: 0,
135 left: 0,
136 right: 0,
137 },
138 id::GLUE => NodeData::Glue {
139 spec: Glue::ZERO,
140 leader: NodeRef::NULL,
141 },
142 id::KERN => NodeData::Kern {
143 kern: 0,
144 expansion_factor: 0,
145 },
146 id::PENALTY => NodeData::Penalty { penalty: 0 },
147 id::MARGIN_KERN => NodeData::MarginKern {
148 width: 0,
149 glyph: NodeRef::NULL,
150 },
151 id::GLYPH => NodeData::Glyph(Default::default()),
152 id::DISC => NodeData::Disc {
153 pre: NodeRef::NULL,
154 post: NodeRef::NULL,
155 replace: NodeRef::NULL,
156 penalty: 0,
157 },
158 id::BOUNDARY => NodeData::Boundary { value: 0 },
159 id::MATH => NodeData::Math {
160 surround: 0,
161 glue: Glue::ZERO,
162 },
163 id::ADJUST => NodeData::Adjust {
164 head: NodeRef::NULL,
165 },
166 id::MARK => NodeData::Mark {
167 class: 0,
168 mark: retex_engine::token::empty_list(),
169 },
170 id::DIR => NodeData::Dir { dir: 0, level: 0 },
171 id::LOCAL_PAR => NodeData::LocalPar {
172 pen_inter: 0,
173 pen_broken: 0,
174 dir: 0,
175 box_left: NodeRef::NULL,
176 box_left_width: 0,
177 box_right: NodeRef::NULL,
178 box_right_width: 0,
179 },
180 id::WHATSIT => NodeData::Whatsit(Box::new(retex_engine::nodes::Whatsit::Other {
181 fields: Vec::new(),
182 data: Vec::new(),
183 })),
184 _ => NodeData::Temp,
185 }
186}
187
188fn int(v: &Value) -> i64 {
189 match v {
190 Value::Integer(n) => *n,
191 Value::Number(n) => n.round() as i64,
192 Value::Boolean(b) => i64::from(*b),
193 _ => 0,
194 }
195}
196
197fn get_field(lua: &Lua, p: NodeRef, key: &str) -> mlua::Result<Value> {
199 get_field_impl(lua, p, key, false)
200}
201
202fn get_field_impl(lua: &Lua, p: NodeRef, key: &str, direct: bool) -> mlua::Result<Value> {
203 enum R {
204 I(i64),
205 N(NodeRef),
206 F(f64),
207 S(Vec<u8>),
208 Nil,
209 }
210 let r = with_engine(lua, |e| -> R {
211 let n = e.nodes.get(p);
212 match key {
213 "id" => return R::I(i64::from(n.id)),
214 "subtype" => return R::I(i64::from(n.subtype)),
215 "next" => return R::N(n.next),
216 "prev" => return R::N(n.prev),
217 "attr" => return R::Nil,
218 _ => {}
219 }
220 match &n.data {
221 NodeData::Box(b) | NodeData::Unset { b, .. } => match key {
222 "width" => R::I(b.width.into()),
223 "height" => R::I(b.height.into()),
224 "depth" => R::I(b.depth.into()),
225 "shift" => R::I(b.shift.into()),
226 "dir" => R::I(b.dir.into()),
227 "glue_order" => R::I(b.glue_order.into()),
228 "glue_sign" => R::I(b.glue_sign.into()),
229 "glue_set" => R::F(b.glue_set),
230 "head" | "list" => R::N(b.head),
231 _ => R::Nil,
232 },
233 NodeData::Glyph(g) => match key {
234 "char" => R::I(g.chr.into()),
235 "font" => R::I(g.font.into()),
236 "lang" => R::I(g.lang.into()),
237 "left" => R::I(g.left.into()),
238 "right" => R::I(g.right.into()),
239 "uchyph" => R::I(i64::from(g.uchyph)),
240 "components" => R::N(g.components),
241 "xoffset" => R::I(g.xoffset.into()),
242 "yoffset" => R::I(g.yoffset.into()),
243 "expansion_factor" => R::I(g.expansion_factor.into()),
244 "data" => R::I(g.data.into()),
245 "width" => R::I(e.font_char_dimen(g.font, g.chr, 0).into()),
246 "height" => R::I(e.font_char_dimen(g.font, g.chr, 1).into()),
247 "depth" => R::I(e.font_char_dimen(g.font, g.chr, 2).into()),
248 _ => R::Nil,
249 },
250 NodeData::Glue { spec, leader } => match key {
251 "width" => R::I(spec.width.into()),
252 "stretch" => R::I(spec.stretch.into()),
253 "shrink" => R::I(spec.shrink.into()),
254 "stretch_order" => R::I(spec.stretch_order.into()),
255 "shrink_order" => R::I(spec.shrink_order.into()),
256 "leader" => R::N(*leader),
257 _ => R::Nil,
258 },
259 NodeData::Kern {
260 kern,
261 expansion_factor,
262 } => match key {
263 "kern" | "width" => R::I((*kern).into()),
264 "expansion_factor" => R::I((*expansion_factor).into()),
265 _ => R::Nil,
266 },
267 NodeData::Penalty { penalty } => match key {
268 "penalty" => R::I((*penalty).into()),
269 _ => R::Nil,
270 },
271 NodeData::MarginKern { width, glyph } => match key {
272 "width" => R::I((*width).into()),
273 "glyph" => R::N(*glyph),
274 _ => R::Nil,
275 },
276 NodeData::Rule {
277 width,
278 depth,
279 height,
280 dir,
281 index,
282 left,
283 right,
284 } => match key {
285 "width" => R::I((*width).into()),
286 "depth" => R::I((*depth).into()),
287 "height" => R::I((*height).into()),
288 "dir" => R::I((*dir).into()),
289 "index" => R::I((*index).into()),
290 "left" => R::I((*left).into()),
291 "right" => R::I((*right).into()),
292 _ => R::Nil,
293 },
294 NodeData::Disc {
295 pre,
296 post,
297 replace,
298 penalty,
299 } => match key {
300 "pre" => R::N(*pre),
301 "post" => R::N(*post),
302 "replace" => R::N(*replace),
303 "penalty" => R::I((*penalty).into()),
304 _ => R::Nil,
305 },
306 NodeData::Boundary { value } => match key {
307 "value" => R::I((*value).into()),
308 _ => R::Nil,
309 },
310 NodeData::Mark { class, mark } => match key {
311 "class" => R::I((*class).into()),
312 "mark" => {
313 let mark = mark.clone();
314 R::S(e.print_to_string(|e| e.show_token_list(&mark, 0, mark.len(), i64::MAX)))
315 }
316 _ => R::Nil,
317 },
318 NodeData::Ins {
319 cost,
320 depth,
321 height,
322 head,
323 ..
324 } => match key {
325 "cost" => R::I((*cost).into()),
326 "depth" => R::I((*depth).into()),
327 "height" => R::I((*height).into()),
328 "head" | "list" => R::N(*head),
329 _ => R::Nil,
330 },
331 NodeData::Adjust { head } => match key {
332 "head" | "list" => R::N(*head),
333 _ => R::Nil,
334 },
335 NodeData::Dir { dir, level } => match key {
336 "dir" => R::I((*dir).into()),
337 "level" => R::I((*level).into()),
338 _ => R::Nil,
339 },
340 NodeData::Math { surround, glue } => match key {
341 "surround" => R::I((*surround).into()),
342 "width" => R::I(glue.width.into()),
343 "stretch" => R::I(glue.stretch.into()),
344 "shrink" => R::I(glue.shrink.into()),
345 "stretch_order" => R::I(glue.stretch_order.into()),
346 "shrink_order" => R::I(glue.shrink_order.into()),
347 _ => R::Nil,
348 },
349 _ => R::Nil,
350 }
351 })?;
352 Ok(match r {
353 R::I(i) => Value::Integer(i),
354 R::N(n) => wrap(lua, n, direct)?,
355 R::F(f) => Value::Number(f),
356 R::S(s) => Value::String(lua.create_string(&s)?),
357 R::Nil => Value::Nil,
358 })
359}
360
361fn set_field(lua: &Lua, p: NodeRef, key: &str, v: Value) -> mlua::Result<()> {
363 let r = to_ref(&v).unwrap_or(NodeRef::NULL);
364 let i = int(&v);
365 let f = match &v {
366 Value::Number(x) => *x,
367 Value::Integer(x) => *x as f64,
368 _ => 0.0,
369 };
370 with_engine(lua, |e| {
371 let n = e.nodes.get_mut(p);
372 match key {
373 "next" => {
374 n.next = r;
375 return;
376 }
377 "prev" => {
378 n.prev = r;
379 return;
380 }
381 "subtype" => {
382 n.subtype = i as u16;
383 return;
384 }
385 _ => {}
386 }
387 match &mut n.data {
388 NodeData::Box(b) | NodeData::Unset { b, .. } => match key {
389 "width" => b.width = i as i32,
390 "height" => b.height = i as i32,
391 "depth" => b.depth = i as i32,
392 "shift" => b.shift = i as i32,
393 "dir" => b.dir = i as i8,
394 "glue_order" => b.glue_order = i as u8,
395 "glue_sign" => b.glue_sign = i as u8,
396 "glue_set" => b.glue_set = f64::from(f as f32),
397 "head" | "list" => b.head = r,
398 _ => {}
399 },
400 NodeData::Glyph(g) => match key {
401 "char" => g.chr = i as u32,
402 "font" => g.font = i as u32,
403 "lang" => g.lang = i as u16,
404 "left" => g.left = i as u8,
405 "right" => g.right = i as u8,
406 "uchyph" => g.uchyph = i != 0,
407 "components" => g.components = r,
408 "xoffset" => g.xoffset = i as i32,
409 "yoffset" => g.yoffset = i as i32,
410 "expansion_factor" => g.expansion_factor = i as i32,
411 "data" => g.data = i as i32,
412 _ => {}
413 },
414 NodeData::Glue { spec, leader } => match key {
415 "width" => spec.width = i as i32,
416 "stretch" => spec.stretch = i as i32,
417 "shrink" => spec.shrink = i as i32,
418 "stretch_order" => spec.stretch_order = i as u8,
419 "shrink_order" => spec.shrink_order = i as u8,
420 "leader" => *leader = r,
421 _ => {}
422 },
423 NodeData::Kern {
424 kern,
425 expansion_factor,
426 } => {
427 if key == "kern" || key == "width" {
428 *kern = i as i32;
429 } else if key == "expansion_factor" {
430 *expansion_factor = i as i32;
431 }
432 }
433 NodeData::MarginKern { width, glyph } => match key {
434 "width" => *width = i as i32,
435 "glyph" => *glyph = r,
436 _ => {}
437 },
438 NodeData::Penalty { penalty } => {
439 if key == "penalty" {
440 *penalty = i as i32;
441 }
442 }
443 NodeData::Rule {
444 width,
445 depth,
446 height,
447 ..
448 } => match key {
449 "width" => *width = i as i32,
450 "depth" => *depth = i as i32,
451 "height" => *height = i as i32,
452 _ => {}
453 },
454 NodeData::Disc {
455 pre,
456 post,
457 replace,
458 penalty,
459 } => match key {
460 "pre" => *pre = r,
461 "post" => *post = r,
462 "replace" => *replace = r,
463 "penalty" => *penalty = i as i32,
464 _ => {}
465 },
466 NodeData::Boundary { value } => {
467 if key == "value" {
468 *value = i as i32;
469 }
470 }
471 NodeData::Mark { class, .. } => {
472 if key == "class" {
473 *class = i as u32;
474 }
475 }
476 _ => {}
477 }
478 })
479}
480
481pub fn install(lua: &Lua) -> mlua::Result<()> {
483 let node = lua.create_table()?;
484 let direct = lua.create_table()?;
485 for (t, is_direct) in [(&node, false), (&direct, true)] {
486 install_common(lua, t, is_direct)?;
487 }
488 direct.set(
489 "todirect",
490 lua.create_function(|_, v: Value| Ok(to_ref(&v).map(|p| i64::from(p.0))))?,
491 )?;
492 direct.set(
493 "tonode",
494 lua.create_function(|lua, v: Value| match to_ref(&v) {
495 Some(p) => wrap(lua, p, false),
496 None => Ok(Value::Nil),
497 })?,
498 )?;
499 node.set("direct", direct)?;
500 lua.globals().set("node", node)?;
501 Ok(())
502}
503
504fn install_common(lua: &Lua, t: &Table, direct: bool) -> mlua::Result<()> {
505 t.set(
506 "types",
507 lua.create_function(|lua, ()| {
508 let out = lua.create_table()?;
509 for (k, v) in &info().names {
510 out.set(*k, v.as_str())?;
511 }
512 Ok(out)
513 })?,
514 )?;
515 t.set(
516 "whatsits",
517 lua.create_function(|lua, ()| {
518 let out = lua.create_table()?;
519 for (k, v) in &info().whatsits {
520 out.set(*k, v.as_str())?;
521 }
522 Ok(out)
523 })?,
524 )?;
525 t.set(
526 "id",
527 lua.create_function(|_, name: String| Ok(info().ids.get(&name).copied()))?,
528 )?;
529 t.set(
530 "subtype",
531 lua.create_function(|_, name: String| {
532 Ok(info()
533 .whatsits
534 .iter()
535 .find(|(_, n)| *n == name)
536 .map(|(k, _)| *k))
537 })?,
538 )?;
539 t.set(
540 "type",
541 lua.create_function(|lua, v: Value| match &v {
542 Value::Integer(n) if !matches!(v, Value::UserData(_)) => {
543 Ok(info().names.get(&(*n as u8)).cloned())
544 }
545 _ => match to_ref(&v) {
546 Some(p) => {
547 let t = with_engine(lua, |e| e.nodes.id(p))?;
548 Ok(info().names.get(&t).cloned())
549 }
550 None => Ok(None),
551 },
552 })?,
553 )?;
554 t.set(
555 "subtypes",
556 lua.create_function(|lua, v: Value| {
557 let idn = match &v {
558 Value::Integer(n) => *n as u8,
559 Value::String(s) => match info().ids.get(&*s.to_str()?) {
560 Some(i) => *i,
561 None => return Ok(Value::Nil),
562 },
563 _ => return Ok(Value::Nil),
564 };
565 let out = lua.create_table()?;
566 if let Some(st) = info().subtypes.get(&idn) {
567 for (k, n) in st {
568 out.set(*k, n.as_str())?;
569 }
570 }
571 Ok(Value::Table(out))
572 })?,
573 )?;
574 t.set(
575 "fields",
576 lua.create_function(|lua, (v, sub): (Value, Option<i64>)| {
577 let idn = match &v {
578 Value::Integer(n) => *n as u8,
579 Value::String(s) => match info().ids.get(&*s.to_str()?) {
580 Some(i) => *i,
581 None => return Ok(Value::Nil),
582 },
583 _ => return Ok(Value::Nil),
584 };
585 let list = if idn == id::WHATSIT {
586 sub.and_then(|s| info().whatsit_fields.get(&(s as u16)))
587 } else {
588 info().fields.get(&idn)
589 };
590 let out = lua.create_table()?;
591 if let Some(fl) = list {
592 for (k, n) in fl {
593 out.set(*k, n.as_str())?;
594 }
595 }
596 Ok(Value::Table(out))
597 })?,
598 )?;
599 t.set(
600 "is_node",
601 lua.create_function(move |_, v: Value| {
602 Ok(match &v {
603 Value::UserData(u) => u.borrow::<LNode>().is_ok(),
604 Value::Integer(n) => direct && *n > 0,
605 _ => false,
606 })
607 })?,
608 )?;
609 t.set(
610 "new",
611 lua.create_function(move |lua, (ty, sub): (Value, Option<Value>)| {
612 let t = match &ty {
613 Value::Integer(n) => *n as u8,
614 Value::String(s) => *info()
615 .ids
616 .get(&*s.to_str()?)
617 .ok_or_else(|| mlua::Error::runtime("unknown node type"))?,
618 _ => return Err(mlua::Error::runtime("node type expected")),
619 };
620 let st = match &sub {
621 Some(Value::Integer(n)) => *n as u16,
622 Some(Value::String(s)) => {
623 let s = s.to_str()?.to_owned();
624 info()
625 .whatsits
626 .iter()
627 .find(|(_, n)| *n == s)
628 .map(|(k, _)| *k)
629 .unwrap_or(0)
630 }
631 _ => 0,
632 };
633 let p = with_engine(lua, |e| e.nodes.alloc(t, st, new_data(t)))?;
634 wrap(lua, p, direct)
635 })?,
636 )?;
637 t.set(
638 "free",
639 lua.create_function(|lua, v: Value| {
640 if let Some(p) = to_ref(&v) {
641 with_engine(lua, |e| e.nodes.free(p))?;
642 }
643 Ok(())
644 })?,
645 )?;
646 t.set(
647 "flush_node",
648 lua.create_function(|lua, v: Value| {
649 if let Some(p) = to_ref(&v) {
650 with_engine(lua, |e| e.nodes.free(p))?;
651 }
652 Ok(())
653 })?,
654 )?;
655 t.set(
656 "flush_list",
657 lua.create_function(|lua, v: Value| {
658 if let Some(p) = to_ref(&v) {
659 with_engine(lua, |e| e.nodes.flush_list(p))?;
660 }
661 Ok(())
662 })?,
663 )?;
664 t.set(
665 "copy",
666 lua.create_function(move |lua, v: Value| match to_ref(&v) {
667 Some(p) => {
668 let q = with_engine(lua, |e| e.nodes.copy_node(p))?;
669 wrap(lua, q, direct)
670 }
671 None => Ok(Value::Nil),
672 })?,
673 )?;
674 t.set(
675 "copy_list",
676 lua.create_function(move |lua, v: Value| match to_ref(&v) {
677 Some(p) => {
678 let q = with_engine(lua, |e| e.nodes.copy_list(p))?;
679 wrap(lua, q, direct)
680 }
681 None => Ok(Value::Nil),
682 })?,
683 )?;
684 t.set(
685 "tail",
686 lua.create_function(move |lua, v: Value| match to_ref(&v) {
687 Some(p) => {
688 let q = with_engine(lua, |e| e.nodes.tail_of(p))?;
689 wrap(lua, q, direct)
690 }
691 None => Ok(Value::Nil),
692 })?,
693 )?;
694 t.set(
695 "slide",
696 lua.create_function(move |lua, v: Value| match to_ref(&v) {
697 Some(p) => {
698 let q = with_engine(lua, |e| slide(e, p))?;
699 wrap(lua, q, direct)
700 }
701 None => Ok(Value::Nil),
702 })?,
703 )?;
704 t.set(
705 "length",
706 lua.create_function(|lua, v: Value| match to_ref(&v) {
707 Some(p) => with_engine(lua, |e| count(e, p, None)),
708 None => Ok(0),
709 })?,
710 )?;
711 t.set(
712 "count",
713 lua.create_function(|lua, (ty, v): (i64, Value)| match to_ref(&v) {
714 Some(p) => with_engine(lua, |e| count(e, p, Some(ty as u8))),
715 None => Ok(0),
716 })?,
717 )?;
718 for (name, by_id) in [("traverse", false), ("traverse_id", true)] {
719 t.set(
720 name,
721 lua.create_function(move |lua, args: MultiValue| {
722 let v: Vec<Value> = args.into_iter().collect();
723 let (filter, start) = if by_id {
724 (
725 Some(int(v.first().unwrap_or(&Value::Nil)) as u8),
726 v.get(1).cloned(),
727 )
728 } else {
729 (None, v.first().cloned())
730 };
731 let start = start.and_then(|s| to_ref(&s)).unwrap_or(NodeRef::NULL);
732 let iter = lua.create_function(move |lua, (_s, cur): (Value, Value)| {
733 let next = match to_ref(&cur) {
734 Some(c) => with_engine(lua, |e| e.nodes.next(c))?,
735 None => start,
736 };
737 let mut p = next;
738 let found = with_engine(lua, |e| {
739 while !p.is_null() {
740 if filter.is_none_or(|f| e.nodes.id(p) == f) {
741 return (p, e.nodes.id(p), e.nodes.get(p).subtype);
742 }
743 p = e.nodes.next(p);
744 }
745 (NodeRef::NULL, 0, 0)
746 })?;
747 if found.0.is_null() {
748 return Ok(MultiValue::new());
749 }
750 Ok(MultiValue::from_iter([
751 wrap(lua, found.0, direct)?,
752 Value::Integer(i64::from(found.1)),
753 Value::Integer(i64::from(found.2)),
754 ]))
755 })?;
756 Ok((iter, Value::Nil, Value::Nil))
757 })?,
758 )?;
759 }
760 t.set(
761 "insert_after",
762 lua.create_function(move |lua, (h, c, n): (Value, Value, Value)| {
763 let (Some(n), head) = (to_ref(&n), to_ref(&h)) else {
764 return Ok(MultiValue::new());
765 };
766 let cur = to_ref(&c);
767 let head = with_engine(lua, |e| match (head, cur) {
768 (None, _) | (_, None) => n,
769 (Some(h), Some(c)) => {
770 let nx = e.nodes.next(c);
771 e.nodes.link(c, n);
772 let tail = e.nodes.tail_of(n);
773 e.nodes.link(tail, nx);
774 h
775 }
776 })?;
777 Ok(MultiValue::from_iter([
778 wrap(lua, head, direct)?,
779 wrap(lua, n, direct)?,
780 ]))
781 })?,
782 )?;
783 t.set(
784 "insert_before",
785 lua.create_function(move |lua, (h, c, n): (Value, Value, Value)| {
786 let Some(n) = to_ref(&n) else {
787 return Ok(MultiValue::new());
788 };
789 let head = to_ref(&h);
790 let cur = to_ref(&c);
791 let head = with_engine(lua, |e| match (head, cur) {
792 (None, _) => n,
793 (Some(h), None) => {
794 let t = e.nodes.tail_of(h);
795 e.nodes.link(t, n);
796 h
797 }
798 (Some(h), Some(c)) => {
799 let pv = e.nodes.get(c).prev;
800 let tail = e.nodes.tail_of(n);
801 e.nodes.link(tail, c);
802 if c == h || pv.is_null() {
803 e.nodes.get_mut(n).prev = NodeRef::NULL;
804 n
805 } else {
806 e.nodes.link(pv, n);
807 h
808 }
809 }
810 })?;
811 Ok(MultiValue::from_iter([
812 wrap(lua, head, direct)?,
813 wrap(lua, n, direct)?,
814 ]))
815 })?,
816 )?;
817 t.set(
818 "remove",
819 lua.create_function(move |lua, (h, c): (Value, Value)| {
820 let (Some(h), Some(c)) = (to_ref(&h), to_ref(&c)) else {
821 return Ok(MultiValue::new());
822 };
823 let (head, next) = with_engine(lua, |e| {
824 let pv = e.nodes.get(c).prev;
825 let nx = e.nodes.next(c);
826 let head = if c == h {
827 nx
828 } else {
829 e.nodes.link(pv, nx);
830 h
831 };
832 if !nx.is_null() && c == h {
833 e.nodes.get_mut(nx).prev = NodeRef::NULL;
834 }
835 let n = e.nodes.get_mut(c);
836 n.next = NodeRef::NULL;
837 n.prev = NodeRef::NULL;
838 (head, nx)
839 })?;
840 Ok(MultiValue::from_iter([
841 wrap(lua, head, direct)?,
842 wrap(lua, next, direct)?,
843 ]))
844 })?,
845 )?;
846 t.set(
847 "has_field",
848 lua.create_function(|lua, (v, f): (Value, String)| match to_ref(&v) {
849 Some(p) => {
850 let t = with_engine(lua, |e| e.nodes.id(p))?;
851 Ok(info()
852 .fields
853 .get(&t)
854 .is_some_and(|fl| fl.iter().any(|(_, n)| *n == f)))
855 }
856 None => Ok(false),
857 })?,
858 )?;
859 t.set(
860 "getfield",
861 lua.create_function(move |lua, (v, f): (Value, String)| match to_ref(&v) {
862 Some(p) => get_field_impl(lua, p, &f, direct),
863 None => Ok(Value::Nil),
864 })?,
865 )?;
866 t.set(
867 "setfield",
868 lua.create_function(|lua, (v, f, x): (Value, String, Value)| match to_ref(&v) {
869 Some(p) => set_field(lua, p, &f, x),
870 None => Ok(()),
871 })?,
872 )?;
873 for name in [
875 "id", "subtype", "next", "prev", "char", "font", "width", "height", "depth", "list",
876 "penalty", "kern", "leader",
877 ] {
878 let key = if name == "list" { "head" } else { name };
879 if name != "id" && name != "subtype" {
880 t.set(
881 format!("get{name}"),
882 lua.create_function(move |lua, v: Value| match to_ref(&v) {
883 Some(p) => get_field_impl(lua, p, key, direct),
884 None => Ok(Value::Nil),
885 })?,
886 )?;
887 } else {
888 t.set(
889 format!("get{name}"),
890 lua.create_function(move |lua, v: Value| match to_ref(&v) {
891 Some(p) => get_field_impl(lua, p, key, direct),
892 None => Ok(Value::Nil),
893 })?,
894 )?;
895 }
896 t.set(
897 format!("set{name}"),
898 lua.create_function(move |lua, (v, x): (Value, Value)| match to_ref(&v) {
899 Some(p) => set_field(lua, p, key, x),
900 None => Ok(()),
901 })?,
902 )?;
903 }
904 for name in [
907 "mlist_to_hlist",
908 "hpack",
909 "vpack",
910 "rangedimensions",
911 "kerning",
912 "ligaturing",
913 "hyphenating",
914 "protect_glyphs",
915 "unprotect_glyphs",
916 "write",
917 "last_node",
918 "end_of_math",
919 "flatten_discretionaries",
920 ] {
921 t.set(
922 name,
923 lua.create_function(move |_, args: MultiValue| Ok(args))?,
924 )?;
925 }
926 t.set(
928 "family_font",
929 lua.create_function(|lua, fam: i64| {
930 if !(0..retex_engine::eqtb::NUM_FAMILIES as i64).contains(&fam) {
931 return Ok(0);
932 }
933 with_engine(lua, |e| i64::from(e.fam_fnt(fam as u32, 0)))
934 })?,
935 )?;
936 t.set(
937 "dimensions",
938 lua.create_function(|lua, args: MultiValue| {
939 let refs: Vec<NodeRef> = args.iter().filter_map(to_ref).collect();
942 let Some(&first) = refs.first() else {
943 return Ok((0, 0, 0));
944 };
945 let stop = refs.get(1).copied().unwrap_or(NodeRef::NULL);
946 with_engine(lua, |e| e.natural_dimensions(first, stop))
947 })?,
948 )?;
949 t.set(
950 "has_attribute",
951 lua.create_function(|lua, (v, n, val): (Value, i64, Option<i64>)| {
952 let Some(p) = to_ref(&v) else {
953 return Ok(None);
954 };
955 let a = with_engine(lua, |e| {
956 e.nodes
957 .get(p)
958 .attr
959 .as_ref()
960 .and_then(|l| l.iter().find(|(k, _)| i64::from(*k) == n).map(|x| x.1))
961 })?;
962 Ok(match (a, val) {
963 (Some(x), Some(w)) if i64::from(x) != w => None,
964 (a, _) => a.map(i64::from),
965 })
966 })?,
967 )?;
968 t.set(
969 "set_attribute",
970 lua.create_function(|lua, (v, n, val): (Value, i64, i64)| {
971 if let Some(p) = to_ref(&v) {
972 with_engine(lua, |e| set_attr(e, p, n as u32, Some(val as i32)))?;
973 }
974 Ok(())
975 })?,
976 )?;
977 t.set(
978 "unset_attribute",
979 lua.create_function(|lua, (v, n): (Value, i64)| {
980 if let Some(p) = to_ref(&v) {
981 with_engine(lua, |e| set_attr(e, p, n as u32, None))?;
982 }
983 Ok(())
984 })?,
985 )?;
986 Ok(())
987}
988
989fn set_attr(e: &mut Engine, p: NodeRef, n: u32, v: Option<i32>) {
990 let node = e.nodes.get_mut(p);
991 let mut list: Vec<(u32, i32)> = node.attr.as_ref().map(|l| l.to_vec()).unwrap_or_default();
992 list.retain(|(k, _)| *k != n);
993 if let Some(v) = v {
994 list.push((n, v));
995 list.sort();
996 }
997 node.attr = if list.is_empty() {
998 None
999 } else {
1000 Some(std::rc::Rc::from(list))
1001 };
1002}
1003
1004fn slide(e: &mut Engine, mut p: NodeRef) -> NodeRef {
1005 loop {
1006 let n = e.nodes.next(p);
1007 if n.is_null() {
1008 return p;
1009 }
1010 e.nodes.get_mut(n).prev = p;
1011 p = n;
1012 }
1013}
1014
1015fn count(e: &Engine, mut p: NodeRef, ty: Option<u8>) -> i64 {
1016 let mut n = 0;
1017 while !p.is_null() {
1018 if ty.is_none_or(|t| e.nodes.id(p) == t) {
1019 n += 1;
1020 }
1021 p = e.nodes.next(p);
1022 }
1023 n
1024}