1use std::{borrow::Cow, collections::VecDeque, fmt, rc::Rc};
2
3use libxml::tree::Node;
5
6use crate::{
7 BoxOps, Digested, DigestedData, TexMode,
8 common::{
9 arena::{self, SymHashMap as HashMap},
10 dimension::Dimension,
11 error::{emit_warn, *},
12 font::Font,
13 locator::Locator,
14 object::Object,
15 store::Stored,
16 },
17 definition::{Definition, Reversion, expandable::Expandable},
18 document::Document,
19 list::List,
20 state::{get_dual_branch, lookup_font},
21 token::{Catcode, Token},
22 tokens::Tokens,
23};
24
25#[derive(Clone)]
27pub struct Whatsit {
28 pub args: Vec<Option<Digested>>,
30 pub properties: HashMap<Stored>,
32 pub definition: Rc<dyn Definition>,
34 pub reversion: Option<Box<Tokens>>,
45 pub dual_reversion: Option<Box<HashMap<Tokens>>>,
48 pub locator: Option<Locator>,
51}
52
53impl Default for Whatsit {
54 fn default() -> Self {
55 Whatsit {
56 args: Vec::new(),
57 properties: HashMap::default(),
58 definition: Rc::new(Expandable::default()),
59 reversion: None,
60 dual_reversion: None,
61 locator: None,
62 }
63 }
64}
65impl PartialEq for Whatsit {
66 fn eq(&self, other: &Whatsit) -> bool {
67 *self.definition == *other.definition
69 && self.args == other.args
70 && if let Some(Stored::Digested(body1)) = self.properties.get("body") {
71 if let Some(Stored::Digested(body2)) = other.properties.get("body") {
72 *body1 == *body2
73 } else {
74 false
75 }
76 } else {
77 !other.properties.contains_key("body")
78 }
79 }
80}
81
82impl Whatsit {
83 pub fn is_math(&self) -> bool {
85 #[allow(clippy::manual_unwrap_or_default)]
86 match self.properties.get("isMath") {
87 Some(&Stored::Bool(v)) => v,
88 _ => false,
89 }
90 }
91
92 pub fn is_empty(&self) -> Result<bool> {
94 Ok(
95 self.get_property_bool("isEmpty")
99 || self.get_property_bool("isSpace")
100 || (self.get_definition().get_cs_name() == "Begin"
101 && match self.get_body()? {
102 Some(b) => b
103 .unlist_ref()
104 .iter()
105 .all(|inner| inner.is_empty().unwrap_or(false)),
106 None => true,
107 }),
108 )
109 }
110 pub fn set_properties(&mut self, props: HashMap<Stored>) {
112 for (key, value) in props {
113 self.properties.insert_sym(key, value);
114 }
115 }
116 pub fn get_definition(&self) -> Rc<dyn Definition> { Rc::clone(&self.definition) }
118 pub fn get_arg(&self, n: usize) -> Option<&Digested> {
122 if n == 0 {
123 emit_warn(
124 "internal",
125 "get_arg",
126 "get_arg(0) called — Perl convention uses 1-based indexing",
127 );
128 return None;
129 }
130 match self.args.get(n - 1) {
131 Some(Some(opt)) => Some(opt),
132 _ => None,
133 }
134 }
135 pub fn get_arg_mut(&mut self, n: usize) -> Option<&mut Digested> {
138 assert!(
139 n > 0,
140 "get_arg_mut() uses 1-based indexing (Perl convention). Use get_arg_mut(1) for the first argument."
141 );
142 match self.args.get_mut(n - 1) {
143 Some(Some(opt)) => Some(opt),
144 _ => None,
145 }
146 }
147 pub fn get_args(&self) -> &Vec<Option<Digested>> { &self.args }
149 pub fn set_args(&mut self, args: Vec<Option<Digested>>) { self.args = args; }
151 pub fn get_trailer(&self) -> Option<Digested> {
153 match self.properties.get("trailer") {
154 Some(Stored::Digested(trailer)) => Some(trailer.clone()),
155 _ => None,
156 }
157 }
158 pub fn set_body(&mut self, mut body: Vec<Digested>) {
163 let trailer_opt = body.pop();
164 let mode_opt: Option<String> = self.get_property("mode").and_then(|p| match &*p {
166 Stored::String(s) => Some(arena::to_string(*s)),
167 _ => None,
168 });
169 let mut list = List::new(body);
170 if let Some(ref mode_str) = mode_opt {
172 list.set_property("mode", Stored::String(arena::pin(mode_str)));
173 if mode_str.contains("math") {
174 list.mode = Some(TexMode::Math);
175 }
176 } else if self.is_math() {
177 list.mode = Some(TexMode::Math);
178 }
179 self.properties.insert("body", Digested::from(list).into());
180 if let Some(digested) = trailer_opt {
181 self.properties.insert("trailer", digested.clone().into());
182 if let (Some(from), Some(to)) = (self.locator, digested.get_locator())
195 && let Some(range) = Locator::new_range(from, to)
196 {
197 self.locator = Some(range);
198 }
199 match digested.data() {
202 DigestedData::Whatsit(trailer) => {
203 let trailer_val = trailer.borrow();
204 let props = trailer_val.get_properties();
205 for (prop, value) in props {
206 self
207 .properties
208 .entry_sym(*prop)
209 .or_insert_with(|| value.clone());
210 }
211 },
212 DigestedData::TBox(tbox) => {
213 let tbox_val = tbox.borrow();
214 let props = tbox_val.get_properties();
215 for (prop, value) in props {
216 self
217 .properties
218 .entry_sym(*prop)
219 .or_insert_with(|| value.clone());
220 }
221 },
222 DigestedData::List(list) => {
223 let list_val = list.borrow();
224 let props = list_val.get_properties();
225 for (prop, value) in props {
226 self
227 .properties
228 .entry_sym(*prop)
229 .or_insert_with(|| value.clone());
230 }
231 },
232 _ => {},
233 }
234 }
235 }
236
237 fn substitute_parameters(&self, spec: Tokens) -> Result<Vec<Token>> {
241 let mut in_toks = VecDeque::from(spec.unlist());
245 let args = self.get_args();
246 let props = &self.properties;
247 let mut result = Vec::with_capacity(in_toks.len());
251 while let Some(token) = in_toks.pop_front() {
252 if token.get_catcode() != Catcode::ARG {
253 result.push(token);
255 } else {
256 let arg_opt = token.with_str(|s| {
257 let n = s.parse::<usize>().unwrap() - 1;
258 if n < args.len() {
259 args[n].clone()
260 } else if n < 10 {
261 None
267 } else {
268 match props.get(s) {
269 Some(Stored::Digested(v)) => Some((*v).clone()),
270 Some(other) => {
271 panic!("unexpected prop in substitute_parameters, needed Digested, got: {other:?}")
272 },
273 None => None,
274 }
275 }
276 });
277 if let Some(arg) = arg_opt {
278 result.extend(arg.revert()?.unlist());
279 }
280 }
281 }
282 Ok(result)
283 }
284}
285
286impl fmt::Debug for Whatsit {
287 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288 write!(f, "Whatsit[")?;
289 let mut pieces = Vec::new();
290 pieces.push(
291 self
292 .get_definition()
293 .get_cs()
294 .with_cs_name(ToString::to_string),
295 );
296 for arg_opt in self.get_args() {
297 if let Some(arg) = arg_opt {
298 pieces.push(arg.stringify());
299 } else {
300 pieces.push(String::new());
301 }
302 }
303 if self.properties.contains_key("body") {
304 pieces.push(self.properties.get("body").unwrap().to_string());
305 if let Some(trailer) = self.properties.get("trailer") {
306 pieces.push(trailer.to_string());
307 }
308 }
309 write!(f, "{}]", pieces.join(","))
310 }
311}
312
313impl fmt::Display for Whatsit {
314 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.revert().unwrap()) }
315}
316
317impl Object for Whatsit {
318 fn get_locator(&self) -> Option<Locator> { self.locator }
319
320 fn stringify(&self) -> String { format!("{self:?}") }
321
322 fn revert(&self) -> Result<Tokens> {
323 let saved_opt = if let Some(this_branch) = get_dual_branch() {
326 if let Some(ref dual_reversion) = self.dual_reversion {
327 dual_reversion.get(this_branch).cloned()
328 } else {
329 self.reversion.as_deref().cloned()
330 }
331 } else {
332 self.reversion.as_deref().cloned()
333 };
334 if let Some(saved) = saved_opt {
335 return Ok(saved);
336 }
337
338 let mut tokens = Vec::new();
339 let defn = &self.definition;
340 if defn.get_reversion_spec().is_none()
341 && let Some(Stored::Digested(digested)) = self.properties.get("alignment")
342 && let DigestedData::Alignment(alignment) = digested.data()
343 {
344 return alignment.borrow().revert();
345 }
346 let spec_opt = if let Some(rev) = self.properties.get("reversion") {
350 match rev {
351 Stored::Tokens(tks) => Some(Cow::Owned(Reversion::Tokens(tks.clone()))),
352 Stored::Reversion(rev) => Some(Cow::Borrowed(rev)),
353 other => panic!("TODO: Unexpected reversion directive {other:?}"),
354 }
355 } else {
356 defn.get_reversion_spec().map(Cow::Owned)
357 };
358 let mut is_closure = false;
359 match spec_opt.as_deref() {
360 Some(Reversion::Closure(spec)) => {
361 is_closure = true;
362 let spec_tokens = spec(self, self.get_args()).unwrap();
363 tokens = self.substitute_parameters(spec_tokens)?;
364 },
365 Some(Reversion::Tokens(spec)) => {
366 if !spec.is_empty() {
367 tokens = self.substitute_parameters(spec.clone())?;
368 }
369 },
370 None => {
371 if let Some(alias) = defn.get_alias() {
372 if !alias.is_empty() {
373 tokens.push(Token::from(alias.as_str()));
377 }
378 } else {
379 tokens.push(defn.get_cs().into_owned());
380 }
381 if let Some(parameters) = defn.get_parameters() {
382 tokens.extend(parameters.revert_digested_arguments(self.get_args())?)
387 }
388 },
389 };
390
391 if !is_closure && let Some(body) = self.get_body()? {
392 tokens.extend(body.revert()?.unlist());
393 if let Some(trailer) = self.get_trailer() {
394 tokens.extend(trailer.revert()?.unlist());
395 }
396 }
397
398 Ok(Tokens::new(tokens))
433 }
434}
435
436impl BoxOps for Whatsit {
437 fn get_properties(&self) -> &HashMap<Stored> { &self.properties }
438 fn with_properties<R, FnR>(&self, caller: FnR) -> R
439 where FnR: FnOnce(&HashMap<Stored>) -> R {
440 caller(&self.properties)
441 }
442 fn get_properties_mut(&mut self) -> &mut HashMap<Stored> { &mut self.properties }
443 fn get_property(&self, key: &str) -> Option<Cow<'_, Stored>> {
444 self.properties.get(key).map(Cow::Borrowed)
445 }
446 fn get_property_mut(&mut self, key: &str) -> Option<&mut Stored> { self.properties.get_mut(key) }
447 fn get_string(&self) -> Result<Cow<'_, str>> { Ok(Cow::Owned(self.revert()?.to_string())) }
448
449 fn be_absorbed(&self, document: &mut Document) -> Result<Vec<Node>> {
450 self.definition.do_absorption(document, self)
459 }
461 fn get_body(&self) -> Result<Option<Digested>> {
462 Ok(match self.properties.get("body") {
463 Some(Stored::Digested(body)) => Some(body.clone()),
464 _ => None,
465 })
466 }
467
468 fn get_font(&self) -> Result<Option<Rc<Font>>> {
469 match self.properties.get("font") {
470 Some(Stored::Font(font)) => Ok(Some(Rc::clone(font))),
471 Some(Stored::FontDirective(fd)) => fd.get_font(Some(self)).map(Some),
472 _ => Ok(None),
473 }
474 }
475
476 fn set_font(&mut self, font: Rc<Font>) { self.properties.insert("font", Stored::Font(font)); }
477
478 fn compute_size(
479 &self,
480 mut options: HashMap<Stored>,
481 ) -> Result<(Dimension, Dimension, Dimension)> {
482 let defn = self.get_definition();
483 match defn.get_sizer() {
484 Some(sizer) => sizer(self),
485 _ => {
486 if self.has_property("cached_width") || self.has_property("cached_height") {
487 let w = match self.get_property("cached_width").as_deref() {
490 Some(Stored::Dimension(d)) => *d,
491 _ => Dimension::default(),
492 };
493 let h = match self.get_property("cached_height").as_deref() {
494 Some(Stored::Dimension(d)) => *d,
495 _ => Dimension::default(),
496 };
497 let d = match self.get_property("cached_depth").as_deref() {
498 Some(Stored::Dimension(d)) => *d,
499 _ => Dimension::default(),
500 };
501 Ok((w, h, d))
502 } else {
503 let mut boxes = Vec::new();
508 if let Some(body_stored) = self.get_property("body")
509 && let Stored::Digested(ref body) = *body_stored
510 {
511 for key in &["mode", "vattach", "width"] {
513 if options.get(key).is_none()
514 && let Some(prop) = body.get_property(key)
515 {
516 options.insert(key, (*prop).clone());
517 }
518 }
519 let unlist_boxes = body.unlist();
520 boxes.extend(unlist_boxes);
521 }
522 if boxes.is_empty() {
523 for arg in self.args.iter().flatten() {
525 boxes.extend(arg.unlist());
526 }
527 }
528 let font = match *self.get_property("font").unwrap() {
529 Stored::Font(ref sf) => sf.clone(),
530 _ => lookup_font().unwrap(),
531 };
532 font.compute_boxes_size(&boxes, options)
533 }
534 },
535 }
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn whatsit_default_has_empty_args_and_properties() {
545 let w = Whatsit::default();
546 assert_eq!(w.args.len(), 0);
547 assert_eq!(w.properties.len(), 0);
548 assert!(w.reversion.is_none());
549 assert!(w.dual_reversion.is_none());
550 }
551
552 #[test]
553 fn is_math_false_by_default() {
554 let w = Whatsit::default();
555 assert!(!w.is_math());
556 }
557
558 #[test]
559 fn is_math_reads_bool_property() {
560 let mut w = Whatsit::default();
561 w.properties.insert("isMath", Stored::Bool(true));
562 assert!(w.is_math());
563 w.properties.insert("isMath", Stored::Bool(false));
564 assert!(!w.is_math());
565 }
566
567 #[test]
568 fn is_math_non_bool_is_false() {
569 let mut w = Whatsit::default();
571 w.properties.insert("isMath", Stored::Int(1));
572 assert!(!w.is_math());
573 }
574
575 #[test]
576 fn get_arg_zero_returns_none_and_warns() {
577 let w = Whatsit::default();
578 assert!(w.get_arg(0).is_none());
579 }
580
581 #[test]
582 fn get_arg_out_of_range_returns_none() {
583 let w = Whatsit::default();
584 assert!(w.get_arg(1).is_none(), "empty args vec");
585 assert!(w.get_arg(100).is_none());
586 }
587
588 #[test]
589 fn set_args_stores_vec() {
590 let mut w = Whatsit::default();
591 w.set_args(vec![None, None, None]);
592 assert_eq!(w.args.len(), 3);
593 }
594
595 #[test]
596 fn set_properties_merges_into_existing() {
597 let mut w = Whatsit::default();
598 let mut extra = HashMap::default();
599 extra.insert("foo", Stored::Bool(true));
600 extra.insert("bar", Stored::Int(42));
601 w.set_properties(extra);
602 assert_eq!(w.properties.len(), 2);
603 }
604
605 #[test]
606 fn whatsit_default_equality() {
607 let a = Whatsit::default();
608 let b = Whatsit::default();
609 assert_eq!(a, b);
610 }
611
612 #[test]
613 fn get_trailer_none_by_default() {
614 let w = Whatsit::default();
615 assert!(w.get_trailer().is_none());
616 }
617}