1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! # Boxworks text preprocessor
//!
//! This crate implements the logic that converts text (words and spaces)
//! into horizontal list elements.
//! It is implemented in the Chief Executive chapter in Knuth's
//! TeX (starting in TeX.2021.1029).

use boxworks::ds;

pub struct Font {
    pub default_space: core::Glue,
}

#[derive(Default)]
pub struct TextPreprocessorImpl {
    fonts: Vec<Font>,
    // TODO: should be initialized to the null font
    // TODO: should current_font be some kind of specific font identifier type.
    current_font: u32,
}

impl boxworks::TextPreprocessor for TextPreprocessorImpl {
    fn add_text(&mut self, text: &str, list: &mut Vec<ds::Horizontal>) {
        // TODO: kerns
        // TODO: ligatures
        for c in text.chars() {
            list.push(ds::Horizontal::Char(ds::Char {
                char: c,
                font: self.current_font,
            }));
        }
    }

    fn add_space(&mut self, list: &mut Vec<ds::Horizontal>) {
        // TeX.2021.1041
        // TODO: space factor, etc.
        list.push(ds::Horizontal::Glue(
            self.fonts[self.current_font as usize].default_space.into(),
        ));
    }
}

impl TextPreprocessorImpl {
    pub fn register_font(&mut self, id: u32, font: Font) {
        assert_eq!(id as usize, self.fonts.len());
        self.fonts.push(font);
    }
    pub fn activate_font(&mut self, id: u32) {
        self.current_font = id;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use boxworks::TextPreprocessor;
    use boxworks_lang as bwl;

    macro_rules! preprocessor_tests {
        ( $( ( $name: ident, $input: expr, $want: expr, ), )+ ) => {
            $(
                #[test]
                fn $name() {
                    let input = $input;
                    let want = $want;
                    run_preprocessor_test(input, want)
                }
            )+
        };
    }

    preprocessor_tests!(
        (
            cmr10_basic,
            "second",
            r#"
                text("second", font=0)
            "#,
        ),
        (
            cmr10_basic_with_space,
            "sec ond",
            r#"
                text("sec", font=0)
                glue(3.33333pt, 1.66666pt, 1.11111pt)
                text("ond", font=0)
            "#,
        ),
    );

    fn run_preprocessor_test(input: &str, want: &str) {
        let want = bwl::parse_horizontal_list(want).unwrap();

        let mut tp: TextPreprocessorImpl = Default::default();
        tp.register_font(
            0,
            Font {
                default_space: core::Glue {
                    width: core::Scaled::ONE * 10 / 3,
                    stretch: core::Scaled::ONE * 5 / 3,
                    stretch_order: core::GlueOrder::Normal,
                    shrink: core::Scaled::ONE * 10 / 9 + core::Scaled(1),
                    shrink_order: core::GlueOrder::Normal,
                },
            },
        );
        tp.activate_font(0);
        let mut got = vec![];
        for word in input.split_inclusive(' ') {
            tp.add_text(word.trim_matches(' '), &mut got);
            if word.ends_with(" ") {
                tp.add_space(&mut got);
            }
        }

        assert_eq!(got, want);
    }
}