boxworks/lang/
convert.rs

1//! Conversions between Box language and Boxworks data structures
2
3use common::font;
4use std::borrow::Cow;
5
6use super::ast;
7use crate::ds;
8
9/// Convert a Boxworks data structure to a Box language data structure.
10pub trait ToBoxLang {
11    type Output;
12    fn to_box_lang(&self) -> Self::Output;
13}
14
15/// Convert a Box language data structure to a Boxworks data structure.
16pub trait ToBoxworks {
17    type Output;
18    fn to_boxworks(&self) -> Self::Output;
19}
20
21impl ToBoxLang for ds::Vertical {
22    type Output = ast::Vertical<'static>;
23    fn to_box_lang(&self) -> Self::Output {
24        use crate::ds::Vertical::*;
25        match self {
26            HBox(hbox) => ast::Vertical::HBox(hbox.to_box_lang()),
27            VBox(vbox) => ast::Vertical::VBox(vbox.to_box_lang()),
28            Rule(rule) => ast::Vertical::Rule(rule.to_box_lang()),
29            Mark(mark) => ast::Vertical::Mark(mark.to_box_lang()),
30            Insertion(insertion) => ast::Vertical::Insertion(insertion.to_box_lang()),
31            Whatsit(_whatsit) => todo!(),
32            Math(math) => ast::Vertical::Math(math.to_box_lang()),
33            Glue(glue) => ast::Vertical::Glue(glue.to_box_lang()),
34            Kern(kern) => ast::Vertical::Kern(kern.to_box_lang()),
35            Penalty(penalty) => ast::Vertical::Penalty(penalty.to_box_lang()),
36        }
37    }
38}
39
40impl<'a> ToBoxworks for ast::Vertical<'a> {
41    type Output = ds::Vertical;
42
43    fn to_boxworks(&self) -> Self::Output {
44        use ast::Vertical::*;
45        match self {
46            HBox(hbox_args) => ds::Vertical::HBox(hbox_args.to_boxworks()),
47            VBox(vbox_args) => ds::Vertical::VBox(vbox_args.to_boxworks()),
48            Glue(glue_args) => ds::Vertical::Glue(glue_args.to_boxworks()),
49            Kern(kern_args) => ds::Vertical::Kern(kern_args.to_boxworks()),
50            Penalty(penalty_args) => ds::Vertical::Penalty(penalty_args.to_boxworks()),
51            Rule(rule_args) => ds::Vertical::Rule(rule_args.to_boxworks()),
52            Mark(mark_args) => ds::Vertical::Mark(mark_args.to_boxworks()),
53            Insertion(insertion_args) => ds::Vertical::Insertion(insertion_args.to_boxworks()),
54            Math(math_args) => ds::Vertical::Math(math_args.to_boxworks()),
55        }
56    }
57}
58
59impl ToBoxLang for Vec<ds::Horizontal> {
60    type Output = Vec<ast::Horizontal<'static>>;
61    fn to_box_lang(&self) -> Self::Output {
62        let mut out = vec![];
63        let mut current_font: Option<font::Id> = None;
64        let mut buf: String = Default::default();
65        let flush_chars = |out: &mut Vec<ast::Horizontal<'static>>,
66                           current_font: &mut Option<font::Id>,
67                           buf: &mut String| {
68            let Some(current_font) = current_font.take() else {
69                // Nothing to flush.
70                return;
71            };
72            out.push(ast::Horizontal::Chars(ast::Chars {
73                content: Cow::<str>::Owned(buf.clone()).into(),
74                font: current_font.into(),
75            }));
76            buf.clear();
77        };
78        for elem in self {
79            match elem {
80                ds::Horizontal::Char(ds::Char { char, font }) => {
81                    if Some(*font) != current_font {
82                        flush_chars(&mut out, &mut current_font, &mut buf);
83                    }
84                    current_font = Some(*font);
85                    buf.push(*char);
86                }
87                _ => {
88                    flush_chars(&mut out, &mut current_font, &mut buf);
89                    out.push(elem.to_box_lang());
90                }
91            }
92        }
93        flush_chars(&mut out, &mut current_font, &mut buf);
94        out
95    }
96}
97
98impl ToBoxLang for Vec<ds::Vertical> {
99    type Output = Vec<ast::Vertical<'static>>;
100    fn to_box_lang(&self) -> Self::Output {
101        self.iter().map(|b| b.to_box_lang()).collect()
102    }
103}
104
105impl ToBoxLang for Vec<ds::DiscretionaryElem> {
106    type Output = Vec<ast::DiscretionaryElem<'static>>;
107    fn to_box_lang(&self) -> Self::Output {
108        self.iter().map(|b| b.to_box_lang()).collect()
109    }
110}
111
112impl<'a> ToBoxworks for Vec<ast::Horizontal<'a>> {
113    type Output = Vec<ds::Horizontal>;
114    fn to_boxworks(&self) -> Self::Output {
115        self.iter().flat_map(|b| b.to_boxworks()).collect()
116    }
117}
118
119impl<'a> ToBoxworks for Vec<ast::Vertical<'a>> {
120    type Output = Vec<ds::Vertical>;
121    fn to_boxworks(&self) -> Self::Output {
122        self.iter().map(|b| b.to_boxworks()).collect()
123    }
124}
125
126impl<'a> ToBoxworks for Vec<ast::DiscretionaryElem<'a>> {
127    type Output = Vec<ds::DiscretionaryElem>;
128    fn to_boxworks(&self) -> Self::Output {
129        self.iter().flat_map(|b| b.to_boxworks()).collect()
130    }
131}
132
133impl<'a> ToBoxworks for ast::Horizontal<'a> {
134    type Output = Vec<ds::Horizontal>;
135
136    fn to_boxworks(&self) -> Self::Output {
137        use ast::Horizontal::*;
138        match self {
139            Chars(chars_args) => chars_args.to_boxworks(),
140            Glue(glue_args) => vec![ds::Horizontal::Glue(glue_args.to_boxworks())],
141            Kern(kern_args) => vec![ds::Horizontal::Kern(kern_args.to_boxworks())],
142            HBox(hbox_args) => vec![ds::Horizontal::HBox(hbox_args.to_boxworks())],
143            VBox(vbox_args) => vec![ds::Horizontal::VBox(vbox_args.to_boxworks())],
144            Ligature(lig_args) => vec![ds::Horizontal::Ligature(lig_args.to_boxworks())],
145            Discretionary(disc_args) => {
146                vec![ds::Horizontal::Discretionary(disc_args.to_boxworks())]
147            }
148            Rule(rule_args) => vec![ds::Horizontal::Rule(rule_args.to_boxworks())],
149            Penalty(penalty_args) => vec![ds::Horizontal::Penalty(penalty_args.to_boxworks())],
150            Mark(mark_args) => vec![ds::Horizontal::Mark(mark_args.to_boxworks())],
151            Adjust(adjust_args) => vec![ds::Horizontal::Adjust(adjust_args.to_boxworks())],
152            Insertion(insertion_args) => {
153                vec![ds::Horizontal::Insertion(insertion_args.to_boxworks())]
154            }
155            Math(math_args) => vec![ds::Horizontal::Math(math_args.to_boxworks())],
156        }
157    }
158}
159
160impl ToBoxLang for ds::Horizontal {
161    type Output = ast::Horizontal<'static>;
162    fn to_box_lang(&self) -> Self::Output {
163        use crate::ds::Horizontal::*;
164        match self {
165            Char(char) => ast::Horizontal::Chars(char.to_box_lang()),
166            HBox(hbox) => ast::Horizontal::HBox(hbox.to_box_lang()),
167            VBox(vbox) => ast::Horizontal::VBox(vbox.to_box_lang()),
168            Rule(rule) => ast::Horizontal::Rule(rule.to_box_lang()),
169            Mark(mark) => ast::Horizontal::Mark(mark.to_box_lang()),
170            Insertion(insertion) => ast::Horizontal::Insertion(insertion.to_box_lang()),
171            Adjust(adjust) => ast::Horizontal::Adjust(adjust.to_box_lang()),
172            Ligature(ligature) => ast::Horizontal::Ligature(ligature.to_box_lang()),
173            Discretionary(discretionary) => {
174                ast::Horizontal::Discretionary(discretionary.to_box_lang())
175            }
176            Whatsit(_whatsit) => todo!(),
177            Math(math) => ast::Horizontal::Math(math.to_box_lang()),
178            Glue(glue) => ast::Horizontal::Glue(glue.to_box_lang()),
179            Kern(kern) => ast::Horizontal::Kern(kern.to_box_lang()),
180            Penalty(penalty) => ast::Horizontal::Penalty(penalty.to_box_lang()),
181        }
182    }
183}
184
185impl<'a> ToBoxworks for ast::DiscretionaryElem<'a> {
186    type Output = Vec<ds::DiscretionaryElem>;
187
188    fn to_boxworks(&self) -> Self::Output {
189        use ast::DiscretionaryElem::*;
190        use ds::DiscretionaryElem as Out;
191        match self {
192            Chars(chars_args) => chars_to_discretionary_elems(chars_args),
193            Kern(kern_args) => vec![Out::Kern(kern_args.to_boxworks())],
194            HBox(hbox_args) => vec![Out::HBox(hbox_args.to_boxworks())],
195            VBox(vbox_args) => vec![Out::VBox(vbox_args.to_boxworks())],
196            Ligature(lig_args) => vec![Out::Ligature(lig_args.to_boxworks())],
197            Rule(rule_args) => vec![Out::Rule(rule_args.to_boxworks())],
198        }
199    }
200}
201
202impl ToBoxLang for ds::DiscretionaryElem {
203    type Output = ast::DiscretionaryElem<'static>;
204    fn to_box_lang(&self) -> Self::Output {
205        use crate::ds::DiscretionaryElem::*;
206        use ast::DiscretionaryElem as Out;
207        match self {
208            Char(char) => Out::Chars(char.to_box_lang()),
209            HBox(hbox) => Out::HBox(hbox.to_box_lang()),
210            VBox(vbox) => Out::VBox(vbox.to_box_lang()),
211            Rule(rule) => Out::Rule(rule.to_box_lang()),
212            Ligature(ligature) => Out::Ligature(ligature.to_box_lang()),
213            Kern(kern) => Out::Kern(kern.to_box_lang()),
214        }
215    }
216}
217
218impl ToBoxLang for ds::VBox {
219    type Output = ast::VBox<'static>;
220    fn to_box_lang(&self) -> Self::Output {
221        ast::VBox {
222            height: self.height.into(),
223            width: self.width.into(),
224            depth: self.depth.into(),
225            shift_amount: self.shift_amount.into(),
226            content: self.list.to_box_lang().into(),
227        }
228    }
229}
230
231impl ToBoxLang for ds::HBox {
232    type Output = ast::HBox<'static>;
233    fn to_box_lang(&self) -> Self::Output {
234        ast::HBox {
235            height: self.height.into(),
236            width: self.width.into(),
237            depth: self.depth.into(),
238            shift_amount: self.shift_amount.into(),
239            glue_ratio: self.glue_ratio.into(),
240            glue_order: self.glue_order.into(),
241            content: self.list.to_box_lang().into(),
242        }
243    }
244}
245
246impl<'a> ToBoxworks for ast::HBox<'a> {
247    type Output = ds::HBox;
248    fn to_boxworks(&self) -> Self::Output {
249        ds::HBox {
250            height: self.height.value,
251            width: self.width.value,
252            depth: self.depth.value,
253            shift_amount: self.shift_amount.value,
254            glue_ratio: self.glue_ratio.value,
255            glue_order: self.glue_order.value,
256            list: self.content.value.to_boxworks(),
257        }
258    }
259}
260
261impl<'a> ToBoxworks for ast::VBox<'a> {
262    type Output = ds::VBox;
263    fn to_boxworks(&self) -> Self::Output {
264        ds::VBox {
265            height: self.height.value,
266            width: self.width.value,
267            depth: self.depth.value,
268            shift_amount: self.shift_amount.value,
269            list: self.content.value.to_boxworks(),
270            ..Default::default()
271        }
272    }
273}
274
275impl<'a> ToBoxworks for ast::Ligature<'a> {
276    type Output = ds::Ligature;
277
278    fn to_boxworks(&self) -> Self::Output {
279        ds::Ligature {
280            char: self.char.value,
281            font: self.font.value,
282            original_chars: self.original_chars.value.clone().into(),
283            includes_left_boundary: self.includes_left_boundary.value,
284            includes_right_boundary: self.includes_right_boundary.value,
285        }
286    }
287}
288
289impl ToBoxLang for ds::Ligature {
290    type Output = ast::Ligature<'static>;
291
292    fn to_box_lang(&self) -> Self::Output {
293        ast::Ligature {
294            char: self.char.into(),
295            original_chars: Cow::<'static, str>::Owned(format!["{}", self.original_chars]).into(),
296            font: self.font.into(),
297            includes_left_boundary: self.includes_left_boundary.into(),
298            includes_right_boundary: self.includes_right_boundary.into(),
299        }
300    }
301}
302
303impl ToBoxLang for ds::Char {
304    type Output = ast::Chars<'static>;
305    fn to_box_lang(&self) -> Self::Output {
306        ast::Chars {
307            content: Cow::<'static, str>::Owned(format!["{}", self.char]).into(),
308            font: self.font.into(),
309        }
310    }
311}
312
313impl<'a> ToBoxworks for ast::Chars<'a> {
314    type Output = Vec<ds::Horizontal>;
315    fn to_boxworks(&self) -> Self::Output {
316        self.content
317            .value
318            .chars()
319            .map(|c| {
320                ds::Horizontal::Char(ds::Char {
321                    char: c,
322                    font: self.font.value,
323                })
324            })
325            .collect()
326    }
327}
328
329fn chars_to_discretionary_elems<'a>(chars: &ast::Chars<'a>) -> Vec<ds::DiscretionaryElem> {
330    chars
331        .content
332        .value
333        .chars()
334        .map(|c| {
335            ds::DiscretionaryElem::Char(ds::Char {
336                char: c,
337                font: chars.font.value,
338            })
339        })
340        .collect()
341}
342
343impl ToBoxLang for ds::Penalty {
344    type Output = ast::Penalty<'static>;
345    fn to_box_lang(&self) -> Self::Output {
346        ast::Penalty {
347            value: self.0.into(),
348        }
349    }
350}
351
352impl<'a> ToBoxworks for ast::Penalty<'a> {
353    type Output = ds::Penalty;
354    fn to_boxworks(&self) -> Self::Output {
355        ds::Penalty(self.value.value)
356    }
357}
358
359impl ToBoxLang for ds::Glue {
360    type Output = ast::Glue<'static>;
361    fn to_box_lang(&self) -> Self::Output {
362        ast::Glue {
363            width: self.value.width.into(),
364            stretch: (self.value.stretch, self.value.stretch_order).into(),
365            shrink: (self.value.shrink, self.value.shrink_order).into(),
366        }
367    }
368}
369
370impl<'a> ToBoxworks for ast::Glue<'a> {
371    type Output = ds::Glue;
372    fn to_boxworks(&self) -> Self::Output {
373        ds::Glue {
374            value: common::Glue {
375                width: self.width.value,
376                stretch: self.stretch.value.0,
377                stretch_order: self.stretch.value.1,
378                shrink: self.shrink.value.0,
379                shrink_order: self.shrink.value.1,
380            },
381            kind: ds::GlueKind::Normal,
382        }
383    }
384}
385
386impl ToBoxLang for ds::Kern {
387    type Output = ast::Kern<'static>;
388    fn to_box_lang(&self) -> Self::Output {
389        ast::Kern {
390            width: self.width.into(),
391        }
392    }
393}
394
395impl<'a> ToBoxworks for ast::Kern<'a> {
396    type Output = ds::Kern;
397    fn to_boxworks(&self) -> Self::Output {
398        ds::Kern {
399            width: self.width.value,
400            kind: ds::KernKind::Normal,
401        }
402    }
403}
404
405impl ToBoxLang for ds::Discretionary {
406    type Output = ast::Discretionary<'static>;
407    fn to_box_lang(&self) -> Self::Output {
408        ast::Discretionary {
409            pre_break: self.pre_break.to_box_lang().into(),
410            post_break: self.post_break.to_box_lang().into(),
411            replace_count: (self.replace_count as i32).into(),
412        }
413    }
414}
415
416impl<'a> ToBoxworks for ast::Discretionary<'a> {
417    type Output = ds::Discretionary;
418    fn to_boxworks(&self) -> Self::Output {
419        ds::Discretionary {
420            pre_break: self.pre_break.value.to_boxworks(),
421            post_break: self.post_break.value.to_boxworks(),
422            replace_count: self.replace_count.value as u32,
423        }
424    }
425}
426
427impl ToBoxLang for ds::Rule {
428    type Output = ast::Rule<'static>;
429    fn to_box_lang(&self) -> Self::Output {
430        ast::Rule {
431            height: ast::MaybeRunning::from_scaled(self.height).into(),
432            width: ast::MaybeRunning::from_scaled(self.width).into(),
433            depth: ast::MaybeRunning::from_scaled(self.depth).into(),
434        }
435    }
436}
437
438impl<'a> ToBoxworks for ast::Rule<'a> {
439    type Output = ds::Rule;
440    fn to_boxworks(&self) -> Self::Output {
441        ds::Rule {
442            height: self.height.value.to_scaled(),
443            width: self.width.value.to_scaled(),
444            depth: self.depth.value.to_scaled(),
445        }
446    }
447}
448
449impl ToBoxLang for ds::Mark {
450    type Output = ast::Mark<'static>;
451    fn to_box_lang(&self) -> Self::Output {
452        ast::Mark::default()
453    }
454}
455
456impl<'a> ToBoxworks for ast::Mark<'a> {
457    type Output = ds::Mark;
458    fn to_boxworks(&self) -> Self::Output {
459        ds::Mark { list: vec![] }
460    }
461}
462
463impl ToBoxLang for ds::Adjust {
464    type Output = ast::Adjust<'static>;
465    fn to_box_lang(&self) -> Self::Output {
466        ast::Adjust {
467            content: self.list.to_box_lang().into(),
468        }
469    }
470}
471
472impl<'a> ToBoxworks for ast::Adjust<'a> {
473    type Output = ds::Adjust;
474    fn to_boxworks(&self) -> Self::Output {
475        ds::Adjust {
476            list: self.content.value.to_boxworks(),
477        }
478    }
479}
480
481impl ToBoxLang for ds::Insertion {
482    type Output = ast::Insertion<'static>;
483    fn to_box_lang(&self) -> Self::Output {
484        ast::Insertion {
485            box_number: (self.box_number as i32).into(),
486            height: self.height.into(),
487            split_max_depth: self.split_max_depth.into(),
488            split_top_skip_width: self.split_top_skip.width.into(),
489            split_top_skip_stretch: (
490                self.split_top_skip.stretch,
491                self.split_top_skip.stretch_order,
492            )
493                .into(),
494            split_top_skip_shrink: (self.split_top_skip.shrink, self.split_top_skip.shrink_order)
495                .into(),
496            float_penalty: (self.float_penalty as i32).into(),
497            vbox: self.vbox.to_box_lang().into(),
498        }
499    }
500}
501
502impl<'a> ToBoxworks for ast::Insertion<'a> {
503    type Output = ds::Insertion;
504    fn to_boxworks(&self) -> Self::Output {
505        ds::Insertion {
506            box_number: self.box_number.value as u8,
507            height: self.height.value,
508            split_max_depth: self.split_max_depth.value,
509            split_top_skip: common::Glue {
510                width: self.split_top_skip_width.value,
511                stretch: self.split_top_skip_stretch.value.0,
512                stretch_order: self.split_top_skip_stretch.value.1,
513                shrink: self.split_top_skip_shrink.value.0,
514                shrink_order: self.split_top_skip_shrink.value.1,
515            },
516            float_penalty: self.float_penalty.value as u32,
517            vbox: self.vbox.value.to_boxworks(),
518        }
519    }
520}
521
522impl ToBoxLang for ds::Math {
523    type Output = ast::Math<'static>;
524    fn to_box_lang(&self) -> Self::Output {
525        ast::Math {
526            kind: match self {
527                ds::Math::Before => Cow::Borrowed("before"),
528                ds::Math::After => Cow::Borrowed("after"),
529            }
530            .into(),
531        }
532    }
533}
534
535impl<'a> ToBoxworks for ast::Math<'a> {
536    type Output = ds::Math;
537    fn to_boxworks(&self) -> Self::Output {
538        match self.kind.value.as_ref() {
539            "after" => ds::Math::After,
540            _ => ds::Math::Before,
541        }
542    }
543}
544
545#[cfg(test)]
546mod tests {
547    use super::*;
548
549    /// The first font.
550    const F1: font::Id = font::Id::ONE;
551
552    /// The second font, used in tests that involve more than one font.
553    fn f2() -> font::Id {
554        font::Id(2)
555    }
556
557    macro_rules! tests {
558        ( $( ($name: ident, $input: expr, $want: expr,), )+ ) => {
559            $(
560                #[test]
561                fn $name() {
562                    let input: Vec<ds::Horizontal> = $input;
563                    let want: Vec<ast::Horizontal<'static>> = $want;
564                    let got = input.to_box_lang();
565                    assert_eq!(got, want);
566                }
567            )+
568        };
569    }
570
571    tests!(
572        (
573            chars_same_font,
574            vec![
575                ds::Char {
576                    char: 'B',
577                    font: F1
578                }
579                .into(),
580                ds::Char {
581                    char: 'o',
582                    font: F1
583                }
584                .into(),
585                ds::Char {
586                    char: 'x',
587                    font: F1
588                }
589                .into(),
590            ],
591            vec![ast::Chars {
592                content: Cow::Borrowed("Box").into(),
593                font: F1.into(),
594            }
595            .into()],
596        ),
597        (
598            chars_different_font,
599            vec![
600                ds::Char {
601                    char: 'B',
602                    font: F1
603                }
604                .into(),
605                ds::Char {
606                    char: 'o',
607                    font: F1
608                }
609                .into(),
610                ds::Char {
611                    char: 'x',
612                    font: F1
613                }
614                .into(),
615                ds::Char {
616                    char: 'e',
617                    font: f2()
618                }
619                .into(),
620                ds::Char {
621                    char: 'd',
622                    font: f2()
623                }
624                .into(),
625            ],
626            vec![
627                ast::Chars {
628                    content: Cow::Borrowed("Box").into(),
629                    font: F1.into(),
630                }
631                .into(),
632                ast::Chars {
633                    content: Cow::Borrowed("ed").into(),
634                    font: f2().into(),
635                }
636                .into(),
637            ],
638        ),
639    );
640}