Skip to main content

latexml_core/binding/def/
traits.rs

1//! A variety of traits helpful for auto-casting between the different components of the
2//! conversion toolchain
3use std::collections::VecDeque;
4
5use crate::{
6  common::{
7    arena, arena::SymHashMap as HashMap, color::Color, error::*, glue::Glue,
8    mudimension::MuDimension, muglue::MuGlue, number::Number, store::Stored,
9  },
10  definition::{Reversion, SizingClosure, argument::ArgWrap, register::*},
11  keyvals::KeyVals,
12  list::List,
13  state::{Scope, lookup_font},
14  token::*,
15  tokens::TeXString,
16  whatsit::Whatsit,
17  *,
18};
19
20/// Build sizing options from a Whatsit's properties, matching Perl's computeSizeStore behavior.
21/// Perl (Box.pm L267-271) adds width, height, depth, vattach, layout from properties to options
22/// before calling computeSize, which passes them through to computeBoxesSize.
23fn sizer_options_from_whatsit(w: &Whatsit) -> HashMap<Stored> {
24  let mut options: HashMap<Stored> = HashMap::default();
25  for key in ["width", "height", "depth", "vattach", "layout", "mode"] {
26    if let Some(v) = w.get_property(key) {
27      options.insert(key, v.into_owned());
28    }
29  }
30  options
31}
32
33/// Helper for sizer string parsing: references either a numeric arg or a named property
34enum SizerRef {
35  Arg(usize),
36  Prop(String),
37}
38
39/// A trait for auto-wrapping a generic type `T` into `Option<Y>`,
40/// where Y can be inferred from context.
41/// (useful in macro helpers, such as `NewDefaultV!`)
42pub trait IntoOption<T>: Sized {
43  /// Performs the conversion.
44  fn into_option(self) -> T;
45}
46
47impl IntoOption<Option<String>> for &str {
48  fn into_option(self) -> Option<String> { Some(self.to_string()) }
49}
50
51impl<T> IntoOption<Option<T>> for Option<T> {
52  fn into_option(self) -> Option<T> { self }
53}
54
55impl IntoOption<bool> for bool {
56  fn into_option(self) -> bool { self }
57}
58impl IntoOption<Option<bool>> for bool {
59  fn into_option(self) -> Option<bool> { Some(self) }
60}
61
62impl<T> IntoOption<Option<Vec<T>>> for Vec<T> {
63  fn into_option(self) -> Option<Vec<T>> { Some(self) }
64}
65
66impl<T> IntoOption<Option<VecDeque<T>>> for VecDeque<T> {
67  fn into_option(self) -> Option<VecDeque<T>> { Some(self) }
68}
69impl IntoOption<Option<usize>> for usize {
70  fn into_option(self) -> Option<usize> { Some(self) }
71}
72
73impl IntoOption<Option<Reversion>> for Tokens {
74  fn into_option(self) -> Option<Reversion> { Some(Reversion::Tokens(self)) }
75}
76impl IntoOption<Option<Reversion>> for &str {
77  fn into_option(self) -> Option<Reversion> {
78    if self.is_empty() {
79      Some(Reversion::Tokens(Tokens!()))
80    } else {
81      Some(Reversion::Tokens(
82        // `assembled`, not the literal `From<&'static str>`: this impl is on
83        // plain `&str`, so the lifetime is unknown here. See the note on
84        // `From<&str> for Reversion` in `definition.rs` — these `&str`
85        // conversion helpers are the residual way a `String` can still reach
86        // the tokenizer without saying so.
87        mouth::tokenize_internal(TeXString::assembled(self.to_string()))
88          .pack_parameters()
89          .ok()
90          .unwrap(),
91      ))
92    }
93  }
94}
95
96impl IntoOption<Option<Scope>> for &str {
97  fn into_option(self) -> Option<Scope> {
98    match self {
99      "" => None,
100      "local" => Some(Scope::Local),
101      "Local" => Some(Scope::Local),
102      "LOCAL" => Some(Scope::Local),
103      "global" => Some(Scope::Global),
104      "Global" => Some(Scope::Global),
105      "GLOBAL" => Some(Scope::Global),
106      other => Some(Scope::Named(arena::pin(other))),
107    }
108  }
109}
110impl IntoOption<Option<Scope>> for String {
111  fn into_option(self) -> Option<Scope> {
112    match self.as_ref() {
113      "" => None,
114      "local" => Some(Scope::Local),
115      "Local" => Some(Scope::Local),
116      "LOCAL" => Some(Scope::Local),
117      "global" => Some(Scope::Global),
118      "Global" => Some(Scope::Global),
119      "GLOBAL" => Some(Scope::Global),
120      _ => Some(Scope::Named(arena::pin(self))),
121    }
122  }
123}
124
125// TODO: Sizers need a lot more work, likely a complete rethink about organization.
126impl IntoOption<Option<SizingClosure>> for i64 {
127  fn into_option(self) -> Option<SizingClosure> {
128    Some(Rc::new(move |_| {
129      Ok((
130        Dimension::new(self),
131        Dimension::new(self),
132        Dimension::new(self),
133      ))
134    }))
135  }
136}
137impl IntoOption<Option<SizingClosure>> for &str {
138  fn into_option(self) -> Option<SizingClosure> {
139    if self.is_empty() {
140      None
141    } else if self == "0" {
142      Some(Rc::new(|_| {
143        Ok((
144          Dimension::default(),
145          Dimension::default(),
146          Dimension::default(),
147        ))
148      }))
149    } else if self.starts_with('#') {
150      // Perl: /^(#\w+)*$/ — parse each #token as either numeric arg or property name
151      // e.g. "#3" → getArg(3), "#alignment" → props{alignment}, "#1#2" → both combined
152      let mut refs: Vec<SizerRef> = Vec::new();
153      let mut rest = self;
154      while let Some(stripped) = rest.strip_prefix('#') {
155        let end = stripped.find('#').unwrap_or(stripped.len());
156        let name = &stripped[..end];
157        if let Ok(n) = name.parse::<usize>() {
158          refs.push(SizerRef::Arg(n));
159        } else {
160          refs.push(SizerRef::Prop(name.to_string()));
161        }
162        rest = &stripped[end..];
163      }
164      Some(Rc::new(move |w| {
165        let mut boxes: Vec<Digested> = Vec::with_capacity(refs.len());
166        for r in &refs {
167          match r {
168            SizerRef::Arg(n) => {
169              if let Some(arg) = w.get_arg(*n) {
170                boxes.push(arg.clone());
171              }
172            },
173            SizerRef::Prop(name) => {
174              if let Some(Stored::Digested(d)) = w.get_property(name).as_deref() {
175                boxes.push(d.clone());
176              }
177            },
178          }
179        }
180        if boxes.len() == 1 {
181          // Perl: computeBoxesSize($boxes[0], %options) — pass whatsit properties as options
182          // so vattach, width, etc. propagate to compute_boxes_size
183          let options = sizer_options_from_whatsit(w);
184          boxes[0].compute_size(options)
185        } else if boxes.is_empty() {
186          Ok((
187            Dimension::default(),
188            Dimension::default(),
189            Dimension::default(),
190          ))
191        } else {
192          let font = match w.get_property("font").as_deref() {
193            Some(Stored::Font(font)) => font.clone(),
194            _ => lookup_font().unwrap(),
195          };
196          let options = sizer_options_from_whatsit(w);
197          font.compute_boxes_size(&boxes, options)
198        }
199      }))
200    } else {
201      // literal string, get its size with the current font?
202      let sized_data = String::from(self);
203      Some(Rc::new(move |w| {
204        let font = match *w.get_property("font").unwrap() {
205          Stored::Font(ref font) => font.clone(),
206          _ => lookup_font().unwrap(),
207        };
208        let options = sizer_options_from_whatsit(w);
209        font.compute_boxes_size(
210          &[Digested::from(Tbox {
211            text: arena::pin(&sized_data),
212            ..Tbox::default()
213          })],
214          options,
215        )
216      }))
217    }
218  }
219}
220
221/// A trait for creating `Result<Tokens>` from all sensible concrete types one could
222/// return from e.g. a DefMacro closure
223pub trait IntoTokensResult<T>: Sized {
224  /// Performs the conversion, used for DefMacro return values etc
225  fn into_tokens_result(self) -> Result<Tokens>;
226}
227
228impl IntoTokensResult<Result<Tokens>> for Token {
229  fn into_tokens_result(self) -> Result<Tokens> { Ok(Tokens!(self)) }
230}
231
232impl IntoTokensResult<Result<Tokens>> for Vec<Token> {
233  fn into_tokens_result(self) -> Result<Tokens> { Ok(Tokens::new(self)) }
234}
235
236impl IntoTokensResult<Result<Tokens>> for Tokens {
237  fn into_tokens_result(self) -> Result<Tokens> { Ok(self) }
238}
239
240impl IntoTokensResult<Result<Tokens>> for Result<Tokens> {
241  fn into_tokens_result(self) -> Result<Tokens> { self }
242}
243
244impl IntoTokensResult<Result<Tokens>> for Result<()> {
245  fn into_tokens_result(self) -> Result<Tokens> {
246    match self {
247      Ok(()) => Ok(Tokens!()),
248      Err(e) => Err(e),
249    }
250  }
251}
252
253impl IntoTokensResult<Result<Tokens>> for () {
254  fn into_tokens_result(self) -> Result<Tokens> { Ok(Tokens!()) }
255}
256
257impl IntoTokensResult<Result<Tokens>> for ArgWrap {
258  // TODO: maybe this should be .revert() ?
259  fn into_tokens_result(self) -> Result<Tokens> { Ok(self.owned_tokens().unwrap_or_default()) }
260}
261impl IntoTokensResult<Result<Tokens>> for Result<ArgWrap> {
262  // TODO: maybe this should be .revert() ?
263  fn into_tokens_result(self) -> Result<Tokens> {
264    self.map(|w| w.owned_tokens().unwrap_or_default())
265  }
266}
267
268/// Create a `Result<ArgWrap>` from any concrete type that Gullet may have a reader for.
269/// Used in auto-casting the data fetched by Parameter readers
270pub trait IntoResultArgWrap<T>: Sized {
271  /// performs the conversion
272  fn into_result_argwrap(self) -> Result<ArgWrap>;
273}
274
275impl IntoResultArgWrap<Result<ArgWrap>> for Error {
276  fn into_result_argwrap(self) -> Result<ArgWrap> { Err(self) }
277}
278
279impl<T> IntoResultArgWrap<Result<ArgWrap>> for Result<T>
280where T: Into<ArgWrap> + Sized
281{
282  fn into_result_argwrap(self) -> Result<ArgWrap> { self.map(|v| v.into()) }
283}
284
285impl<T> IntoResultArgWrap<Result<ArgWrap>> for T
286where T: Into<ArgWrap> + Sized
287{
288  fn into_result_argwrap(self) -> Result<ArgWrap> { Ok(self.into()) }
289}
290
291impl IntoResultArgWrap<Result<ArgWrap>> for Vec<Token> {
292  fn into_result_argwrap(self) -> Result<ArgWrap> { Ok(ArgWrap::Tokens(Tokens::new(self))) }
293}
294
295/// Creates `Result<bool>` from some type `T`
296pub trait IntoBoolResult<T>: Sized {
297  /// Performs the conversion, used for DefConditional return values etc
298  fn into_bool_result(self) -> Result<bool>;
299}
300impl IntoBoolResult<Result<bool>> for bool {
301  fn into_bool_result(self) -> Result<bool> { Ok(self) }
302}
303impl IntoBoolResult<Result<bool>> for Result<bool> {
304  fn into_bool_result(self) -> Result<bool> { self }
305}
306
307/// Creates a `Result<Vec<Digested>>` from some type `T`
308pub trait IntoDigestedResult<T>: Sized {
309  /// Performs the conversion, used for DefPrimitive return values etc
310  fn into_digested_result(self) -> Result<Vec<Digested>>;
311}
312impl IntoDigestedResult<Result<Vec<Digested>>> for () {
313  fn into_digested_result(self) -> Result<Vec<Digested>> { Ok(Vec::new()) }
314}
315impl IntoDigestedResult<Result<Vec<Digested>>> for Error {
316  fn into_digested_result(self) -> Result<Vec<Digested>> { Err(self) }
317}
318impl IntoDigestedResult<Result<Vec<Digested>>> for Result<()> {
319  fn into_digested_result(self) -> Result<Vec<Digested>> { self.map(|_| Vec::new()) }
320}
321impl IntoDigestedResult<Result<Vec<Digested>>> for Tbox {
322  fn into_digested_result(self) -> Result<Vec<Digested>> { Ok(vec![self.into()]) }
323}
324impl IntoDigestedResult<Result<Vec<Digested>>> for Result<Tbox> {
325  fn into_digested_result(self) -> Result<Vec<Digested>> { self.map(|tb| vec![tb.into()]) }
326}
327
328impl IntoDigestedResult<Result<Vec<Digested>>> for Whatsit {
329  fn into_digested_result(self) -> Result<Vec<Digested>> { Ok(vec![self.into()]) }
330}
331
332impl IntoDigestedResult<Result<Vec<Digested>>> for List {
333  fn into_digested_result(self) -> Result<Vec<Digested>> { Ok(vec![self.into()]) }
334}
335
336impl IntoDigestedResult<Result<Vec<Digested>>> for Digested {
337  fn into_digested_result(self) -> Result<Vec<Digested>> { Ok(vec![self]) }
338}
339
340impl IntoDigestedResult<Result<Vec<Digested>>> for Vec<Digested> {
341  fn into_digested_result(self) -> Result<Vec<Digested>> { Ok(self) }
342}
343
344impl IntoDigestedResult<Result<Vec<Digested>>> for Result<Vec<Digested>> {
345  fn into_digested_result(self) -> Result<Vec<Digested>> { self }
346}
347impl IntoDigestedResult<Result<Vec<Digested>>> for Result<Digested> {
348  fn into_digested_result(self) -> Result<Vec<Digested>> { self.map(|d| vec![d]) }
349}
350
351/// Creates an `Option<RegisterValue>` from some type `T`.
352/// Useful for Register `getter` closures
353pub trait IntoRegisterValueOption<T>: Sized {
354  fn into_register_value_option(self) -> Option<RegisterValue>;
355}
356impl IntoRegisterValueOption<Option<RegisterValue>> for () {
357  fn into_register_value_option(self) -> Option<RegisterValue> { None }
358}
359impl IntoRegisterValueOption<Option<RegisterValue>> for Option<RegisterValue> {
360  fn into_register_value_option(self) -> Option<RegisterValue> { self }
361}
362impl IntoRegisterValueOption<Option<RegisterValue>> for usize {
363  fn into_register_value_option(self) -> Option<RegisterValue> {
364    Some(RegisterValue::Number(Number(self as i64)))
365  }
366}
367impl IntoRegisterValueOption<Option<RegisterValue>> for Number {
368  fn into_register_value_option(self) -> Option<RegisterValue> { Some(RegisterValue::Number(self)) }
369}
370impl IntoRegisterValueOption<Option<RegisterValue>> for Dimension {
371  fn into_register_value_option(self) -> Option<RegisterValue> {
372    Some(RegisterValue::Dimension(self))
373  }
374}
375impl IntoRegisterValueOption<Option<RegisterValue>> for MuDimension {
376  fn into_register_value_option(self) -> Option<RegisterValue> {
377    Some(RegisterValue::MuDimension(self))
378  }
379}
380impl IntoRegisterValueOption<Option<RegisterValue>> for Glue {
381  fn into_register_value_option(self) -> Option<RegisterValue> { Some(RegisterValue::Glue(self)) }
382}
383impl IntoRegisterValueOption<Option<RegisterValue>> for MuGlue {
384  fn into_register_value_option(self) -> Option<RegisterValue> { Some(RegisterValue::MuGlue(self)) }
385}
386impl IntoRegisterValueOption<Option<RegisterValue>> for Token {
387  fn into_register_value_option(self) -> Option<RegisterValue> { Some(RegisterValue::Token(self)) }
388}
389impl IntoRegisterValueOption<Option<RegisterValue>> for Option<Token> {
390  fn into_register_value_option(self) -> Option<RegisterValue> { self.map(RegisterValue::Token) }
391}
392impl IntoRegisterValueOption<Option<RegisterValue>> for Tokens {
393  fn into_register_value_option(self) -> Option<RegisterValue> { Some(RegisterValue::Tokens(self)) }
394}
395
396impl IntoRegisterValueOption<Option<RegisterValue>> for Option<Number> {
397  fn into_register_value_option(self) -> Option<RegisterValue> { self.map(RegisterValue::Number) }
398}
399
400// Convenience methods for predigest closures that require Result<Option<Digested>>
401pub trait IntoDigestedOptionResult<T>: Sized {
402  fn into_digested_option_result(self) -> Result<Option<Digested>>;
403}
404
405impl IntoDigestedOptionResult<Result<Option<Digested>>> for () {
406  fn into_digested_option_result(self: ()) -> Result<Option<Digested>> { Ok(None) }
407}
408
409impl IntoDigestedOptionResult<Result<Option<Digested>>> for Glue {
410  fn into_digested_option_result(self) -> Result<Option<Digested>> {
411    RegisterValue::Glue(self).into_digested_option_result()
412  }
413}
414impl IntoDigestedOptionResult<Result<Option<Digested>>> for MuGlue {
415  fn into_digested_option_result(self) -> Result<Option<Digested>> {
416    RegisterValue::MuGlue(self).into_digested_option_result()
417  }
418}
419impl IntoDigestedOptionResult<Result<Option<Digested>>> for Dimension {
420  fn into_digested_option_result(self) -> Result<Option<Digested>> {
421    RegisterValue::Dimension(self).into_digested_option_result()
422  }
423}
424impl IntoDigestedOptionResult<Result<Option<Digested>>> for MuDimension {
425  fn into_digested_option_result(self) -> Result<Option<Digested>> {
426    RegisterValue::MuDimension(self).into_digested_option_result()
427  }
428}
429
430impl IntoDigestedOptionResult<Result<Option<Digested>>> for Number {
431  fn into_digested_option_result(self) -> Result<Option<Digested>> {
432    RegisterValue::Number(self).into_digested_option_result()
433  }
434}
435
436impl IntoDigestedOptionResult<Result<Option<Digested>>> for RegisterValue {
437  fn into_digested_option_result(self) -> Result<Option<Digested>> { Ok(Some(self.into())) }
438}
439impl IntoDigestedOptionResult<Result<Option<Digested>>> for Option<Digested> {
440  fn into_digested_option_result(self) -> Result<Option<Digested>> { Ok(self) }
441}
442impl IntoDigestedOptionResult<Result<Option<Digested>>> for Result<Option<Digested>> {
443  fn into_digested_option_result(self) -> Result<Option<Digested>> { self }
444}
445impl IntoDigestedOptionResult<Result<Option<Digested>>> for KeyVals {
446  fn into_digested_option_result(self) -> Result<Option<Digested>> {
447    Ok(Some(Digested::from(self)))
448  }
449}
450impl IntoDigestedOptionResult<Result<Option<Digested>>> for Option<KeyVals> {
451  fn into_digested_option_result(self) -> Result<Option<Digested>> {
452    match self {
453      None => Ok(None),
454      Some(kv) => kv.into(),
455    }
456  }
457}
458impl IntoDigestedOptionResult<Result<Option<Digested>>> for List {
459  fn into_digested_option_result(self) -> Result<Option<Digested>> {
460    Ok(Some(Digested::from(self)))
461  }
462}
463
464pub trait IntoPropertiesResult {
465  fn into_properties_result(self) -> Result<HashMap<Stored>>;
466}
467impl IntoPropertiesResult for HashMap<Stored> {
468  fn into_properties_result(self) -> Result<HashMap<Stored>> { Ok(self) }
469}
470impl IntoPropertiesResult for Result<HashMap<Stored>> {
471  fn into_properties_result(self) -> Result<HashMap<Stored>> { self }
472}
473
474pub trait IntoFontField<T>: Sized {
475  fn into_font_field(self) -> T;
476}
477
478impl IntoFontField<Option<bool>> for bool {
479  fn into_font_field(self) -> Option<bool> { Some(self) }
480}
481
482impl IntoFontField<bool> for bool {
483  fn into_font_field(self) -> bool { self }
484}
485
486impl IntoFontField<Option<Cow<'static, str>>> for &'static str {
487  fn into_font_field(self) -> Option<Cow<'static, str>> { Some(Cow::Borrowed(self)) }
488}
489impl IntoFontField<Option<f64>> for f64 {
490  fn into_font_field(self) -> Option<f64> { Some(self) }
491}
492impl IntoFontField<Option<f64>> for i32 {
493  fn into_font_field(self) -> Option<f64> { Some(self as f64) }
494}
495impl IntoFontField<Option<Color>> for Color {
496  fn into_font_field(self) -> Option<Color> { Some(self) }
497}