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)]
55pub struct TabularCellBox {
56 pub x: Length,
57 pub baseline_y: Length,
58 pub contents: Vec<(Length, PureHorzBox)>,
59}
60
61#[derive(Clone, Debug, PartialEq)]
65pub struct TabularBox {
66 pub width: Length,
67 pub height: Length,
68 pub depth: Length,
70 pub cells: Vec<TabularCellBox>,
71 pub rules: Vec<GraphicsElem>,
72}
73
74#[derive(Clone, Debug, PartialEq)]
78pub struct Solved {
79 pub width: Length,
80 pub height: Length,
81 pub cells: Vec<TabularCellBox>,
82 pub xs: Vec<Length>,
83 pub ys: Vec<Length>,
84}
85
86type RestRow = Vec<Option<(usize, Length)>>;
91
92type RestCol = Vec<Option<(usize, Length)>>;
95
96fn normalize_tabular(rows: Vec<Vec<Cell>>) -> (usize, Vec<Vec<Cell>>) {
101 let ncols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
102 let htabular = rows
103 .into_iter()
104 .map(|mut row| {
105 while row.len() < ncols {
106 row.push(Cell::Empty);
107 }
108 row
109 })
110 .collect();
111 (ncols, htabular)
112}
113
114fn transpose(rows: &[Vec<Cell>], ncols: usize) -> Vec<Vec<&Cell>> {
117 (0..ncols)
118 .map(|c| rows.iter().map(|row| &row[c]).collect())
119 .collect()
120 }
122
123fn determine_row_metrics(restprev: &RestRow, row: &[Cell]) -> (RestRow, Length, Length) {
126 let mut restacc: RestRow = Vec::with_capacity(row.len());
127 let mut hgt_max = Length::ZERO;
128 let mut dpt_mag_max = Length::ZERO;
129 for (slot, cell) in restprev.iter().zip(row.iter()) {
130 match (slot, cell) {
131 (None, Cell::Normal(pads, content)) => {
132 let (_, hgt, dpt) = natural_metrics(content);
133 hgt_max = hgt_max.max(hgt + pads.t);
134 dpt_mag_max = dpt_mag_max.max(dpt + pads.b);
135 restacc.push(None);
136 }
137 (None, Cell::Empty) => restacc.push(None),
138 (None, Cell::Multi(nr, _nc, pads, content)) => {
142 let (_, hgt, dpt) = natural_metrics(content);
143 let len = (hgt + pads.t) + (dpt + pads.b);
144 let nr = (*nr).max(1);
145 let restelem = if nr == 1 { None } else { Some((nr, len)) };
146 restacc.push(restelem);
147 }
148 (Some((numrow, len)), Cell::Empty) => {
150 restacc.push(Some((*numrow, *len)));
151 }
152 (Some(_), Cell::Normal(pads, content)) => {
156 let (_, hgt, dpt) = natural_metrics(content);
157 hgt_max = hgt_max.max(hgt + pads.t);
158 dpt_mag_max = dpt_mag_max.max(dpt + pads.b);
159 restacc.push(None);
160 }
161 (Some(_), Cell::Multi(nr, _nc, pads, content)) => {
162 let (_, hgt, dpt) = natural_metrics(content);
163 let len = (hgt + pads.t) + (dpt + pads.b);
164 let nr = (*nr).max(1);
165 let restelem = if nr == 1 { None } else { Some((nr, len)) };
166 restacc.push(restelem);
167 }
168 }
169 }
170 let rest = restacc
171 .into_iter()
172 .map(|slot| match slot {
173 None => None,
174 Some((1, _)) => None,
175 Some((numrow, len)) => Some((numrow - 1, len - hgt_max - dpt_mag_max)),
176 })
177 .collect();
178 (rest, hgt_max, dpt_mag_max)
179}
180
181fn determine_column_width(restprev: &RestCol, col: &[&Cell]) -> (RestCol, Length) {
184 let mut restacc: RestCol = Vec::with_capacity(col.len());
185 let mut wid_max = Length::ZERO;
186 for (slot, cell) in restprev.iter().zip(col.iter()) {
187 match (slot, cell) {
188 (None, Cell::Normal(pads, content)) => {
189 let (wid, _, _) = natural_metrics(content);
190 wid_max = wid_max.max(pads.l + wid + pads.r);
191 restacc.push(None);
192 }
193 (None, Cell::Empty) => restacc.push(None),
194 (None, Cell::Multi(_nr, nc, pads, content)) => {
195 let (widraw, _, _) = natural_metrics(content);
196 let wid = pads.l + widraw + pads.r;
197 let nc = (*nc).max(1);
198 if nc == 1 {
199 wid_max = wid_max.max(wid);
200 }
201 restacc.push(Some((nc, wid)));
202 }
203 (Some((numcol, widrest)), Cell::Empty) => {
204 let numcol = *numcol;
205 if numcol == 1 {
206 wid_max = wid_max.max(*widrest);
207 }
208 restacc.push(Some((numcol, *widrest)));
209 }
210 (Some(_), Cell::Normal(pads, content)) => {
213 let (wid, _, _) = natural_metrics(content);
214 wid_max = wid_max.max(pads.l + wid + pads.r);
215 restacc.push(None);
216 }
217 (Some(_), Cell::Multi(_nr, nc, pads, content)) => {
218 let (widraw, _, _) = natural_metrics(content);
219 let wid = pads.l + widraw + pads.r;
220 let nc = (*nc).max(1);
221 if nc == 1 {
222 wid_max = wid_max.max(wid);
223 }
224 restacc.push(Some((nc, wid)));
225 }
226 }
227 }
228 let rest = restacc
229 .into_iter()
230 .map(|slot| match slot {
231 None => None,
232 Some((1, _)) => None,
233 Some((numcol, wid)) => Some((numcol - 1, wid - wid_max)),
234 })
235 .collect();
236 (rest, wid_max)
237}
238
239fn multi_cell_width(widlst: &[Length], index_c: usize, nc: usize) -> Length {
243 if widlst.is_empty() {
244 return Length::ZERO;
245 }
246 let end = (index_c + nc).saturating_sub(1).min(widlst.len() - 1);
247 widlst[index_c.min(end)..=end]
248 .iter()
249 .fold(Length::ZERO, |acc, w| acc + *w)
250}
251
252fn multi_cell_vertical(vmetrlst: &[(Length, Length)], index_r: usize, nr: usize) -> Length {
256 if vmetrlst.is_empty() {
257 return Length::ZERO;
258 }
259 let end = (index_r + nr).saturating_sub(1).min(vmetrlst.len() - 1);
260 vmetrlst[index_r.min(end)..=end]
261 .iter()
262 .fold(Length::ZERO, |acc, (hgt, dpt)| acc + *hgt + *dpt)
263}
264
265fn pad_content(pads: Paddings, content: Vec<HorzBox>) -> Vec<HorzBox> {
269 let mut out = Vec::with_capacity(content.len() + 2);
270 out.push(HorzBox::Pure(PureHorzBox::FixedEmpty { width: pads.l }));
271 out.extend(content);
272 out.push(HorzBox::Pure(PureHorzBox::FixedEmpty { width: pads.r }));
273 out
274}
275
276fn solidify_tabular(
280 vmetrlst: &[(Length, Length)],
281 widlst: &[Length],
282 xs: &[Length],
283 ys: &[Length],
284 htabular: Vec<Vec<Cell>>,
285) -> Vec<TabularCellBox> {
286 let mut cells = Vec::new();
287 for (index_r, row) in htabular.into_iter().enumerate() {
288 let hgt_row = vmetrlst
291 .get(index_r)
292 .map(|(h, _)| *h)
293 .unwrap_or(Length::ZERO);
294 let row_top = ys.get(index_r).copied().unwrap_or(Length::ZERO);
295 for (index_c, cell) in row.into_iter().enumerate() {
296 let x = xs.get(index_c).copied().unwrap_or(Length::ZERO);
297 match cell {
298 Cell::Empty => {}
299 Cell::Normal(pads, content) => {
300 let wid = widlst.get(index_c).copied().unwrap_or(Length::ZERO);
301 let padded = pad_content(pads, content);
302 let (contents, _fit_hgt, _fit_dpt) = fit_cell(padded, wid);
307 let baseline_y = row_top - hgt_row;
308 cells.push(TabularCellBox {
309 x,
310 baseline_y,
311 contents,
312 });
313 }
314 Cell::Multi(nr, nc, pads, content) => {
315 let nr = nr.max(1);
316 let nc = nc.max(1);
317 let wid = multi_cell_width(widlst, index_c, nc);
318 let padded = pad_content(pads, content);
319 let (contents, fit_hgt, fit_dpt) = fit_cell(padded, wid);
320 let hgt_cell = if nr == 1 {
328 hgt_row
329 } else {
330 let vlen_cell = multi_cell_vertical(vmetrlst, index_r, nr);
331 let vlen_content = fit_hgt + fit_dpt;
332 let lenspace = (vlen_cell - vlen_content) * 0.5;
333 fit_hgt + lenspace
334 };
335 let baseline_y = row_top - hgt_cell;
336 cells.push(TabularCellBox {
337 x,
338 baseline_y,
339 contents,
340 });
341 }
342 }
343 }
344 }
345 cells
346}
347
348pub fn main(rows: Vec<Vec<Cell>>) -> Solved {
350 let nrows = rows.len();
351 let (ncols, htabular) = normalize_tabular(rows);
352
353 let mut restrow: RestRow = vec![None; ncols];
355 let mut vmetrlst: Vec<(Length, Length)> = Vec::with_capacity(nrows);
356 for row in &htabular {
357 let (rest, hgt, dpt) = determine_row_metrics(&restrow, row);
358 restrow = rest;
359 vmetrlst.push((hgt, dpt));
360 }
361
362 let vtabular = transpose(&htabular, ncols);
364 let mut restcol: RestCol = vec![None; nrows];
365 let mut widlst: Vec<Length> = Vec::with_capacity(ncols);
366 for col in &vtabular {
367 let (rest, wid) = determine_column_width(&restcol, col);
368 restcol = rest;
369 widlst.push(wid);
370 }
371
372 let width = widlst.iter().fold(Length::ZERO, |acc, w| acc + *w);
373 let height = vmetrlst
374 .iter()
375 .fold(Length::ZERO, |acc, (h, d)| acc + *h + *d);
376
377 let mut xs = Vec::with_capacity(ncols + 1);
381 xs.push(Length::ZERO);
382 let mut x = Length::ZERO;
383 for w in &widlst {
384 x = x + *w;
385 xs.push(x);
386 }
387 let mut ys = Vec::with_capacity(nrows + 1);
388 ys.push(height);
389 let mut y = height;
390 for (hgt, dpt) in &vmetrlst {
391 y = y - (*hgt + *dpt);
392 ys.push(y);
393 }
394
395 let cells = solidify_tabular(&vmetrlst, &widlst, &xs, &ys, htabular);
396
397 Solved {
398 width,
399 height,
400 cells,
401 xs,
402 ys,
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 fn probe(w: f64, h: f64, d: f64) -> Vec<HorzBox> {
414 vec![HorzBox::Pure(PureHorzBox::Graphics {
415 width: Length::pt(w),
416 height: Length::pt(h),
417 depth: Length::pt(d),
418 elems: Vec::new(),
419 origin_independent: false,
420 })]
421 }
422
423 fn zero_pads() -> Paddings {
424 Paddings {
425 l: Length::ZERO,
426 r: Length::ZERO,
427 t: Length::ZERO,
428 b: Length::ZERO,
429 }
430 }
431
432 #[test]
433 fn two_by_two_normal_grid_geometry() {
434 let rows = vec![
438 vec![
439 Cell::Normal(zero_pads(), probe(30.0, 12.0, 3.0)),
440 Cell::Normal(zero_pads(), probe(20.0, 8.0, 2.0)),
441 ],
442 vec![
443 Cell::Normal(zero_pads(), probe(25.0, 10.0, 4.0)),
444 Cell::Normal(zero_pads(), probe(15.0, 6.0, 1.0)),
445 ],
446 ];
447 let solved = main(rows);
448
449 assert_eq!(solved.width, Length::pt(50.0));
450 assert_eq!(solved.height, Length::pt(29.0));
451 assert_eq!(
452 solved.xs,
453 vec![Length::pt(0.0), Length::pt(30.0), Length::pt(50.0)]
454 );
455 assert_eq!(
456 solved.ys,
457 vec![Length::pt(29.0), Length::pt(14.0), Length::pt(0.0)]
458 );
459 assert_eq!(solved.cells.len(), 4);
460
461 assert_eq!(solved.cells[0].x, Length::pt(0.0));
463 assert_eq!(solved.cells[0].baseline_y, Length::pt(17.0));
464 assert_eq!(solved.cells[1].x, Length::pt(30.0));
465 assert_eq!(solved.cells[1].baseline_y, Length::pt(17.0));
466 assert_eq!(solved.cells[2].x, Length::pt(0.0));
467 assert_eq!(solved.cells[2].baseline_y, Length::pt(4.0));
468 assert_eq!(solved.cells[3].x, Length::pt(30.0));
469 assert_eq!(solved.cells[3].baseline_y, Length::pt(4.0));
470 }
471
472 #[test]
473 fn empty_cell_produces_no_box() {
474 let rows = vec![vec![
475 Cell::Normal(zero_pads(), probe(10.0, 5.0, 1.0)),
476 Cell::Empty,
477 ]];
478 let solved = main(rows);
479 assert_eq!(solved.cells.len(), 1);
480 assert_eq!(solved.xs.len(), 3);
481 }
482
483 #[test]
484 fn multi_column_span_absorbs_following_empty() {
485 let rows = vec![
490 vec![
491 Cell::Multi(1, 2, zero_pads(), probe(50.0, 10.0, 2.0)),
492 Cell::Empty,
493 ],
494 vec![
495 Cell::Normal(zero_pads(), probe(20.0, 5.0, 1.0)),
496 Cell::Normal(zero_pads(), probe(25.0, 6.0, 1.0)),
497 ],
498 ];
499 let solved = main(rows);
500
501 assert_eq!(
502 solved.xs,
503 vec![Length::pt(0.0), Length::pt(20.0), Length::pt(50.0)]
504 );
505 assert_eq!(solved.cells.len(), 3);
508 assert_eq!(solved.cells[0].x, Length::pt(0.0));
509 }
510
511 #[test]
517 fn multi_row_span_centers_content_across_the_rows_it_spans() {
518 let rows = vec![
521 vec![
522 Cell::Normal(zero_pads(), probe(20.0, 10.0, 2.0)),
523 Cell::Normal(zero_pads(), probe(15.0, 8.0, 1.0)),
524 ],
525 vec![
526 Cell::Multi(2, 1, zero_pads(), probe(12.0, 6.0, 1.0)),
527 Cell::Normal(zero_pads(), probe(15.0, 9.0, 3.0)),
528 ],
529 vec![
530 Cell::Empty,
531 Cell::Normal(zero_pads(), probe(15.0, 7.0, 2.0)),
532 ],
533 ];
534 let solved = main(rows);
535
536 assert_eq!(solved.height, Length::pt(33.0));
539 assert_eq!(
540 solved.ys,
541 vec![
542 Length::pt(33.0),
543 Length::pt(21.0),
544 Length::pt(9.0),
545 Length::pt(0.0)
546 ]
547 );
548 assert_eq!(
551 solved.xs,
552 vec![Length::pt(0.0), Length::pt(20.0), Length::pt(35.0)]
553 );
554
555 assert_eq!(solved.cells.len(), 5);
558 assert_eq!(solved.cells[0].baseline_y, Length::pt(23.0)); assert_eq!(solved.cells[1].baseline_y, Length::pt(23.0));
560 assert_eq!(solved.cells[2].x, Length::pt(0.0));
564 assert_eq!(solved.cells[2].baseline_y, Length::pt(8.0));
565 assert_eq!(solved.cells[3].baseline_y, Length::pt(12.0)); assert_eq!(solved.cells[4].baseline_y, Length::pt(2.0)); }
568
569 #[test]
570 fn tabular_box_measures_as_a_single_leaf() {
571 let rows = vec![vec![Cell::Normal(zero_pads(), probe(30.0, 12.0, 3.0))]];
572 let solved = main(rows);
573 let tab = TabularBox {
574 width: solved.width,
575 height: solved.height,
576 depth: Length::ZERO,
577 cells: solved.cells,
578 rules: Vec::new(),
579 };
580 let bx = HorzBox::Pure(PureHorzBox::Tabular(tab.clone()));
581 assert_eq!(
582 crate::linebreak::natural_metrics(std::slice::from_ref(&bx)),
583 (tab.width, tab.height, Length::ZERO)
584 );
585 let HorzBox::Pure(p) = &bx;
586 assert!(!p.is_glue());
587 assert_eq!(p.natural_width(), tab.width);
588 }
589}