boxworks_knuthplass/
debug.rs

1use std::{cell::RefCell, rc::Rc};
2
3use boxworks::ds;
4
5pub struct FeasibleBreakpoint {
6    pub elem_index: usize,
7    pub badness: i32,
8    pub penalty: i32,
9    pub demerits: i32,
10    pub artificial_demerits: bool,
11    pub previous_node_index: usize,
12}
13
14pub struct NewActiveNode {
15    pub node_index: usize,
16    pub line_number: usize,
17    pub fitness_class: u8,
18    pub hyphenated: bool,
19    pub total_demerits: i32,
20    pub artificial_demerits: bool,
21    pub previous_node_index: usize,
22}
23
24pub trait Logger {
25    fn log_attempt(&mut self, attempt: Attempt);
26    fn log_feasible_breakpoint(&mut self, list: &[ds::Horizontal], fb: FeasibleBreakpoint);
27    fn log_new_active_node(&mut self, an: NewActiveNode);
28    /// Called with the node index of the active node the algorithm chose
29    /// (TeX.2021.874-877); the chosen breakpoints are the chain of
30    /// `previous_node_index` links from this node. TeX's log has no
31    /// equivalent line, hence the default no-op. This is used e.g. in knuthplass.dev
32    /// to extract more information from the algorithm.
33    fn log_selected_node(&mut self, _node_index: usize) {}
34}
35
36pub struct TexLogger {
37    writer: Rc<RefCell<dyn std::fmt::Write>>,
38    next_elem_to_write: usize,
39}
40
41impl TexLogger {
42    pub fn new(writer: Rc<RefCell<dyn std::fmt::Write>>) -> Self {
43        Self {
44            writer,
45            next_elem_to_write: 0,
46        }
47    }
48}
49
50/// Attempts made at line breaking.
51#[derive(Clone, Copy)]
52pub enum Attempt {
53    First,
54    Second,
55    Emergency,
56}
57
58impl Attempt {
59    pub fn number(self) -> u8 {
60        use Attempt::*;
61        match self {
62            First => 1,
63            Second => 2,
64            Emergency => 3,
65        }
66    }
67}
68
69impl std::fmt::Display for Attempt {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        use Attempt::*;
72        let s = match self {
73            First => "firstpass",
74            Second => "secondpass",
75            Emergency => "emergencypass",
76        };
77        write!(f, "{}", s)
78    }
79}
80
81impl Logger for TexLogger {
82    fn log_attempt(&mut self, attempt: Attempt) {
83        self.next_elem_to_write = 0;
84        // TeX.2021.863
85        let _ = writeln!(self.writer.borrow_mut(), "@{}", attempt);
86    }
87    fn log_feasible_breakpoint(&mut self, list: &[ds::Horizontal], fb: FeasibleBreakpoint) {
88        if self.next_elem_to_write <= fb.elem_index {
89            let upper = if fb.elem_index >= list.len() {
90                list.len() - 1
91            } else {
92                fb.elem_index
93            };
94            _ = boxworks::ds::short_display_hlist(
95                &mut *self.writer.borrow_mut(),
96                &list[self.next_elem_to_write..=upper],
97            );
98            _ = writeln!(&mut *self.writer.borrow_mut());
99        }
100        self.next_elem_to_write = fb.elem_index + 1;
101        // TeX prints "*" for badness beyond infinite and for artificial
102        // demerits (TeX.2021.856).
103        let b = if fb.badness > crate::INFINITE_BADNESS {
104            "*".into()
105        } else {
106            fb.badness.to_string()
107        };
108        let d = if fb.artificial_demerits {
109            "*".into()
110        } else {
111            fb.demerits.to_string()
112        };
113        _ = writeln!(
114            self.writer.borrow_mut(),
115            "@{} via @@{} b={} p={} d={}",
116            match list.get(fb.elem_index) {
117                None => r"\par",
118                Some(elem) => {
119                    use ds::Horizontal::*;
120                    match elem {
121                        Discretionary(discretionary) => {
122                            self.next_elem_to_write += discretionary.replace_count as usize;
123                            r"\discretionary"
124                        }
125                        _ => "",
126                    }
127                }
128            },
129            fb.previous_node_index,
130            b,
131            fb.penalty,
132            d,
133        );
134    }
135    fn log_new_active_node(&mut self, an: NewActiveNode) {
136        // TeX.2021.846
137        let _ = writeln!(
138            self.writer.borrow_mut(),
139            "@@{}: line {}.{}{} t={} -> @@{}",
140            an.node_index,
141            an.line_number,
142            an.fitness_class,
143            if an.hyphenated { "-" } else { "" },
144            an.total_demerits,
145            an.previous_node_index,
146        );
147    }
148}