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