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