1use crate::graphics::GraphicsElem;
22use crate::hbox::{HorzBox, PureHorzBox};
23use crate::length::Length;
24use crate::linebreak::{fit_cell, natural_metrics};
25
26#[derive(Clone, Copy, Debug, PartialEq)]
28pub struct Paddings {
29 pub l: Length,
30 pub r: Length,
31 pub t: Length,
32 pub b: Length,
33}
34
35#[derive(Clone, Debug, PartialEq)]
38pub enum Cell {
39 Normal(Paddings, Vec<HorzBox>),
41 Empty,
45 Multi(usize, usize, Paddings, Vec<HorzBox>),
47}
48
49#[derive(Clone, Debug, PartialEq, syan::visit::Ast)]
55#[subast(crate::hbox::PureHorzBox)]
56pub struct TabularCellBox {
57 pub x: Length,
58 pub baseline_y: Length,
59 pub contents: Vec<(Length, PureHorzBox)>,
60}
61
62#[derive(Clone, Debug, PartialEq, syan::visit::Ast)]
66#[subast(crate::tabular::TabularCellBox, crate::graphics::GraphicsElem)]
67pub struct TabularBox {
68 pub width: Length,
69 pub height: Length,
70 pub depth: Length,
72 pub cells: Vec<TabularCellBox>,
73 pub rules: Vec<GraphicsElem>,
74}
75
76#[derive(Clone, Debug, PartialEq)]
80pub struct Solved {
81 pub width: Length,
82 pub height: Length,
83 pub cells: Vec<TabularCellBox>,
84 pub xs: Vec<Length>,
85 pub ys: Vec<Length>,
86}
87
88type RestRow = Vec<Option<(usize, Length)>>;
93
94type RestCol = Vec<Option<(usize, Length)>>;
97
98fn normalize_tabular(rows: Vec<Vec<Cell>>) -> (usize, Vec<Vec<Cell>>) {
103 let ncols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
104 let htabular = rows
105 .into_iter()
106 .map(|mut row| {
107 while row.len() < ncols {
108 row.push(Cell::Empty);
109 }
110 row
111 })
112 .collect();
113 (ncols, htabular)
114}
115
116fn transpose(rows: &[Vec<Cell>], ncols: usize) -> Vec<Vec<&Cell>> {
119 (0..ncols)
120 .map(|c| rows.iter().map(|row| &row[c]).collect())
121 .collect()
122 }
124
125fn determine_row_metrics(restprev: &RestRow, row: &[Cell]) -> (RestRow, Length, Length) {
128 let mut restacc: RestRow = Vec::with_capacity(row.len());
129 let mut hgt_max = Length::ZERO;
130 let mut dpt_mag_max = Length::ZERO;
131 for (slot, cell) in restprev.iter().zip(row.iter()) {
132 match (slot, cell) {
133 (None, Cell::Normal(pads, content)) => {
134 let (_, hgt, dpt) = natural_metrics(content);
135 hgt_max = hgt_max.max(hgt + pads.t);
136 dpt_mag_max = dpt_mag_max.max(dpt + pads.b);
137 restacc.push(None);
138 }
139 (None, Cell::Empty) => restacc.push(None),
140 (None, Cell::Multi(nr, _nc, pads, content)) => {
144 let (_, hgt, dpt) = natural_metrics(content);
145 let len = (hgt + pads.t) + (dpt + pads.b);
146 let nr = (*nr).max(1);
147 let restelem = if nr == 1 { None } else { Some((nr, len)) };
148 restacc.push(restelem);
149 }
150 (Some((numrow, len)), Cell::Empty) => {
152 restacc.push(Some((*numrow, *len)));
153 }
154 (Some(_), Cell::Normal(pads, content)) => {
158 let (_, hgt, dpt) = natural_metrics(content);
159 hgt_max = hgt_max.max(hgt + pads.t);
160 dpt_mag_max = dpt_mag_max.max(dpt + pads.b);
161 restacc.push(None);
162 }
163 (Some(_), Cell::Multi(nr, _nc, pads, content)) => {
164 let (_, hgt, dpt) = natural_metrics(content);
165 let len = (hgt + pads.t) + (dpt + pads.b);
166 let nr = (*nr).max(1);
167 let restelem = if nr == 1 { None } else { Some((nr, len)) };
168 restacc.push(restelem);
169 }
170 }
171 }
172 let rest = restacc
173 .into_iter()
174 .map(|slot| match slot {
175 None => None,
176 Some((1, _)) => None,
177 Some((numrow, len)) => Some((numrow - 1, len - hgt_max - dpt_mag_max)),
178 })
179 .collect();
180 (rest, hgt_max, dpt_mag_max)
181}
182
183fn determine_column_width(restprev: &RestCol, col: &[&Cell]) -> (RestCol, Length) {
186 let mut restacc: RestCol = Vec::with_capacity(col.len());
187 let mut wid_max = Length::ZERO;
188 for (slot, cell) in restprev.iter().zip(col.iter()) {
189 match (slot, cell) {
190 (None, Cell::Normal(pads, content)) => {
191 let (wid, _, _) = natural_metrics(content);
192 wid_max = wid_max.max(pads.l + wid + pads.r);
193 restacc.push(None);
194 }
195 (None, Cell::Empty) => restacc.push(None),
196 (None, Cell::Multi(_nr, nc, pads, content)) => {
197 let (widraw, _, _) = natural_metrics(content);
198 let wid = pads.l + widraw + pads.r;
199 let nc = (*nc).max(1);
200 if nc == 1 {
201 wid_max = wid_max.max(wid);
202 }
203 restacc.push(Some((nc, wid)));
204 }
205 (Some((numcol, widrest)), Cell::Empty) => {
206 let numcol = *numcol;
207 if numcol == 1 {
208 wid_max = wid_max.max(*widrest);
209 }
210 restacc.push(Some((numcol, *widrest)));
211 }
212 (Some(_), Cell::Normal(pads, content)) => {
215 let (wid, _, _) = natural_metrics(content);
216 wid_max = wid_max.max(pads.l + wid + pads.r);
217 restacc.push(None);
218 }
219 (Some(_), Cell::Multi(_nr, nc, pads, content)) => {
220 let (widraw, _, _) = natural_metrics(content);
221 let wid = pads.l + widraw + pads.r;
222 let nc = (*nc).max(1);
223 if nc == 1 {
224 wid_max = wid_max.max(wid);
225 }
226 restacc.push(Some((nc, wid)));
227 }
228 }
229 }
230 let rest = restacc
231 .into_iter()
232 .map(|slot| match slot {
233 None => None,
234 Some((1, _)) => None,
235 Some((numcol, wid)) => Some((numcol - 1, wid - wid_max)),
236 })
237 .collect();
238 (rest, wid_max)
239}
240
241fn multi_cell_width(widlst: &[Length], index_c: usize, nc: usize) -> Length {
245 if widlst.is_empty() {
246 return Length::ZERO;
247 }
248 let end = (index_c + nc).saturating_sub(1).min(widlst.len() - 1);
249 widlst[index_c.min(end)..=end]
250 .iter()
251 .fold(Length::ZERO, |acc, w| acc + *w)
252}
253
254fn multi_cell_vertical(vmetrlst: &[(Length, Length)], index_r: usize, nr: usize) -> Length {
258 if vmetrlst.is_empty() {
259 return Length::ZERO;
260 }
261 let end = (index_r + nr).saturating_sub(1).min(vmetrlst.len() - 1);
262 vmetrlst[index_r.min(end)..=end]
263 .iter()
264 .fold(Length::ZERO, |acc, (hgt, dpt)| acc + *hgt + *dpt)
265}
266
267fn pad_content(pads: Paddings, content: Vec<HorzBox>) -> Vec<HorzBox> {
271 let mut out = Vec::with_capacity(content.len() + 2);
272 out.push(HorzBox::Pure(PureHorzBox::FixedEmpty { width: pads.l }));
273 out.extend(content);
274 out.push(HorzBox::Pure(PureHorzBox::FixedEmpty { width: pads.r }));
275 out
276}
277
278fn solidify_tabular(
282 vmetrlst: &[(Length, Length)],
283 widlst: &[Length],
284 xs: &[Length],
285 ys: &[Length],
286 htabular: Vec<Vec<Cell>>,
287) -> Vec<TabularCellBox> {
288 let mut cells = Vec::new();
289 for (index_r, row) in htabular.into_iter().enumerate() {
290 let hgt_row = vmetrlst
293 .get(index_r)
294 .map(|(h, _)| *h)
295 .unwrap_or(Length::ZERO);
296 let row_top = ys.get(index_r).copied().unwrap_or(Length::ZERO);
297 for (index_c, cell) in row.into_iter().enumerate() {
298 let x = xs.get(index_c).copied().unwrap_or(Length::ZERO);
299 match cell {
300 Cell::Empty => {}
301 Cell::Normal(pads, content) => {
302 let wid = widlst.get(index_c).copied().unwrap_or(Length::ZERO);
303 let padded = pad_content(pads, content);
304 let (contents, _fit_hgt, _fit_dpt) = fit_cell(padded, wid);
309 let baseline_y = row_top - hgt_row;
310 cells.push(TabularCellBox {
311 x,
312 baseline_y,
313 contents,
314 });
315 }
316 Cell::Multi(nr, nc, pads, content) => {
317 let nr = nr.max(1);
318 let nc = nc.max(1);
319 let wid = multi_cell_width(widlst, index_c, nc);
320 let padded = pad_content(pads, content);
321 let (contents, fit_hgt, fit_dpt) = fit_cell(padded, wid);
322 let hgt_cell = if nr == 1 {
330 hgt_row
331 } else {
332 let vlen_cell = multi_cell_vertical(vmetrlst, index_r, nr);
333 let vlen_content = fit_hgt + fit_dpt;
334 let lenspace = (vlen_cell - vlen_content) * 0.5;
335 fit_hgt + lenspace
336 };
337 let baseline_y = row_top - hgt_cell;
338 cells.push(TabularCellBox {
339 x,
340 baseline_y,
341 contents,
342 });
343 }
344 }
345 }
346 }
347 cells
348}
349
350pub fn main(rows: Vec<Vec<Cell>>) -> Solved {
352 let nrows = rows.len();
353 let (ncols, htabular) = normalize_tabular(rows);
354
355 let mut restrow: RestRow = vec![None; ncols];
357 let mut vmetrlst: Vec<(Length, Length)> = Vec::with_capacity(nrows);
358 for row in &htabular {
359 let (rest, hgt, dpt) = determine_row_metrics(&restrow, row);
360 restrow = rest;
361 vmetrlst.push((hgt, dpt));
362 }
363
364 let vtabular = transpose(&htabular, ncols);
366 let mut restcol: RestCol = vec![None; nrows];
367 let mut widlst: Vec<Length> = Vec::with_capacity(ncols);
368 for col in &vtabular {
369 let (rest, wid) = determine_column_width(&restcol, col);
370 restcol = rest;
371 widlst.push(wid);
372 }
373
374 let width = widlst.iter().fold(Length::ZERO, |acc, w| acc + *w);
375 let height = vmetrlst
376 .iter()
377 .fold(Length::ZERO, |acc, (h, d)| acc + *h + *d);
378
379 let mut xs = Vec::with_capacity(ncols + 1);
383 xs.push(Length::ZERO);
384 let mut x = Length::ZERO;
385 for w in &widlst {
386 x = x + *w;
387 xs.push(x);
388 }
389 let mut ys = Vec::with_capacity(nrows + 1);
390 ys.push(height);
391 let mut y = height;
392 for (hgt, dpt) in &vmetrlst {
393 y = y - (*hgt + *dpt);
394 ys.push(y);
395 }
396
397 let cells = solidify_tabular(&vmetrlst, &widlst, &xs, &ys, htabular);
398
399 Solved {
400 width,
401 height,
402 cells,
403 xs,
404 ys,
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 fn probe(w: f64, h: f64, d: f64) -> Vec<HorzBox> {
416 vec![HorzBox::Pure(PureHorzBox::Graphics {
417 width: Length::pt(w),
418 height: Length::pt(h),
419 depth: Length::pt(d),
420 elems: Vec::new(),
421 origin_independent: false,
422 })]
423 }
424
425 fn zero_pads() -> Paddings {
426 Paddings {
427 l: Length::ZERO,
428 r: Length::ZERO,
429 t: Length::ZERO,
430 b: Length::ZERO,
431 }
432 }
433
434 #[test]
435 fn two_by_two_normal_grid_geometry() {
436 let rows = vec![
440 vec![
441 Cell::Normal(zero_pads(), probe(30.0, 12.0, 3.0)),
442 Cell::Normal(zero_pads(), probe(20.0, 8.0, 2.0)),
443 ],
444 vec![
445 Cell::Normal(zero_pads(), probe(25.0, 10.0, 4.0)),
446 Cell::Normal(zero_pads(), probe(15.0, 6.0, 1.0)),
447 ],
448 ];
449 let solved = main(rows);
450
451 assert_eq!(solved.width, Length::pt(50.0));
452 assert_eq!(solved.height, Length::pt(29.0));
453 assert_eq!(
454 solved.xs,
455 vec![Length::pt(0.0), Length::pt(30.0), Length::pt(50.0)]
456 );
457 assert_eq!(
458 solved.ys,
459 vec![Length::pt(29.0), Length::pt(14.0), Length::pt(0.0)]
460 );
461 assert_eq!(solved.cells.len(), 4);
462
463 assert_eq!(solved.cells[0].x, Length::pt(0.0));
465 assert_eq!(solved.cells[0].baseline_y, Length::pt(17.0));
466 assert_eq!(solved.cells[1].x, Length::pt(30.0));
467 assert_eq!(solved.cells[1].baseline_y, Length::pt(17.0));
468 assert_eq!(solved.cells[2].x, Length::pt(0.0));
469 assert_eq!(solved.cells[2].baseline_y, Length::pt(4.0));
470 assert_eq!(solved.cells[3].x, Length::pt(30.0));
471 assert_eq!(solved.cells[3].baseline_y, Length::pt(4.0));
472 }
473
474 #[test]
475 fn empty_cell_produces_no_box() {
476 let rows = vec![vec![
477 Cell::Normal(zero_pads(), probe(10.0, 5.0, 1.0)),
478 Cell::Empty,
479 ]];
480 let solved = main(rows);
481 assert_eq!(solved.cells.len(), 1);
482 assert_eq!(solved.xs.len(), 3);
483 }
484
485 #[test]
486 fn multi_column_span_absorbs_following_empty() {
487 let rows = vec![
492 vec![
493 Cell::Multi(1, 2, zero_pads(), probe(50.0, 10.0, 2.0)),
494 Cell::Empty,
495 ],
496 vec![
497 Cell::Normal(zero_pads(), probe(20.0, 5.0, 1.0)),
498 Cell::Normal(zero_pads(), probe(25.0, 6.0, 1.0)),
499 ],
500 ];
501 let solved = main(rows);
502
503 assert_eq!(
504 solved.xs,
505 vec![Length::pt(0.0), Length::pt(20.0), Length::pt(50.0)]
506 );
507 assert_eq!(solved.cells.len(), 3);
510 assert_eq!(solved.cells[0].x, Length::pt(0.0));
511 }
512
513 #[test]
519 fn multi_row_span_centers_content_across_the_rows_it_spans() {
520 let rows = vec![
523 vec![
524 Cell::Normal(zero_pads(), probe(20.0, 10.0, 2.0)),
525 Cell::Normal(zero_pads(), probe(15.0, 8.0, 1.0)),
526 ],
527 vec![
528 Cell::Multi(2, 1, zero_pads(), probe(12.0, 6.0, 1.0)),
529 Cell::Normal(zero_pads(), probe(15.0, 9.0, 3.0)),
530 ],
531 vec![
532 Cell::Empty,
533 Cell::Normal(zero_pads(), probe(15.0, 7.0, 2.0)),
534 ],
535 ];
536 let solved = main(rows);
537
538 assert_eq!(solved.height, Length::pt(33.0));
541 assert_eq!(
542 solved.ys,
543 vec![
544 Length::pt(33.0),
545 Length::pt(21.0),
546 Length::pt(9.0),
547 Length::pt(0.0)
548 ]
549 );
550 assert_eq!(
553 solved.xs,
554 vec![Length::pt(0.0), Length::pt(20.0), Length::pt(35.0)]
555 );
556
557 assert_eq!(solved.cells.len(), 5);
560 assert_eq!(solved.cells[0].baseline_y, Length::pt(23.0)); assert_eq!(solved.cells[1].baseline_y, Length::pt(23.0));
562 assert_eq!(solved.cells[2].x, Length::pt(0.0));
566 assert_eq!(solved.cells[2].baseline_y, Length::pt(8.0));
567 assert_eq!(solved.cells[3].baseline_y, Length::pt(12.0)); assert_eq!(solved.cells[4].baseline_y, Length::pt(2.0)); }
570
571 #[test]
572 fn tabular_box_measures_as_a_single_leaf() {
573 let rows = vec![vec![Cell::Normal(zero_pads(), probe(30.0, 12.0, 3.0))]];
574 let solved = main(rows);
575 let tab = TabularBox {
576 width: solved.width,
577 height: solved.height,
578 depth: Length::ZERO,
579 cells: solved.cells,
580 rules: Vec::new(),
581 };
582 let bx = HorzBox::Pure(PureHorzBox::Tabular(tab.clone()));
583 assert_eq!(
584 crate::linebreak::natural_metrics(std::slice::from_ref(&bx)),
585 (tab.width, tab.height, Length::ZERO)
586 );
587 let HorzBox::Pure(p) = &bx;
588 assert!(!p.is_glue());
589 assert_eq!(p.natural_width(), tab.width);
590 }
591}