Skip to main content

math_core_renderer_internal/
table.rs

1use std::{fmt::Write, num::NonZeroU16};
2
3#[cfg(feature = "serde")]
4use serde::Serialize;
5
6use crate::fmt::new_line_and_indent;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
9#[cfg_attr(feature = "serde", derive(Serialize))]
10pub enum ColumnAlignment {
11    LeftJustified = 0,
12    Centered = 1,
13    RightJustified = 2,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17#[cfg_attr(feature = "serde", derive(Serialize))]
18pub enum LineType {
19    Solid = 3,
20    Dashed = 4,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq)]
24#[cfg_attr(feature = "serde", derive(Serialize))]
25pub enum ColumnSpec {
26    WithContent(ColumnAlignment, Option<LineType>),
27    OnlyLine(LineType),
28}
29
30#[derive(Debug, PartialEq)]
31#[cfg_attr(feature = "serde", derive(Serialize))]
32pub struct ArraySpec<'arena> {
33    pub beginning_line: Option<LineType>,
34    pub is_sub: bool,
35    pub column_spec: &'arena [ColumnSpec],
36}
37
38#[derive(Debug, Clone, Copy, PartialEq)]
39#[cfg_attr(feature = "serde", derive(Serialize))]
40pub enum Alignment {
41    Centered,
42    Cases,
43    Alternating,
44}
45
46/// Equation-number / label metadata for the last row of a numbered environment.
47///
48/// The last row of `align`, `gather`, `equation`, `multline`, etc. has no trailing
49/// `\\` row separator, so its tag (equation number) and link target (from `\label`)
50/// can't ride on a `Node::RowSeparator`. They're arena-allocated and threaded
51/// through `Node::EquationArray` / `Node::MultLine` instead.
52#[derive(Debug)]
53#[cfg_attr(feature = "serde", derive(Serialize))]
54pub struct RowLabelInfo<'arena> {
55    pub tag: Option<NonZeroU16>,
56    pub link_target: Option<&'arena str>,
57}
58
59enum AlignmentType<'arena> {
60    Predefined(Alignment),
61    Custom(&'arena ArraySpec<'arena>),
62    MultLine(NonZeroU16),
63}
64
65const MTD_OPEN_STYLE: &str = "<mtd style=\"";
66const MTD_CLOSE_STYLE: &str = "\">";
67const LEFT_ALIGN: &str = "text-align: left;justify-items: start;";
68pub const RIGHT_ALIGN: &str = "text-align: right;justify-items: end;";
69const PADDING_RIGHT_ZERO: &str = "padding-right: 0;";
70const PADDING_LEFT_ZERO: &str = "padding-left: 0;";
71const PADDING_TOP_BOTTOM_ZERO: &str = "padding-top: 0;padding-bottom: 0;";
72const BORDER_RIGHT_SOLID: &str = "border-right: 0.05em solid currentcolor;";
73const BORDER_RIGHT_DASHED: &str = "border-right: 0.05em dashed currentcolor;";
74const SIMPLE_CENTERED: &str = "<mtd>";
75
76pub struct ColumnGenerator<'arena> {
77    typ: AlignmentType<'arena>,
78    column_idx: usize,
79    row_idx: usize,
80}
81
82impl<'arena> ColumnGenerator<'arena> {
83    pub fn new_predefined(align: Alignment) -> Self {
84        ColumnGenerator {
85            typ: AlignmentType::Predefined(align),
86            column_idx: 0,
87            row_idx: 0,
88        }
89    }
90
91    pub fn new_custom(array_spec: &'arena ArraySpec<'arena>) -> Self {
92        ColumnGenerator {
93            typ: AlignmentType::Custom(array_spec),
94            column_idx: 0,
95            row_idx: 0,
96        }
97    }
98
99    pub fn new_multline(num_rows: NonZeroU16) -> Self {
100        ColumnGenerator {
101            typ: AlignmentType::MultLine(num_rows),
102            column_idx: 0,
103            row_idx: 0,
104        }
105    }
106
107    pub fn reset_to_new_row(&mut self) {
108        self.column_idx = 0;
109        self.row_idx += 1;
110    }
111
112    pub fn write_next_mtd(
113        &mut self,
114        s: &mut String,
115        indent_num: usize,
116    ) -> Result<(), std::fmt::Error> {
117        new_line_and_indent(s, indent_num);
118        let column_idx = self.column_idx;
119        self.column_idx += 1;
120        match self.typ {
121            AlignmentType::Predefined(align) => {
122                let is_even = column_idx.is_multiple_of(2);
123                match align {
124                    Alignment::Cases => {
125                        write!(s, "{MTD_OPEN_STYLE}{LEFT_ALIGN}{PADDING_RIGHT_ZERO}")?;
126                        if !is_even {
127                            write!(s, "padding-left:1em;")?;
128                        }
129                        write!(s, "{MTD_CLOSE_STYLE}")?;
130                    }
131                    Alignment::Centered => {
132                        write!(s, "{SIMPLE_CENTERED}")?;
133                    }
134                    Alignment::Alternating => {
135                        write!(s, "{MTD_OPEN_STYLE}")?;
136                        if is_even {
137                            write!(s, "{RIGHT_ALIGN}{PADDING_RIGHT_ZERO}")?;
138                        } else {
139                            write!(s, "{LEFT_ALIGN}{PADDING_LEFT_ZERO}")?;
140                        }
141                        write!(s, "{MTD_CLOSE_STYLE}")?;
142                    }
143                }
144            }
145            AlignmentType::Custom(array_spec) => {
146                let mut column_spec = array_spec
147                    .column_spec
148                    .get(column_idx)
149                    .unwrap_or(&ColumnSpec::WithContent(ColumnAlignment::Centered, None));
150                while let ColumnSpec::OnlyLine(line_type) = column_spec {
151                    column_spec = array_spec
152                        .column_spec
153                        .get(self.column_idx)
154                        .unwrap_or(&ColumnSpec::WithContent(ColumnAlignment::Centered, None));
155                    self.column_idx += 1;
156                    write!(s, "{MTD_OPEN_STYLE}")?;
157                    match line_type {
158                        LineType::Solid => {
159                            write!(s, "{BORDER_RIGHT_SOLID}")?;
160                        }
161                        LineType::Dashed => {
162                            write!(s, "{BORDER_RIGHT_DASHED}")?;
163                        }
164                    }
165                    if array_spec.is_sub {
166                        write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
167                    }
168                    write!(s, "padding-left: 0.1em;padding-right: 0.1em;")?;
169                    write!(s, "\"></mtd>")?;
170                    new_line_and_indent(s, indent_num);
171                }
172                match column_spec {
173                    ColumnSpec::WithContent(alignment, line_type) => {
174                        if matches!(alignment, ColumnAlignment::Centered)
175                            && line_type.is_none()
176                            && !array_spec.is_sub
177                        {
178                            write!(s, "{SIMPLE_CENTERED}")?;
179                            return Ok(());
180                        }
181                        write!(s, "{MTD_OPEN_STYLE}")?;
182                        match alignment {
183                            ColumnAlignment::LeftJustified => {
184                                write!(s, "{LEFT_ALIGN}")?;
185                            }
186                            ColumnAlignment::Centered => {}
187                            ColumnAlignment::RightJustified => {
188                                write!(s, "{RIGHT_ALIGN}")?;
189                            }
190                        }
191                        match line_type {
192                            Some(LineType::Solid) => {
193                                write!(s, "{BORDER_RIGHT_SOLID}")?;
194                            }
195                            Some(LineType::Dashed) => {
196                                write!(s, "{BORDER_RIGHT_DASHED}")?;
197                            }
198                            _ => {}
199                        }
200                        if array_spec.is_sub {
201                            write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
202                        }
203                        write!(s, "{MTD_CLOSE_STYLE}")?;
204                    }
205                    ColumnSpec::OnlyLine(_) => {}
206                }
207            }
208            AlignmentType::MultLine(num_rows) => {
209                let row_idx = self.row_idx;
210                // Multline is left-aligned for the first row, right-aligned for the last row,
211                // and centered for all other rows.
212                if row_idx == 0 {
213                    write!(s, "{MTD_OPEN_STYLE}{LEFT_ALIGN}{MTD_CLOSE_STYLE}")?;
214                } else if row_idx + 1 == (num_rows.get() as usize) {
215                    write!(s, "{MTD_OPEN_STYLE}{RIGHT_ALIGN}{MTD_CLOSE_STYLE}")?;
216                } else {
217                    write!(s, "{SIMPLE_CENTERED}")?;
218                }
219            }
220        }
221        Ok(())
222    }
223}