1use common::font;
4use std::borrow::Cow;
5
6use super::ast;
7use crate::ds;
8
9pub trait ToBoxLang {
11 type Output;
12 fn to_box_lang(&self) -> Self::Output;
13}
14
15pub 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 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 const F1: font::Id = font::Id::ONE;
551
552 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}