1use std::{
3 collections::VecDeque,
4 fmt::{self, Debug, Display},
5};
6
7use rustc_hash::FxHashMap as HashMap;
8
9use super::cell::Cell;
10use crate::{Digested, common::dimension::Dimension, state::Stored, token::Token, tokens::Tokens};
11
12pub type Row = Template;
14#[derive(Debug, Clone, Default, PartialEq)]
15pub enum Align {
16 #[default]
17 Left,
18 Center,
19 Right,
20 Justify,
21 Char(String),
23}
24impl Align {
25 pub fn char_code(&self) -> char {
26 match self {
27 Align::Right => 'r',
28 Align::Left => 'l',
29 Align::Center => 'c',
30 Align::Justify => 'p',
31 Align::Char(_) => 'c', }
33 }
34 pub fn name(&self) -> String {
35 match self {
36 Align::Right => "right".to_string(),
37 Align::Left => "left".to_string(),
38 Align::Center => "center".to_string(),
39 Align::Justify => "justify".to_string(),
40 Align::Char(ch) => format!("char:{ch}"),
41 }
42 }
43}
44impl From<char> for Align {
45 fn from(c: char) -> Align {
46 match c {
47 'l' => Align::Left,
48 'r' => Align::Right,
49 'c' => Align::Center,
50 'p' => Align::Justify,
51 _ => Align::default(), }
53 }
54}
55
56#[derive(Debug, Copy, Clone, PartialEq)]
58pub enum Axis {
59 Column,
60 Row,
61}
62impl Axis {
63 pub fn name(&self) -> &'static str {
65 match self {
66 Axis::Column => "column",
67 Axis::Row => "row",
68 }
69 }
70 pub fn marker_name(&self) -> &'static str {
72 match self {
73 Axis::Column => "row",
74 Axis::Row => "column",
75 }
76 }
77}
78
79#[derive(Debug, Copy, Clone, PartialEq)]
80pub enum ColumnSpec {
81 Integer, Empty, Unknown, Text, MultiText, Math, MathAltText, D, Graphics, }
92impl ColumnSpec {
93 pub fn difference_heuristic(&self, other: &ColumnSpec) -> f64 {
95 use ColumnSpec::*;
96 match self {
97 Empty => match other {
98 Empty => 0.0,
99 Math => 0.05,
100 Integer => 0.05,
101 Text => 0.05,
102 Unknown => 0.05,
103 MathAltText => 0.05,
104 _ => 0.75,
105 },
106 Math => match other {
107 Empty => 0.05,
108 Math => 0.0,
109 Integer => 0.1,
110 MathAltText => 0.2,
111 _ => 0.75,
112 },
113 Integer => match other {
114 Empty => 0.05,
115 Math => 0.1,
116 Integer => 0.0,
117 MathAltText => 0.2,
118 _ => 0.75,
119 },
120 Text => match other {
121 Empty => 0.05,
122 Text => 0.0,
123 MathAltText => 0.2,
124 _ => 0.75, },
126 MultiText => match other {
127 Empty => 0.05,
128 MultiText => 0.0,
129 MathAltText => 0.2,
130 _ => 0.75, },
132 Unknown => match other {
133 Empty => 0.05,
134 Unknown => 0.0,
135 MathAltText => 0.2,
136 _ => 0.75,
137 },
138 MathAltText => match other {
139 Empty => 0.05,
140 Math => 0.2,
141 Integer => 0.2,
142 Text => 0.2,
143 Unknown => 0.2,
144 MathAltText => 0.0,
145 _ => 0.75,
146 },
147 D => match other {
148 D => 0.0,
149 _ => 0.75,
150 },
151 Graphics => match other {
152 Graphics => 0.0,
153 _ => 0.75,
154 },
155 }
156 }
157}
158impl Display for ColumnSpec {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 match self {
161 ColumnSpec::Integer => write!(f, "i"),
162 ColumnSpec::Empty => write!(f, "_"),
163 ColumnSpec::Unknown => write!(f, "?"),
164 ColumnSpec::Text => write!(f, "t"),
165 ColumnSpec::MultiText => write!(f, "tt"),
166 ColumnSpec::Math => write!(f, "m"),
167 ColumnSpec::MathAltText => write!(f, "mx"),
168 ColumnSpec::D => write!(f, "d"),
169 ColumnSpec::Graphics => write!(f, "g"),
170 }
171 }
172}
173
174#[derive(Debug, Copy, Clone, PartialEq)]
175pub enum BorderSpec {
176 Top,
177 Bottom,
178 Left,
179 Right,
180}
181
182#[derive(Debug, Clone, Default)]
183pub struct TemplateConfig {
184 pub repeating: Option<bool>,
185 pub pseudorow: Option<bool>,
186 pub non_repeating: usize,
187 pub repeated: Vec<Cell>,
188 pub reversion: Option<Tokens>,
189 pub columns: Option<Vec<Cell>>,
190 pub tokens: Option<Vec<Token>>,
191 pub save_before: Option<VecDeque<Token>>,
192 pub save_between: Option<VecDeque<Token>>,
193}
194
195#[derive(Debug, Clone, Default, PartialEq)]
196pub struct Template {
197 repeating: bool,
198 pseudorow: bool,
199 non_repeating: usize,
200 repeated: Vec<Cell>,
201 reversion: Option<Tokens>,
202 columns: Vec<Cell>,
203 pub tokens: Vec<Token>,
204 padding: Option<Dimension>,
205 pub top_padding: Option<Dimension>,
206 pub bottom_padding: Option<Dimension>,
207 pub before: VecDeque<Digested>,
208 pub after: VecDeque<Digested>,
209 save_before: VecDeque<Token>,
210 save_between: VecDeque<Token>,
211 disabled_intercolumn: bool,
212 pub cached_width: Option<Dimension>,
213 pub cached_height: Option<Dimension>,
214 pub cached_depth: Option<Dimension>,
215 pub x: Option<Dimension>,
216 pub y: Option<Dimension>,
217 pub properties: HashMap<String, Stored>,
221}
222
223impl Display for Template {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Alignment[]",) }
225}
226impl Template {
227 pub fn new(config: TemplateConfig) -> Self {
228 let repeating = config.repeating.unwrap_or(false) || !config.repeated.is_empty();
229 let pseudorow = config.pseudorow.unwrap_or(false);
230 let mut columns = config.columns.unwrap_or_default();
231 let mut repeated = config.repeated;
232 let non_repeating = columns.len();
233 let save_before = config.save_before.unwrap_or_default();
234 let save_between = config.save_between.unwrap_or_default(); for column in columns.iter_mut() {
236 column.empty = true;
237 }
238 for v in repeated.iter_mut() {
239 v.empty = true;
240 }
241
242 Template {
243 columns,
244 pseudorow,
245 repeating,
246 repeated,
247 non_repeating,
248 save_before,
249 save_between,
250 disabled_intercolumn: false,
251 before: VecDeque::new(),
252 after: VecDeque::new(),
253 padding: None,
254 top_padding: None,
255 bottom_padding: None,
256 cached_width: None,
257 cached_height: None,
258 cached_depth: None,
259 x: None,
260 y: None,
261 reversion: config.reversion,
262 tokens: config.tokens.unwrap_or_default(),
263 properties: rustc_hash::FxHashMap::default(),
264 }
265 }
266 pub fn set_reversion(&mut self, tks: Tokens) { self.reversion = Some(tks); }
267 pub fn set_repeating(&mut self) { self.repeating = true; }
268 pub fn set_padding(&mut self, d: Dimension) { self.padding = Some(d); }
269 pub fn get_padding(&self) -> Option<&Dimension> { self.padding.as_ref() }
270
271 pub fn disable_intercolumn(&mut self) {
275 if !self.columns.is_empty() || !self.repeated.is_empty() {
276 self.disabled_intercolumn = true;
277 }
278 }
279
280 pub fn finish(&mut self) {
283 let last = if self.repeating {
284 self.repeated.last_mut()
285 } else {
286 self.columns.last_mut()
287 };
288 if let Some(prev) = last
289 && !self.disabled_intercolumn
290 {
291 let mut after = prev.after.take().unwrap_or_default().unlist();
295 after.push(T_CS!("\\lx@intercol"));
296 prev.after = Some(Tokens::new(after));
297 prev.has_intercol_after = true;
298 }
299 }
300
301 pub fn add_before_column(&mut self, mut new: VecDeque<Token>) {
303 let current_sb = self.save_before.drain(..);
304 new.extend(current_sb);
305 self.save_before = new; }
307 pub fn add_after_column(&mut self, new: Vec<Token>) {
310 if let Some(current_column) = self.columns.last_mut() {
311 let current_after = current_column.after.take().unwrap_or_default().unlist();
312 current_column.after = Some(Tokens!(T_CS!("\\lx@column@trimright"), new, current_after));
313 }
314 }
315
316 pub fn add_between_column(&mut self, tokens: Vec<Token>) {
318 if let Some(current_column) = self.columns.last_mut() {
319 let mut combined = Vec::new();
320 let current_after = current_column.after.take().unwrap_or_default().unlist();
321 combined.extend(current_after);
322 if !self.disabled_intercolumn {
324 combined.push(T_CS!("\\lx@intercol"));
325 }
326 combined.extend(tokens);
327 current_column.after = Some(Tokens::new(combined));
328 } else {
329 self.save_between.extend(tokens);
330 }
331 }
332
333 pub fn add_column(&mut self, mut col: Cell) {
335 if let Some(prev) = if self.repeating {
337 self.repeated.last_mut()
338 } else {
339 self.columns.last_mut()
340 } && !self.disabled_intercolumn
341 {
342 let mut after = prev.after.take().unwrap_or_default().unlist();
343 after.push(T_CS!("\\lx@intercol"));
344 prev.after = Some(Tokens::new(after));
345 prev.has_intercol_after = true;
346 }
347 let mut before = Vec::new();
349 if !self.save_between.is_empty() {
350 before.extend(self.save_between.clone());
351 }
352 let has_intercol_before = !self.disabled_intercolumn;
354 if has_intercol_before {
355 before.push(T_CS!("\\lx@intercol"));
356 }
357 self.disabled_intercolumn = false;
359
360 if let Some(prop_before) = col.before {
361 before.extend(prop_before.unlist());
362 }
363 if !self.save_before.is_empty() {
364 before.extend(self.save_before.clone());
365 }
366 col.before = if !before.is_empty() {
367 Some(Tokens::new(before))
368 } else {
369 None
370 };
371 col.has_intercol_before = has_intercol_before;
372 let mut after = vec![T_CS!("\\lx@column@trimright")];
373 if let Some(prop_after) = col.after {
374 after.extend(prop_after.unlist());
375 }
376 col.after = if after.is_empty() {
377 None
378 } else {
379 Some(Tokens::new(after))
380 };
381 col.empty = true;
382 self.save_between = VecDeque::new();
383 self.save_before = VecDeque::new();
384
385 if self.repeating {
386 self.non_repeating = self.columns.len();
387 self.repeated.push(col);
388 } else {
389 self.columns.push(col);
390 }
391 }
392
393 pub fn get_column_mut(&mut self, n: usize) -> Option<&mut Cell> {
394 let all_columns = self.columns.len();
395 if (n > all_columns) && self.repeating {
396 let rep = &self.repeated;
397 let m = rep.len();
398 if m > 0 {
399 for i in all_columns..n {
400 let dup = rep[(i - self.non_repeating) % m].clone();
401 self.columns.push(dup);
402 }
403 }
404 }
405 if n > 0 {
406 self.columns.get_mut(n - 1)
407 } else {
408 None
409 }
410 }
411
412 pub fn get_columns(&self) -> &[Cell] { &self.columns }
413 pub fn get_columns_mut(&mut self) -> &mut Vec<Cell> { &mut self.columns }
414 pub fn get_repeated_mut(&mut self) -> &mut Vec<Cell> { &mut self.repeated }
415 pub fn set_pseudo(&mut self) { self.pseudorow = true; }
416 pub fn unset_pseudo(&mut self) { self.pseudorow = false; }
417 pub fn is_pseudo(&self) -> bool { self.pseudorow }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn align_default_is_left() {
426 assert_eq!(Align::default(), Align::Left);
427 }
428
429 #[test]
430 fn align_char_code() {
431 assert_eq!(Align::Left.char_code(), 'l');
432 assert_eq!(Align::Right.char_code(), 'r');
433 assert_eq!(Align::Center.char_code(), 'c');
434 assert_eq!(Align::Justify.char_code(), 'p');
435 assert_eq!(Align::Char(".".to_string()).char_code(), 'c');
437 }
438
439 #[test]
440 fn align_name() {
441 assert_eq!(Align::Left.name(), "left");
442 assert_eq!(Align::Right.name(), "right");
443 assert_eq!(Align::Center.name(), "center");
444 assert_eq!(Align::Justify.name(), "justify");
445 assert_eq!(Align::Char(".".to_string()).name(), "char:.");
446 }
447
448 #[test]
449 fn align_from_char_basic() {
450 assert_eq!(Align::from('l'), Align::Left);
451 assert_eq!(Align::from('r'), Align::Right);
452 assert_eq!(Align::from('c'), Align::Center);
453 assert_eq!(Align::from('p'), Align::Justify);
454 }
455
456 #[test]
457 fn align_from_char_unknown_is_default() {
458 assert_eq!(Align::from('x'), Align::Left);
460 assert_eq!(Align::from('?'), Align::Left);
461 }
462
463 #[test]
464 fn axis_name() {
465 assert_eq!(Axis::Column.name(), "column");
466 assert_eq!(Axis::Row.name(), "row");
467 }
468
469 #[test]
470 fn axis_marker_name_is_inverse() {
471 assert_eq!(Axis::Column.marker_name(), "row");
475 assert_eq!(Axis::Row.marker_name(), "column");
476 }
477
478 #[test]
479 fn column_spec_display_chars() {
480 assert_eq!(format!("{}", ColumnSpec::Integer), "i");
481 assert_eq!(format!("{}", ColumnSpec::Empty), "_");
482 assert_eq!(format!("{}", ColumnSpec::Unknown), "?");
483 assert_eq!(format!("{}", ColumnSpec::Text), "t");
484 assert_eq!(format!("{}", ColumnSpec::MultiText), "tt");
485 assert_eq!(format!("{}", ColumnSpec::Math), "m");
486 assert_eq!(format!("{}", ColumnSpec::MathAltText), "mx");
487 assert_eq!(format!("{}", ColumnSpec::D), "d");
488 assert_eq!(format!("{}", ColumnSpec::Graphics), "g");
489 }
490
491 #[test]
492 fn column_spec_difference_heuristic_self_is_zero() {
493 assert_eq!(
495 ColumnSpec::Empty.difference_heuristic(&ColumnSpec::Empty),
496 0.0
497 );
498 assert_eq!(
499 ColumnSpec::Math.difference_heuristic(&ColumnSpec::Math),
500 0.0
501 );
502 assert_eq!(
503 ColumnSpec::Integer.difference_heuristic(&ColumnSpec::Integer),
504 0.0
505 );
506 assert_eq!(
507 ColumnSpec::Text.difference_heuristic(&ColumnSpec::Text),
508 0.0
509 );
510 assert_eq!(ColumnSpec::D.difference_heuristic(&ColumnSpec::D), 0.0);
511 assert_eq!(
512 ColumnSpec::Graphics.difference_heuristic(&ColumnSpec::Graphics),
513 0.0
514 );
515 }
516
517 #[test]
518 fn column_spec_difference_heuristic_incompatible_is_large() {
519 assert_eq!(
521 ColumnSpec::Graphics.difference_heuristic(&ColumnSpec::Math),
522 0.75
523 );
524 assert_eq!(ColumnSpec::D.difference_heuristic(&ColumnSpec::Text), 0.75);
525 }
526}