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
46enum AlignmentType<'arena> {
47    Predefined(Alignment),
48    Custom(&'arena ArraySpec<'arena>),
49    MultLine(NonZeroU16),
50}
51
52const MTD_OPEN_STYLE: &str = "<mtd style=\"";
53const MTD_CLOSE_STYLE: &str = "\">";
54const LEFT_ALIGN: &str = "text-align: left;justify-items: start;";
55pub const RIGHT_ALIGN: &str = "text-align: right;justify-items: end;";
56const PADDING_RIGHT_ZERO: &str = "padding-right: 0;";
57const PADDING_LEFT_ZERO: &str = "padding-left: 0;";
58const PADDING_TOP_BOTTOM_ZERO: &str = "padding-top: 0;padding-bottom: 0;";
59const BORDER_RIGHT_SOLID: &str = "border-right: 0.05em solid currentcolor;";
60const BORDER_RIGHT_DASHED: &str = "border-right: 0.05em dashed currentcolor;";
61const SIMPLE_CENTERED: &str = "<mtd>";
62
63pub struct ColumnGenerator<'arena> {
64    typ: AlignmentType<'arena>,
65    column_idx: usize,
66    row_idx: usize,
67}
68
69impl<'arena> ColumnGenerator<'arena> {
70    pub fn new_predefined(align: Alignment) -> Self {
71        ColumnGenerator {
72            typ: AlignmentType::Predefined(align),
73            column_idx: 0,
74            row_idx: 0,
75        }
76    }
77
78    pub fn new_custom(array_spec: &'arena ArraySpec<'arena>) -> Self {
79        ColumnGenerator {
80            typ: AlignmentType::Custom(array_spec),
81            column_idx: 0,
82            row_idx: 0,
83        }
84    }
85
86    pub fn new_multline(num_rows: NonZeroU16) -> Self {
87        ColumnGenerator {
88            typ: AlignmentType::MultLine(num_rows),
89            column_idx: 0,
90            row_idx: 0,
91        }
92    }
93
94    pub fn reset_to_new_row(&mut self) {
95        self.column_idx = 0;
96        self.row_idx += 1;
97    }
98
99    pub fn write_next_mtd(
100        &mut self,
101        s: &mut String,
102        indent_num: usize,
103    ) -> Result<(), std::fmt::Error> {
104        new_line_and_indent(s, indent_num);
105        let column_idx = self.column_idx;
106        self.column_idx += 1;
107        match self.typ {
108            AlignmentType::Predefined(align) => {
109                let is_even = column_idx.is_multiple_of(2);
110                match align {
111                    Alignment::Cases => {
112                        write!(s, "{MTD_OPEN_STYLE}{LEFT_ALIGN}{PADDING_RIGHT_ZERO}")?;
113                        if !is_even {
114                            write!(s, "padding-left:1em;")?;
115                        }
116                        write!(s, "{MTD_CLOSE_STYLE}")?;
117                    }
118                    Alignment::Centered => {
119                        write!(s, "{SIMPLE_CENTERED}")?;
120                    }
121                    Alignment::Alternating => {
122                        write!(s, "{MTD_OPEN_STYLE}")?;
123                        if is_even {
124                            write!(s, "{RIGHT_ALIGN}{PADDING_RIGHT_ZERO}")?;
125                        } else {
126                            write!(s, "{LEFT_ALIGN}{PADDING_LEFT_ZERO}")?;
127                        }
128                        write!(s, "{MTD_CLOSE_STYLE}")?;
129                    }
130                }
131            }
132            AlignmentType::Custom(array_spec) => {
133                let mut column_spec = array_spec
134                    .column_spec
135                    .get(column_idx)
136                    .unwrap_or(&ColumnSpec::WithContent(ColumnAlignment::Centered, None));
137                while let ColumnSpec::OnlyLine(line_type) = column_spec {
138                    column_spec = array_spec
139                        .column_spec
140                        .get(self.column_idx)
141                        .unwrap_or(&ColumnSpec::WithContent(ColumnAlignment::Centered, None));
142                    self.column_idx += 1;
143                    write!(s, "{MTD_OPEN_STYLE}")?;
144                    match line_type {
145                        LineType::Solid => {
146                            write!(s, "{BORDER_RIGHT_SOLID}")?;
147                        }
148                        LineType::Dashed => {
149                            write!(s, "{BORDER_RIGHT_DASHED}")?;
150                        }
151                    }
152                    if array_spec.is_sub {
153                        write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
154                    }
155                    write!(s, "padding-left: 0.1em;padding-right: 0.1em;")?;
156                    write!(s, "\"></mtd>")?;
157                    new_line_and_indent(s, indent_num);
158                }
159                match column_spec {
160                    ColumnSpec::WithContent(alignment, line_type) => {
161                        if matches!(alignment, ColumnAlignment::Centered)
162                            && line_type.is_none()
163                            && !array_spec.is_sub
164                        {
165                            write!(s, "{SIMPLE_CENTERED}")?;
166                            return Ok(());
167                        }
168                        write!(s, "{MTD_OPEN_STYLE}")?;
169                        match alignment {
170                            ColumnAlignment::LeftJustified => {
171                                write!(s, "{LEFT_ALIGN}")?;
172                            }
173                            ColumnAlignment::Centered => {}
174                            ColumnAlignment::RightJustified => {
175                                write!(s, "{RIGHT_ALIGN}")?;
176                            }
177                        }
178                        match line_type {
179                            Some(LineType::Solid) => {
180                                write!(s, "{BORDER_RIGHT_SOLID}")?;
181                            }
182                            Some(LineType::Dashed) => {
183                                write!(s, "{BORDER_RIGHT_DASHED}")?;
184                            }
185                            _ => {}
186                        }
187                        if array_spec.is_sub {
188                            write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
189                        }
190                        write!(s, "{MTD_CLOSE_STYLE}")?;
191                    }
192                    ColumnSpec::OnlyLine(_) => {}
193                }
194            }
195            AlignmentType::MultLine(num_rows) => {
196                let row_idx = self.row_idx;
197                // Multline is left-aligned for the first row, right-aligned for the last row,
198                // and centered for all other rows.
199                if row_idx == 0 {
200                    write!(s, "{MTD_OPEN_STYLE}{LEFT_ALIGN}{MTD_CLOSE_STYLE}")?;
201                } else if row_idx + 1 == (num_rows.get() as usize) {
202                    write!(s, "{MTD_OPEN_STYLE}{RIGHT_ALIGN}{MTD_CLOSE_STYLE}")?;
203                } else {
204                    write!(s, "{SIMPLE_CENTERED}")?;
205                }
206            }
207        };
208        Ok(())
209    }
210}