math_core_renderer_internal/
table.rs1use alloc::string::String;
2use core::{fmt::Write, num::NonZeroU16};
3
4#[cfg(feature = "serde")]
5use serde::Serialize;
6
7use crate::ast::Indentation;
8use crate::fmt::new_line_and_indent;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
11#[cfg_attr(feature = "serde", derive(Serialize))]
12pub enum ColumnAlignment {
13 LeftJustified = 0,
14 Centered = 1,
15 RightJustified = 2,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq)]
19#[cfg_attr(feature = "serde", derive(Serialize))]
20pub enum LineType {
21 Solid = 3,
22 Dashed = 4,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
29#[cfg_attr(feature = "serde", derive(Serialize))]
30pub enum ColumnSpecEntry {
31 WithContent {
32 alignment: ColumnAlignment,
33 border_right: Option<LineType>,
34 },
35 OnlyLine(LineType),
36}
37
38pub type ColumnSpec<'arena> = &'arena [ColumnSpecEntry];
39
40#[derive(Debug, PartialEq)]
41#[cfg_attr(feature = "serde", derive(Serialize))]
42pub struct ArraySpec<'arena> {
43 pub border_left: Option<LineType>,
46 pub border_top: Option<LineType>,
49 pub is_sub: bool,
52 pub column_spec: ColumnSpec<'arena>,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq)]
56#[cfg_attr(feature = "serde", derive(Serialize))]
57pub enum Alignment {
58 Centered,
59 Cases,
60 Alternating,
61}
62
63#[derive(Debug)]
70#[cfg_attr(feature = "serde", derive(Serialize))]
71pub struct RowLabelInfo<'arena> {
72 pub tag: EquationTag<'arena>,
73 pub link_target: Option<&'arena str>,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq)]
78#[cfg_attr(feature = "serde", derive(Serialize))]
79pub struct EquationTag<'arena> {
80 pub text: &'arena str,
81 pub parenthesized: bool,
84}
85
86enum AlignmentType<'arena> {
87 Predefined(Alignment),
88 Custom(&'arena ArraySpec<'arena>),
89 MultLine(NonZeroU16),
90}
91
92const MTD_OPEN_STYLE: &str = "<mtd style=\"";
93const MTD_CLOSE_STYLE: &str = "\">";
94const LEFT_ALIGN: &str = "text-align: left;justify-items: start;";
95pub const RIGHT_ALIGN: &str = "text-align: right;justify-items: end;";
96const PADDING_RIGHT_ZERO: &str = "padding-right: 0;";
97const PADDING_LEFT_ZERO: &str = "padding-left: 0;";
98const PADDING_TOP_BOTTOM_ZERO: &str = "padding-top: 0;padding-bottom: 0;";
99const BORDER_RIGHT_SOLID: &str = "border-right: 0.05em solid currentcolor;";
100const BORDER_RIGHT_DASHED: &str = "border-right: 0.05em dashed currentcolor;";
101pub const BORDER_TOP_SOLID: &str = "border-top: 0.05em solid currentcolor;";
102pub const BORDER_TOP_DASHED: &str = "border-top: 0.05em dashed currentcolor;";
103const SIMPLE_CENTERED: &str = "<mtd>";
104
105pub struct ColumnGenerator<'arena> {
106 typ: AlignmentType<'arena>,
107 column_idx: usize,
108 row_idx: usize,
109 row_border_top: Option<LineType>,
113}
114
115impl<'arena> ColumnGenerator<'arena> {
116 pub fn new_predefined(align: Alignment) -> Self {
117 ColumnGenerator {
118 typ: AlignmentType::Predefined(align),
119 column_idx: 0,
120 row_idx: 0,
121 row_border_top: None,
122 }
123 }
124
125 pub fn new_custom(array_spec: &'arena ArraySpec<'arena>) -> Self {
126 ColumnGenerator {
127 typ: AlignmentType::Custom(array_spec),
128 column_idx: 0,
129 row_idx: 0,
130 row_border_top: None,
131 }
132 }
133
134 pub fn new_multline(num_rows: NonZeroU16) -> Self {
135 ColumnGenerator {
136 typ: AlignmentType::MultLine(num_rows),
137 column_idx: 0,
138 row_idx: 0,
139 row_border_top: None,
140 }
141 }
142
143 pub fn reset_to_new_row(&mut self) {
144 self.column_idx = 0;
145 self.row_idx += 1;
146 }
147
148 pub fn set_row_border_top(&mut self, border_top: Option<LineType>) {
150 self.row_border_top = border_top;
151 }
152
153 pub fn write_next_mtd(
154 &mut self,
155 s: &mut String,
156 indent_num: usize,
157 indentation: Indentation,
158 ) -> Result<(), core::fmt::Error> {
159 new_line_and_indent(s, indent_num, indentation);
160 let column_idx = self.column_idx;
161 self.column_idx += 1;
162 let border_top = match self.row_border_top {
166 None => "",
167 Some(LineType::Solid) => BORDER_TOP_SOLID,
168 Some(LineType::Dashed) => BORDER_TOP_DASHED,
169 };
170 match self.typ {
171 AlignmentType::Predefined(align) => {
172 let is_even = column_idx.is_multiple_of(2);
173 match align {
174 Alignment::Cases => {
175 write!(
176 s,
177 "{MTD_OPEN_STYLE}{border_top}{LEFT_ALIGN}{PADDING_RIGHT_ZERO}"
178 )?;
179 if !is_even {
180 write!(s, "padding-left:1em;")?;
181 }
182 write!(s, "{MTD_CLOSE_STYLE}")?;
183 }
184 Alignment::Centered => {
185 write_simple_mtd(s, border_top)?;
186 }
187 Alignment::Alternating => {
188 write!(s, "{MTD_OPEN_STYLE}{border_top}")?;
189 if is_even {
190 write!(s, "{RIGHT_ALIGN}{PADDING_RIGHT_ZERO}")?;
191 } else {
192 write!(s, "{LEFT_ALIGN}{PADDING_LEFT_ZERO}")?;
193 }
194 write!(s, "{MTD_CLOSE_STYLE}")?;
195 }
196 }
197 }
198 AlignmentType::Custom(array_spec) => {
199 static DEFAULT_COLUMN_SPEC: ColumnSpecEntry = ColumnSpecEntry::WithContent {
200 alignment: ColumnAlignment::Centered,
201 border_right: None,
202 };
203 let mut column_spec = array_spec
204 .column_spec
205 .get(column_idx)
206 .unwrap_or(&DEFAULT_COLUMN_SPEC);
207 while let ColumnSpecEntry::OnlyLine(line_type) = column_spec {
208 column_spec = array_spec
209 .column_spec
210 .get(self.column_idx)
211 .unwrap_or(&DEFAULT_COLUMN_SPEC);
212 self.column_idx += 1;
213 write!(s, "{MTD_OPEN_STYLE}{border_top}")?;
214 match line_type {
215 LineType::Solid => {
216 write!(s, "{BORDER_RIGHT_SOLID}")?;
217 }
218 LineType::Dashed => {
219 write!(s, "{BORDER_RIGHT_DASHED}")?;
220 }
221 }
222 if array_spec.is_sub {
223 write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
224 }
225 write!(s, "padding-left: 0.1em;padding-right: 0.1em;")?;
226 write!(s, "\"></mtd>")?;
227 new_line_and_indent(s, indent_num, indentation);
228 }
229 match column_spec {
230 ColumnSpecEntry::WithContent {
231 alignment,
232 border_right,
233 } => {
234 if matches!(alignment, ColumnAlignment::Centered)
235 && border_right.is_none()
236 && !array_spec.is_sub
237 {
238 write_simple_mtd(s, border_top)?;
239 return Ok(());
240 }
241 write!(s, "{MTD_OPEN_STYLE}{border_top}")?;
242 match alignment {
243 ColumnAlignment::LeftJustified => {
244 write!(s, "{LEFT_ALIGN}")?;
245 }
246 ColumnAlignment::Centered => {}
247 ColumnAlignment::RightJustified => {
248 write!(s, "{RIGHT_ALIGN}")?;
249 }
250 }
251 match border_right {
252 Some(LineType::Solid) => {
253 write!(s, "{BORDER_RIGHT_SOLID}")?;
254 }
255 Some(LineType::Dashed) => {
256 write!(s, "{BORDER_RIGHT_DASHED}")?;
257 }
258 _ => {}
259 }
260 if array_spec.is_sub {
261 write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
262 }
263 write!(s, "{MTD_CLOSE_STYLE}")?;
264 }
265 ColumnSpecEntry::OnlyLine(_) => {}
266 }
267 }
268 AlignmentType::MultLine(num_rows) => {
269 let row_idx = self.row_idx;
270 if row_idx == 0 {
273 write!(
274 s,
275 "{MTD_OPEN_STYLE}{border_top}{LEFT_ALIGN}{MTD_CLOSE_STYLE}"
276 )?;
277 } else if row_idx + 1 == (num_rows.get() as usize) {
278 write!(
279 s,
280 "{MTD_OPEN_STYLE}{border_top}{RIGHT_ALIGN}{MTD_CLOSE_STYLE}"
281 )?;
282 } else {
283 write_simple_mtd(s, border_top)?;
284 }
285 }
286 }
287 Ok(())
288 }
289}
290
291fn write_simple_mtd(s: &mut String, border_top: &str) -> core::fmt::Result {
294 if border_top.is_empty() {
295 write!(s, "{SIMPLE_CENTERED}")
296 } else {
297 write!(s, "{MTD_OPEN_STYLE}{border_top}{MTD_CLOSE_STYLE}")
298 }
299}