Skip to main content

latex_rust/layout/
numbering.rs

1//! Equation counters, `\tag`, and `\label` / `\ref` (two-pass).
2
3use std::collections::HashMap;
4
5use crate::parser::{EnvRow, EqNumber, MathNode, MatrixStyle};
6
7/// How auto equation numbers are written.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum NumberStyle {
10    /// `(1)`, `(2)`, …
11    Arabic,
12    /// `(i)`, `(ii)`, …
13    Roman,
14    /// `(a)`, `(b)`, …
15    Alphabetic,
16}
17
18/// Wrapper around the number body.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum NumberFormat {
21    /// `(1)`
22    Parenthesized,
23    /// `[1]`
24    Bracketed,
25    /// `1`
26    Plain,
27}
28
29/// Counter style for a render (or a sequence of [`layout_with_numbering`](super::layout_with_numbering) calls).
30///
31/// # Examples
32///
33/// ```
34/// use latex_rust::{NumberFormat, NumberStyle, NumberingConfig};
35///
36/// let cfg = NumberingConfig::new();
37/// assert_eq!(cfg.style, NumberStyle::Arabic);
38/// assert_eq!(cfg.start, 1);
39/// assert_eq!(cfg.format, NumberFormat::Parenthesized);
40/// ```
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct NumberingConfig {
43    /// Digit / roman / letter.
44    pub style: NumberStyle,
45    /// First auto number (usually 1).
46    pub start: usize,
47    /// Parentheses, brackets, or none.
48    pub format: NumberFormat,
49}
50
51impl Default for NumberingConfig {
52    fn default() -> Self {
53        Self {
54            style: NumberStyle::Arabic,
55            start: 1,
56            format: NumberFormat::Parenthesized,
57        }
58    }
59}
60
61impl NumberingConfig {
62    /// Arabic, start at 1, parenthesized.
63    #[must_use]
64    pub fn new() -> Self {
65        Self::default()
66    }
67}
68
69/// Mutable numbering / label table. Survives across `layout_with_numbering` calls.
70///
71/// # Examples
72///
73/// ```
74/// use latex_rust::{NumberingConfig, NumberingState};
75///
76/// let state = NumberingState::new(NumberingConfig::new());
77/// assert!(state.label("eq:1").is_none());
78/// ```
79#[derive(Clone, Debug)]
80pub struct NumberingState {
81    config: NumberingConfig,
82    next: usize,
83    labels: HashMap<String, String>,
84    assigned: Vec<Option<String>>,
85}
86
87impl Default for NumberingState {
88    fn default() -> Self {
89        Self::new(NumberingConfig::default())
90    }
91}
92
93impl NumberingState {
94    /// Counter starts at `config.start`.
95    #[must_use]
96    pub fn new(config: NumberingConfig) -> Self {
97        let next = config.start;
98        Self {
99            config,
100            next,
101            labels: HashMap::new(),
102            assigned: Vec::new(),
103        }
104    }
105
106    /// Formatted number bound to `key`, if `\label{key}` was seen.
107    #[must_use]
108    pub fn label(&self, key: &str) -> Option<&str> {
109        self.labels.get(key).map(String::as_str)
110    }
111
112    pub(crate) fn lookup(&self, key: &str) -> Option<&str> {
113        self.label(key)
114    }
115
116    pub(crate) fn assigned(&self, i: usize) -> Option<&str> {
117        self.assigned.get(i).and_then(|o| o.as_deref())
118    }
119
120    /// Walk `node`, assign numbers, fill labels. Returns the index of the first
121    /// new assignment (for this tree).
122    pub fn collect(&mut self, node: &MathNode) -> usize {
123        let start = self.assigned.len();
124        collect_node(node, self);
125        start
126    }
127
128    fn wrap(&self, body: &str) -> String {
129        match self.config.format {
130            NumberFormat::Parenthesized => format!("({body})"),
131            NumberFormat::Bracketed => format!("[{body}]"),
132            NumberFormat::Plain => body.to_string(),
133        }
134    }
135
136    fn auto_body(&self, n: usize) -> String {
137        match self.config.style {
138            NumberStyle::Arabic => n.to_string(),
139            NumberStyle::Roman => to_roman(n),
140            NumberStyle::Alphabetic => to_alpha(n),
141        }
142    }
143
144    fn auto_display(&mut self) -> String {
145        let n = self.next;
146        self.next = self.next.saturating_add(1);
147        self.wrap(&self.auto_body(n))
148    }
149
150    fn bind(&mut self, labels: &[String], display: &str) {
151        for k in labels {
152            self.labels.insert(k.clone(), display.to_string());
153        }
154    }
155}
156
157fn collect_node(node: &MathNode, st: &mut NumberingState) {
158    match node {
159        MathNode::Matrix(style, _, rows) => collect_matrix(*style, rows, st),
160        MathNode::Row(v) | MathNode::Substack(v) => {
161            for n in v {
162                collect_node(n, st);
163            }
164        }
165        MathNode::Fraction(a, b)
166        | MathNode::Superscript(a, b)
167        | MathNode::Subscript(a, b)
168        | MathNode::CancelTo(a, b) => {
169            collect_node(a, st);
170            collect_node(b, st);
171        }
172        MathNode::SubSup(a, b, c) => {
173            collect_node(a, st);
174            collect_node(b, st);
175            collect_node(c, st);
176        }
177        MathNode::Radical(deg, r) => {
178            if let Some(d) = deg {
179                collect_node(d, st);
180            }
181            collect_node(r, st);
182        }
183        MathNode::Delimited(_, b, _)
184        | MathNode::Accent(b, _)
185        | MathNode::Color(_, b)
186        | MathNode::TextColor(_, b)
187        | MathNode::ColorBox(_, b)
188        | MathNode::Phantom(_, b)
189        | MathNode::Intertext(b)
190        | MathNode::Tag { body: b, .. } => collect_node(b, st),
191        MathNode::FColorBox(_, _, b) => collect_node(b, st),
192        MathNode::Sum(lo, hi) | MathNode::Product(lo, hi) | MathNode::Integral(_, lo, hi) => {
193            if let Some(n) = lo {
194                collect_node(n, st);
195            }
196            if let Some(n) = hi {
197                collect_node(n, st);
198            }
199        }
200        MathNode::Limit(lo) => {
201            if let Some(n) = lo {
202                collect_node(n, st);
203            }
204        }
205        MathNode::OverUnder(b, over, under) => {
206            collect_node(b, st);
207            if let Some(n) = over {
208                collect_node(n, st);
209            }
210            if let Some(n) = under {
211                collect_node(n, st);
212            }
213        }
214        MathNode::Atom(_, _)
215        | MathNode::SizedDelim(_, _, _)
216        | MathNode::Text(_, _)
217        | MathNode::Space(_)
218        | MathNode::Operator(_, _)
219        | MathNode::Symbol(_)
220        | MathNode::Strut(_, _)
221        | MathNode::Ref(_)
222        | MathNode::Label(_)
223        | MathNode::NoNumber
224        | MathNode::Hline => {}
225    }
226}
227
228fn collect_matrix(style: MatrixStyle, rows: &[EnvRow], st: &mut NumberingState) {
229    for row in rows {
230        match row {
231            EnvRow::Cells { cells, .. } => {
232                for c in cells {
233                    collect_node(c, st);
234                }
235            }
236            EnvRow::Intertext(n) => collect_node(n, st),
237            EnvRow::Hline => {}
238        }
239    }
240    if style.numbers_rows() {
241        for row in rows {
242            match row {
243                EnvRow::Intertext(_) | EnvRow::Hline => {}
244                EnvRow::Cells { number, labels, .. } => {
245                    let display = assign(number, st);
246                    if let Some(d) = &display {
247                        st.bind(labels, d);
248                    }
249                    st.assigned.push(display);
250                }
251            }
252        }
253    } else if style.numbers_once() {
254        let mut number = EqNumber::Default;
255        let mut labels = Vec::new();
256        for row in rows {
257            if let EnvRow::Cells {
258                number: n,
259                labels: l,
260                ..
261            } = row
262            {
263                match n {
264                    EqNumber::Default => {}
265                    other => number = other.clone(),
266                }
267                labels.extend(l.iter().cloned());
268            }
269        }
270        let display = assign(&number, st);
271        if let Some(d) = &display {
272            st.bind(&labels, d);
273        }
274        st.assigned.push(display);
275    }
276}
277
278fn assign(number: &EqNumber, st: &mut NumberingState) -> Option<String> {
279    match number {
280        EqNumber::Suppress => None,
281        EqNumber::Tag { star, body } => {
282            let plain = node_plain(body);
283            let display = if *star { plain } else { st.wrap(&plain) };
284            Some(display)
285        }
286        EqNumber::Default => Some(st.auto_display()),
287    }
288}
289
290fn node_plain(n: &MathNode) -> String {
291    match n {
292        MathNode::Atom(c, _) => c.to_string(),
293        MathNode::Text(s, _) => s.clone(),
294        MathNode::Symbol(name) => name.clone(),
295        MathNode::Row(v) => v.iter().map(node_plain).collect(),
296        MathNode::Tag { body, .. } => node_plain(body),
297        other => other.gold(),
298    }
299}
300
301fn to_roman(mut n: usize) -> String {
302    if n == 0 {
303        return "0".into();
304    }
305    let pairs: [(usize, &str); 13] = [
306        (1000, "m"),
307        (900, "cm"),
308        (500, "d"),
309        (400, "cd"),
310        (100, "c"),
311        (90, "xc"),
312        (50, "l"),
313        (40, "xl"),
314        (10, "x"),
315        (9, "ix"),
316        (5, "v"),
317        (4, "iv"),
318        (1, "i"),
319    ];
320    let mut s = String::new();
321    for (v, g) in pairs {
322        while n >= v {
323            s.push_str(g);
324            n -= v;
325        }
326    }
327    s
328}
329
330fn to_alpha(mut n: usize) -> String {
331    if n == 0 {
332        return "0".into();
333    }
334    let mut s = String::new();
335    while n > 0 {
336        n -= 1;
337        s.insert(0, char::from(b'a' + (n % 26) as u8));
338        n /= 26;
339    }
340    s
341}