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 = 1,
14 Centered,
15 RightJustified,
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 shove: Option<ColumnAlignment>,
114}
115
116impl<'arena> ColumnGenerator<'arena> {
117 pub fn new_predefined(align: Alignment) -> Self {
118 ColumnGenerator {
119 typ: AlignmentType::Predefined(align),
120 column_idx: 0,
121 row_idx: 0,
122 row_border_top: None,
123 shove: None,
124 }
125 }
126
127 pub fn new_custom(array_spec: &'arena ArraySpec<'arena>) -> Self {
128 ColumnGenerator {
129 typ: AlignmentType::Custom(array_spec),
130 column_idx: 0,
131 row_idx: 0,
132 row_border_top: None,
133 shove: None,
134 }
135 }
136
137 pub fn new_multline(num_rows: NonZeroU16, shove: Option<ColumnAlignment>) -> Self {
138 ColumnGenerator {
139 typ: AlignmentType::MultLine(num_rows),
140 column_idx: 0,
141 row_idx: 0,
142 row_border_top: None,
143 shove,
144 }
145 }
146
147 pub fn start_new_row(&mut self, border_top: Option<LineType>, shove: Option<ColumnAlignment>) {
150 self.column_idx = 0;
151 self.row_idx += 1;
152 self.row_border_top = border_top;
153 self.shove = shove;
154 }
155
156 pub fn write_next_mtd(
157 &mut self,
158 s: &mut String,
159 indent_num: usize,
160 indentation: Indentation,
161 ) -> Result<(), core::fmt::Error> {
162 new_line_and_indent(s, indent_num, indentation);
163 let column_idx = self.column_idx;
164 self.column_idx += 1;
165 let border_top = match self.row_border_top {
169 None => "",
170 Some(LineType::Solid) => BORDER_TOP_SOLID,
171 Some(LineType::Dashed) => BORDER_TOP_DASHED,
172 };
173 match self.typ {
174 AlignmentType::Predefined(align) => {
175 let is_even = column_idx.is_multiple_of(2);
176 match align {
177 Alignment::Cases => {
178 write!(
179 s,
180 "{MTD_OPEN_STYLE}{border_top}{LEFT_ALIGN}{PADDING_RIGHT_ZERO}"
181 )?;
182 if !is_even {
183 write!(s, "padding-left:1em;")?;
184 }
185 write!(s, "{MTD_CLOSE_STYLE}")?;
186 }
187 Alignment::Centered => {
188 write_simple_mtd(s, border_top)?;
189 }
190 Alignment::Alternating => {
191 write!(s, "{MTD_OPEN_STYLE}{border_top}")?;
192 if is_even {
193 write!(s, "{RIGHT_ALIGN}{PADDING_RIGHT_ZERO}")?;
194 } else {
195 write!(s, "{LEFT_ALIGN}{PADDING_LEFT_ZERO}")?;
196 }
197 write!(s, "{MTD_CLOSE_STYLE}")?;
198 }
199 }
200 }
201 AlignmentType::Custom(array_spec) => {
202 static DEFAULT_COLUMN_SPEC: ColumnSpecEntry = ColumnSpecEntry::WithContent {
203 alignment: ColumnAlignment::Centered,
204 border_right: None,
205 };
206 let mut column_spec = array_spec
207 .column_spec
208 .get(column_idx)
209 .unwrap_or(&DEFAULT_COLUMN_SPEC);
210 while let ColumnSpecEntry::OnlyLine(line_type) = column_spec {
211 column_spec = array_spec
212 .column_spec
213 .get(self.column_idx)
214 .unwrap_or(&DEFAULT_COLUMN_SPEC);
215 self.column_idx += 1;
216 write!(s, "{MTD_OPEN_STYLE}{border_top}")?;
217 match line_type {
218 LineType::Solid => {
219 write!(s, "{BORDER_RIGHT_SOLID}")?;
220 }
221 LineType::Dashed => {
222 write!(s, "{BORDER_RIGHT_DASHED}")?;
223 }
224 }
225 if array_spec.is_sub {
226 write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
227 }
228 write!(s, "padding-left: 0.1em;padding-right: 0.1em;")?;
229 write!(s, "\"></mtd>")?;
230 new_line_and_indent(s, indent_num, indentation);
231 }
232 match column_spec {
233 ColumnSpecEntry::WithContent {
234 alignment,
235 border_right,
236 } => {
237 if matches!(alignment, ColumnAlignment::Centered)
238 && border_right.is_none()
239 && !array_spec.is_sub
240 {
241 write_simple_mtd(s, border_top)?;
242 return Ok(());
243 }
244 write!(s, "{MTD_OPEN_STYLE}{border_top}")?;
245 match alignment {
246 ColumnAlignment::LeftJustified => {
247 write!(s, "{LEFT_ALIGN}")?;
248 }
249 ColumnAlignment::Centered => {}
250 ColumnAlignment::RightJustified => {
251 write!(s, "{RIGHT_ALIGN}")?;
252 }
253 }
254 match border_right {
255 Some(LineType::Solid) => {
256 write!(s, "{BORDER_RIGHT_SOLID}")?;
257 }
258 Some(LineType::Dashed) => {
259 write!(s, "{BORDER_RIGHT_DASHED}")?;
260 }
261 _ => {}
262 }
263 if array_spec.is_sub {
264 write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
265 }
266 write!(s, "{MTD_CLOSE_STYLE}")?;
267 }
268 ColumnSpecEntry::OnlyLine(_) => {}
269 }
270 }
271 AlignmentType::MultLine(num_rows) => {
272 let align = if let Some(shove) = self.shove {
273 shove
274 } else {
275 let row_idx = self.row_idx;
278 if row_idx == 0 {
279 ColumnAlignment::LeftJustified
280 } else if row_idx + 1 == (num_rows.get() as usize) {
281 ColumnAlignment::RightJustified
282 } else {
283 ColumnAlignment::Centered
284 }
285 };
286 match align {
287 ColumnAlignment::LeftJustified => {
288 write!(s, "{MTD_OPEN_STYLE}{LEFT_ALIGN}{MTD_CLOSE_STYLE}")?
289 }
290 ColumnAlignment::Centered => write_simple_mtd(s, "")?,
291 ColumnAlignment::RightJustified => {
292 write!(s, "{MTD_OPEN_STYLE}{RIGHT_ALIGN}{MTD_CLOSE_STYLE}")?
293 }
294 }
295 }
296 }
297 Ok(())
298 }
299}
300
301fn write_simple_mtd(s: &mut String, border_top: &str) -> core::fmt::Result {
304 if border_top.is_empty() {
305 write!(s, "{SIMPLE_CENTERED}")
306 } else {
307 write!(s, "{MTD_OPEN_STYLE}{border_top}{MTD_CLOSE_STYLE}")
308 }
309}