typst_library/layout/grid/resolve.rs
1use std::num::{NonZeroU32, NonZeroUsize};
2use std::ops::{Deref, DerefMut, Range};
3use std::sync::Arc;
4
5use ecow::eco_format;
6use typst_library::Dir;
7use typst_library::diag::{
8 At, Hint, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint, bail,
9};
10use typst_library::engine::Engine;
11use typst_library::foundations::{Content, Fold, Packed, Smart, StyleChain};
12use typst_library::layout::{
13 Abs, Alignment, Axes, Celled, GridCell, GridChild, GridElem, GridItem, Length,
14 OuterHAlignment, OuterVAlignment, Rel, ResolvedCelled, Sides, Sizing,
15};
16use typst_library::model::{TableCell, TableChild, TableElem, TableItem};
17use typst_library::text::TextElem;
18use typst_library::visualize::{Paint, Stroke};
19
20use typst_syntax::Span;
21use typst_utils::{NonZeroExt, SmallBitSet};
22
23use crate::pdf::{TableCellKind, TableHeaderScope};
24
25/// Convert a grid to a cell grid.
26#[typst_macros::time(span = elem.span())]
27pub fn grid_to_cellgrid(
28 elem: &Packed<GridElem>,
29 engine: &mut Engine,
30 styles: StyleChain,
31) -> SourceResult<CellGrid> {
32 let inset = elem.inset.get_cloned(styles);
33 let align = elem.align.get_ref(styles);
34 let columns = elem.columns.get_ref(styles);
35 let rows = elem.rows.get_ref(styles);
36 let column_gutter = elem.column_gutter.get_ref(styles);
37 let row_gutter = elem.row_gutter.get_ref(styles);
38 let fill = elem.fill.get_ref(styles);
39 let stroke = elem.stroke.resolve(styles);
40
41 let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
42 let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
43 // Use trace to link back to the grid when a specific cell errors
44 let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
45 let resolve_item = |item: &GridItem| grid_item_to_resolvable(item, styles);
46 let children = elem.children.iter().map(|child| match child {
47 GridChild::Header(header) => ResolvableGridChild::Header {
48 repeat: header.repeat.get(styles),
49 level: header.level.get(styles),
50 span: header.span(),
51 items: header.children.iter().map(resolve_item),
52 },
53 GridChild::Footer(footer) => ResolvableGridChild::Footer {
54 repeat: footer.repeat.get(styles),
55 span: footer.span(),
56 items: footer.children.iter().map(resolve_item),
57 },
58 GridChild::Item(item) => {
59 ResolvableGridChild::Item(grid_item_to_resolvable(item, styles))
60 }
61 });
62 resolve_cellgrid(
63 tracks,
64 gutter,
65 children,
66 fill,
67 align,
68 &inset,
69 &stroke,
70 engine,
71 styles,
72 elem.span(),
73 )
74 .trace(engine.world, tracepoint, elem.span())
75}
76
77/// Convert a table to a cell grid.
78#[typst_macros::time(span = elem.span())]
79pub fn table_to_cellgrid(
80 elem: &Packed<TableElem>,
81 engine: &mut Engine,
82 styles: StyleChain,
83) -> SourceResult<CellGrid> {
84 let inset = elem.inset.get_cloned(styles);
85 let align = elem.align.get_ref(styles);
86 let columns = elem.columns.get_ref(styles);
87 let rows = elem.rows.get_ref(styles);
88 let column_gutter = elem.column_gutter.get_ref(styles);
89 let row_gutter = elem.row_gutter.get_ref(styles);
90 let fill = elem.fill.get_ref(styles);
91 let stroke = elem.stroke.resolve(styles);
92
93 let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
94 let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
95 // Use trace to link back to the table when a specific cell errors
96 let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
97 let resolve_item = |item: &TableItem| table_item_to_resolvable(item, styles);
98 let children = elem.children.iter().map(|child| match child {
99 TableChild::Header(header) => ResolvableGridChild::Header {
100 repeat: header.repeat.get(styles),
101 level: header.level.get(styles),
102 span: header.span(),
103 items: header.children.iter().map(resolve_item),
104 },
105 TableChild::Footer(footer) => ResolvableGridChild::Footer {
106 repeat: footer.repeat.get(styles),
107 span: footer.span(),
108 items: footer.children.iter().map(resolve_item),
109 },
110 TableChild::Item(item) => {
111 ResolvableGridChild::Item(table_item_to_resolvable(item, styles))
112 }
113 });
114 resolve_cellgrid(
115 tracks,
116 gutter,
117 children,
118 fill,
119 align,
120 &inset,
121 &stroke,
122 engine,
123 styles,
124 elem.span(),
125 )
126 .trace(engine.world, tracepoint, elem.span())
127}
128
129fn grid_item_to_resolvable(
130 item: &GridItem,
131 styles: StyleChain,
132) -> ResolvableGridItem<Packed<GridCell>> {
133 match item {
134 GridItem::HLine(hline) => ResolvableGridItem::HLine {
135 y: hline.y.get(styles),
136 start: hline.start.get(styles),
137 end: hline.end.get(styles),
138 stroke: hline.stroke.resolve(styles),
139 span: hline.span(),
140 position: match hline.position.get(styles) {
141 OuterVAlignment::Top => LinePosition::Before,
142 OuterVAlignment::Bottom => LinePosition::After,
143 },
144 },
145 GridItem::VLine(vline) => ResolvableGridItem::VLine {
146 x: vline.x.get(styles),
147 start: vline.start.get(styles),
148 end: vline.end.get(styles),
149 stroke: vline.stroke.resolve(styles),
150 span: vline.span(),
151 position: match vline.position.get(styles) {
152 OuterHAlignment::Left if styles.resolve(TextElem::dir) == Dir::RTL => {
153 LinePosition::After
154 }
155 OuterHAlignment::Right if styles.resolve(TextElem::dir) == Dir::RTL => {
156 LinePosition::Before
157 }
158 OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
159 OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
160 },
161 },
162 GridItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
163 }
164}
165
166fn table_item_to_resolvable(
167 item: &TableItem,
168 styles: StyleChain,
169) -> ResolvableGridItem<Packed<TableCell>> {
170 match item {
171 TableItem::HLine(hline) => ResolvableGridItem::HLine {
172 y: hline.y.get(styles),
173 start: hline.start.get(styles),
174 end: hline.end.get(styles),
175 stroke: hline.stroke.resolve(styles),
176 span: hline.span(),
177 position: match hline.position.get(styles) {
178 OuterVAlignment::Top => LinePosition::Before,
179 OuterVAlignment::Bottom => LinePosition::After,
180 },
181 },
182 TableItem::VLine(vline) => ResolvableGridItem::VLine {
183 x: vline.x.get(styles),
184 start: vline.start.get(styles),
185 end: vline.end.get(styles),
186 stroke: vline.stroke.resolve(styles),
187 span: vline.span(),
188 position: match vline.position.get(styles) {
189 OuterHAlignment::Left if styles.resolve(TextElem::dir) == Dir::RTL => {
190 LinePosition::After
191 }
192 OuterHAlignment::Right if styles.resolve(TextElem::dir) == Dir::RTL => {
193 LinePosition::Before
194 }
195 OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
196 OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
197 },
198 },
199 TableItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
200 }
201}
202
203impl ResolvableCell for Packed<TableCell> {
204 fn resolve_cell(
205 mut self,
206 x: usize,
207 y: usize,
208 fill: &Option<Paint>,
209 align: Smart<Alignment>,
210 inset: Sides<Option<Rel<Length>>>,
211 stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
212 breakable: bool,
213 styles: StyleChain,
214 kind: Smart<TableCellKind>,
215 ) -> Cell {
216 let cell = &mut *self;
217 let colspan = cell.colspan.get(styles);
218 let rowspan = cell.rowspan.get(styles);
219 let breakable = cell.breakable.get(styles).unwrap_or(breakable);
220 let fill = cell.fill.get_cloned(styles).unwrap_or_else(|| fill.clone());
221
222 let kind = cell.kind.get(styles).or(kind);
223
224 let cell_stroke = cell.stroke.resolve(styles);
225 let stroke_overridden =
226 cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
227
228 // Using a typical 'Sides' fold, an unspecified side loses to a
229 // specified side. Additionally, when both are specified, an inner
230 // None wins over the outer Some, and vice-versa. When both are
231 // specified and Some, fold occurs, which, remarkably, leads to an Arc
232 // clone.
233 //
234 // In the end, we flatten because, for layout purposes, an unspecified
235 // cell stroke is the same as specifying 'none', so we equate the two
236 // concepts.
237 let stroke = cell_stroke.fold(stroke).map(Option::flatten);
238 cell.x.set(Smart::Custom(x));
239 cell.y.set(Smart::Custom(y));
240 cell.fill.set(Smart::Custom(fill.clone()));
241 cell.align.set(match align {
242 Smart::Custom(align) => Smart::Custom(
243 cell.align.get(styles).map_or(align, |inner| inner.fold(align)),
244 ),
245 // Don't fold if the table is using outer alignment. Use the
246 // cell's alignment instead (which, in the end, will fold with
247 // the outer alignment when it is effectively displayed).
248 Smart::Auto => cell.align.get(styles),
249 });
250 cell.inset.set(Smart::Custom(
251 cell.inset.get(styles).map_or(inset, |inner| inner.fold(inset)),
252 ));
253 cell.stroke.set(
254 // Here we convert the resolved stroke to a regular stroke, however
255 // with resolved units (that is, 'em' converted to absolute units).
256 // We also convert any stroke unspecified by both the cell and the
257 // outer stroke ('None' in the folded stroke) to 'none', that is,
258 // all sides are present in the resulting Sides object accessible
259 // by show rules on table cells.
260 stroke.as_ref().map(|side| {
261 Some(side.as_ref().map(|cell_stroke| {
262 Arc::new((**cell_stroke).clone().map(Length::from))
263 }))
264 }),
265 );
266 cell.breakable.set(Smart::Custom(breakable));
267 cell.kind.set(kind);
268 Cell {
269 body: self.pack(),
270 fill,
271 colspan,
272 rowspan,
273 stroke,
274 stroke_overridden,
275 breakable,
276 }
277 }
278
279 fn x(&self, styles: StyleChain) -> Smart<usize> {
280 self.x.get(styles)
281 }
282
283 fn y(&self, styles: StyleChain) -> Smart<usize> {
284 self.y.get(styles)
285 }
286
287 fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
288 self.colspan.get(styles)
289 }
290
291 fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
292 self.rowspan.get(styles)
293 }
294
295 fn span(&self) -> Span {
296 Packed::span(self)
297 }
298}
299
300impl ResolvableCell for Packed<GridCell> {
301 fn resolve_cell(
302 mut self,
303 x: usize,
304 y: usize,
305 fill: &Option<Paint>,
306 align: Smart<Alignment>,
307 inset: Sides<Option<Rel<Length>>>,
308 stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
309 breakable: bool,
310 styles: StyleChain,
311 _: Smart<TableCellKind>,
312 ) -> Cell {
313 let cell = &mut *self;
314 let colspan = cell.colspan.get(styles);
315 let rowspan = cell.rowspan.get(styles);
316 let breakable = cell.breakable.get(styles).unwrap_or(breakable);
317 let fill = cell.fill.get_cloned(styles).unwrap_or_else(|| fill.clone());
318
319 let cell_stroke = cell.stroke.resolve(styles);
320 let stroke_overridden =
321 cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
322
323 // Using a typical 'Sides' fold, an unspecified side loses to a
324 // specified side. Additionally, when both are specified, an inner
325 // None wins over the outer Some, and vice-versa. When both are
326 // specified and Some, fold occurs, which, remarkably, leads to an Arc
327 // clone.
328 //
329 // In the end, we flatten because, for layout purposes, an unspecified
330 // cell stroke is the same as specifying 'none', so we equate the two
331 // concepts.
332 let stroke = cell_stroke.fold(stroke).map(Option::flatten);
333 cell.x.set(Smart::Custom(x));
334 cell.y.set(Smart::Custom(y));
335 cell.fill.set(Smart::Custom(fill.clone()));
336 cell.align.set(match align {
337 Smart::Custom(align) => Smart::Custom(
338 cell.align.get(styles).map_or(align, |inner| inner.fold(align)),
339 ),
340 // Don't fold if the grid is using outer alignment. Use the
341 // cell's alignment instead (which, in the end, will fold with
342 // the outer alignment when it is effectively displayed).
343 Smart::Auto => cell.align.get(styles),
344 });
345 cell.inset.set(Smart::Custom(
346 cell.inset.get(styles).map_or(inset, |inner| inner.fold(inset)),
347 ));
348 cell.stroke.set(
349 // Here we convert the resolved stroke to a regular stroke, however
350 // with resolved units (that is, 'em' converted to absolute units).
351 // We also convert any stroke unspecified by both the cell and the
352 // outer stroke ('None' in the folded stroke) to 'none', that is,
353 // all sides are present in the resulting Sides object accessible
354 // by show rules on grid cells.
355 stroke.as_ref().map(|side| {
356 Some(side.as_ref().map(|cell_stroke| {
357 Arc::new((**cell_stroke).clone().map(Length::from))
358 }))
359 }),
360 );
361 cell.breakable.set(Smart::Custom(breakable));
362 Cell {
363 body: self.pack(),
364 fill,
365 colspan,
366 rowspan,
367 stroke,
368 stroke_overridden,
369 breakable,
370 }
371 }
372
373 fn x(&self, styles: StyleChain) -> Smart<usize> {
374 self.x.get(styles)
375 }
376
377 fn y(&self, styles: StyleChain) -> Smart<usize> {
378 self.y.get(styles)
379 }
380
381 fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
382 self.colspan.get(styles)
383 }
384
385 fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
386 self.rowspan.get(styles)
387 }
388
389 fn span(&self) -> Span {
390 Packed::span(self)
391 }
392}
393
394/// Represents an explicit grid line (horizontal or vertical) specified by the
395/// user.
396#[derive(Debug, Eq, PartialEq, Hash)]
397pub struct Line {
398 /// The index of the track after this line. This will be the index of the
399 /// row a horizontal line is above of, or of the column right after a
400 /// vertical line.
401 ///
402 /// Must be within `0..=tracks.len()` (where `tracks` is either `grid.cols`
403 /// or `grid.rows`, ignoring gutter tracks, as appropriate).
404 pub index: usize,
405 /// The index of the track at which this line starts being drawn.
406 /// This is the first column a horizontal line appears in, or the first row
407 /// a vertical line appears in.
408 ///
409 /// Must be within `0..tracks.len()` minus gutter tracks.
410 pub start: usize,
411 /// The index after the last track through which the line is drawn.
412 /// Thus, the line is drawn through tracks `start..end` (note that `end` is
413 /// exclusive).
414 ///
415 /// Must be within `1..=tracks.len()` minus gutter tracks.
416 /// `None` indicates the line should go all the way to the end.
417 pub end: Option<NonZeroUsize>,
418 /// The line's stroke. This is `None` when the line is explicitly used to
419 /// override a previously specified line.
420 pub stroke: Option<Arc<Stroke<Abs>>>,
421 /// The line's position in relation to the track with its index.
422 pub position: LinePosition,
423}
424
425/// A repeatable grid header. Starts at the first row.
426#[derive(Debug, Eq, PartialEq, Hash)]
427pub struct Header {
428 /// The range of rows included in this header.
429 pub range: Range<usize>,
430 /// The header's level.
431 ///
432 /// Higher level headers repeat together with lower level headers. If a
433 /// lower level header stops repeating, all higher level headers do as
434 /// well.
435 pub level: NonZeroU32,
436 /// Whether this header cannot be repeated nor should have orphan
437 /// prevention because it would be about to cease repetition, either
438 /// because it is followed by headers of conflicting levels, or because
439 /// it is at the end of the table (possibly followed by some footers at the
440 /// end).
441 pub short_lived: bool,
442}
443
444/// A repeatable grid footer. Stops at the last row.
445#[derive(Debug, Eq, PartialEq, Hash)]
446pub struct Footer {
447 /// The first row included in this footer.
448 pub start: usize,
449 /// The index after the last row included in this footer.
450 pub end: usize,
451 /// The footer's level.
452 ///
453 /// Used similarly to header level.
454 pub level: u32,
455}
456
457impl Footer {
458 /// The footer's range of included rows.
459 #[inline]
460 pub fn range(&self) -> Range<usize> {
461 self.start..self.end
462 }
463}
464
465/// A possibly repeatable grid child (header or footer).
466///
467/// It still exists even when not repeatable, but must not have additional
468/// considerations by grid layout, other than for consistency (such as making
469/// a certain group of rows unbreakable).
470#[derive(Debug, Eq, PartialEq, Hash)]
471pub struct Repeatable<T> {
472 inner: T,
473
474 /// Whether the user requested the child to repeat.
475 pub repeated: bool,
476}
477
478impl<T> Deref for Repeatable<T> {
479 type Target = T;
480
481 fn deref(&self) -> &Self::Target {
482 &self.inner
483 }
484}
485
486impl<T> DerefMut for Repeatable<T> {
487 fn deref_mut(&mut self) -> &mut Self::Target {
488 &mut self.inner
489 }
490}
491
492impl<T> Repeatable<T> {
493 /// Returns `Some` if the value is repeated, `None` otherwise.
494 #[inline]
495 pub fn as_repeated(&self) -> Option<&T> {
496 if self.repeated { Some(&self.inner) } else { None }
497 }
498}
499
500/// Used for cell-like elements which are aware of their final properties in
501/// the table, and may have property overrides.
502pub trait ResolvableCell {
503 /// Resolves the cell's fields, given its coordinates and default grid-wide
504 /// fill, align, inset and stroke properties, plus the expected value of
505 /// the `breakable` field.
506 /// Returns a final Cell.
507 #[allow(clippy::too_many_arguments)]
508 fn resolve_cell(
509 self,
510 x: usize,
511 y: usize,
512 fill: &Option<Paint>,
513 align: Smart<Alignment>,
514 inset: Sides<Option<Rel<Length>>>,
515 stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
516 breakable: bool,
517 styles: StyleChain,
518 kind: Smart<TableCellKind>,
519 ) -> Cell;
520
521 /// Returns this cell's column override.
522 fn x(&self, styles: StyleChain) -> Smart<usize>;
523
524 /// Returns this cell's row override.
525 fn y(&self, styles: StyleChain) -> Smart<usize>;
526
527 /// The amount of columns spanned by this cell.
528 fn colspan(&self, styles: StyleChain) -> NonZeroUsize;
529
530 /// The amount of rows spanned by this cell.
531 fn rowspan(&self, styles: StyleChain) -> NonZeroUsize;
532
533 /// The cell's span, for errors.
534 fn span(&self) -> Span;
535}
536
537/// A grid item, possibly affected by automatic cell positioning. Can be either
538/// a line or a cell.
539pub enum ResolvableGridItem<T: ResolvableCell> {
540 /// A horizontal line in the grid.
541 HLine {
542 /// The row above which the horizontal line is drawn.
543 y: Smart<usize>,
544 start: usize,
545 end: Option<NonZeroUsize>,
546 stroke: Option<Arc<Stroke<Abs>>>,
547 /// The span of the corresponding line element.
548 span: Span,
549 /// The line's position. "before" here means on top of row `y`, while
550 /// "after" means below it.
551 position: LinePosition,
552 },
553 /// A vertical line in the grid.
554 VLine {
555 /// The column before which the vertical line is drawn.
556 x: Smart<usize>,
557 start: usize,
558 end: Option<NonZeroUsize>,
559 stroke: Option<Arc<Stroke<Abs>>>,
560 /// The span of the corresponding line element.
561 span: Span,
562 /// The line's position. "before" here means to the left of column `x`,
563 /// while "after" means to its right (both considering LTR).
564 position: LinePosition,
565 },
566 /// A cell in the grid.
567 Cell(T),
568}
569
570/// Represents a cell in CellGrid, to be laid out by GridLayouter.
571#[derive(Debug, PartialEq, Hash)]
572pub struct Cell {
573 /// The cell's body.
574 pub body: Content,
575 /// The cell's fill.
576 pub fill: Option<Paint>,
577 /// The amount of columns spanned by the cell.
578 pub colspan: NonZeroUsize,
579 /// The amount of rows spanned by the cell.
580 pub rowspan: NonZeroUsize,
581 /// The cell's stroke.
582 ///
583 /// We use an Arc to avoid unnecessary space usage when all sides are the
584 /// same, or when the strokes come from a common source.
585 pub stroke: Sides<Option<Arc<Stroke<Abs>>>>,
586 /// Which stroke sides were explicitly overridden by the cell, over the
587 /// grid's global stroke setting.
588 ///
589 /// This is used to define whether or not this cell's stroke sides should
590 /// have priority over adjacent cells' stroke sides, if those don't
591 /// override their own stroke properties (and thus have less priority when
592 /// defining with which stroke to draw grid lines around this cell).
593 pub stroke_overridden: Sides<bool>,
594 /// Whether rows spanned by this cell can be placed in different pages.
595 /// By default, a cell spanning only fixed-size rows is unbreakable, while
596 /// a cell spanning at least one `auto`-sized row is breakable.
597 pub breakable: bool,
598}
599
600impl Cell {
601 /// Create a simple cell given its body.
602 pub fn new(body: Content) -> Self {
603 Self {
604 body,
605 fill: None,
606 colspan: NonZeroUsize::ONE,
607 rowspan: NonZeroUsize::ONE,
608 stroke: Sides::splat(None),
609 stroke_overridden: Sides::splat(false),
610 breakable: true,
611 }
612 }
613}
614
615/// Indicates whether the line should be drawn before or after the track with
616/// its index. This is mostly only relevant when gutter is used, since, then,
617/// the position after a track is not the same as before the next
618/// non-gutter track.
619#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
620pub enum LinePosition {
621 /// The line should be drawn before its track (e.g. hline on top of a row).
622 Before,
623 /// The line should be drawn after its track (e.g. hline below a row).
624 After,
625}
626
627/// A grid entry.
628#[derive(Debug, PartialEq, Hash)]
629pub enum Entry {
630 /// An entry which holds a cell.
631 Cell(Cell),
632 /// An entry which is merged with another cell.
633 Merged {
634 /// The index of the cell this entry is merged with.
635 parent: usize,
636 },
637}
638
639impl Entry {
640 /// Obtains the cell inside this entry, if this is not a merged cell.
641 pub fn as_cell(&self) -> Option<&Cell> {
642 match self {
643 Self::Cell(cell) => Some(cell),
644 Self::Merged { .. } => None,
645 }
646 }
647}
648
649/// Any grid child, which can be either a header or an item.
650pub enum ResolvableGridChild<T: ResolvableCell, I> {
651 Header { repeat: bool, level: NonZeroU32, span: Span, items: I },
652 Footer { repeat: bool, span: Span, items: I },
653 Item(ResolvableGridItem<T>),
654}
655
656/// A grid of cells, including the columns, rows, and cell data.
657#[derive(Debug, PartialEq, Hash)]
658pub struct CellGrid {
659 /// The grid cells.
660 pub entries: Vec<Entry>,
661 /// The column tracks including gutter tracks.
662 pub cols: Vec<Sizing>,
663 /// The row tracks including gutter tracks.
664 pub rows: Vec<Sizing>,
665 /// The vertical lines before each column, or on the end border.
666 /// Gutter columns are not included.
667 /// Contains up to 'cols_without_gutter.len() + 1' vectors of lines.
668 pub vlines: Vec<Vec<Line>>,
669 /// The horizontal lines on top of each row, or on the bottom border.
670 /// Gutter rows are not included.
671 /// Contains up to 'rows_without_gutter.len() + 1' vectors of lines.
672 pub hlines: Vec<Vec<Line>>,
673 /// The repeatable headers of this grid.
674 pub headers: Vec<Repeatable<Header>>,
675 /// The repeatable footer of this grid.
676 pub footer: Option<Repeatable<Footer>>,
677 /// Whether this grid has gutters.
678 pub has_gutter: bool,
679}
680
681impl CellGrid {
682 /// Generates the cell grid, given the tracks and cells.
683 pub fn new(
684 tracks: Axes<&[Sizing]>,
685 gutter: Axes<&[Sizing]>,
686 cells: impl IntoIterator<Item = Cell>,
687 ) -> Self {
688 let entries = cells.into_iter().map(Entry::Cell).collect();
689 Self::new_internal(tracks, gutter, vec![], vec![], vec![], None, entries)
690 }
691
692 /// Generates the cell grid, given the tracks and resolved entries.
693 pub fn new_internal(
694 tracks: Axes<&[Sizing]>,
695 gutter: Axes<&[Sizing]>,
696 vlines: Vec<Vec<Line>>,
697 hlines: Vec<Vec<Line>>,
698 headers: Vec<Repeatable<Header>>,
699 footer: Option<Repeatable<Footer>>,
700 entries: Vec<Entry>,
701 ) -> Self {
702 let mut cols = vec![];
703 let mut rows = vec![];
704
705 // Number of content columns: Always at least one.
706 let num_cols = tracks.x.len().max(1);
707
708 // Number of content rows: At least as many as given, but also at least
709 // as many as needed to place each item.
710 let num_rows = {
711 let len = entries.len();
712 let given = tracks.y.len();
713 let needed = len / num_cols + (len % num_cols).clamp(0, 1);
714 given.max(needed)
715 };
716
717 let has_gutter = gutter.any(|tracks| !tracks.is_empty());
718 let auto = Sizing::Auto;
719 let zero = Sizing::Rel(Rel::zero());
720 let get_or = |tracks: &[_], idx, default| {
721 tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
722 };
723
724 // Collect content and gutter columns.
725 for x in 0..num_cols {
726 cols.push(get_or(tracks.x, x, auto));
727 if has_gutter {
728 cols.push(get_or(gutter.x, x, zero));
729 }
730 }
731
732 // Collect content and gutter rows.
733 for y in 0..num_rows {
734 rows.push(get_or(tracks.y, y, auto));
735 if has_gutter {
736 rows.push(get_or(gutter.y, y, zero));
737 }
738 }
739
740 // Remove superfluous gutter tracks.
741 if has_gutter {
742 cols.pop();
743 rows.pop();
744 }
745
746 Self {
747 cols,
748 rows,
749 entries,
750 vlines,
751 hlines,
752 headers,
753 footer,
754 has_gutter,
755 }
756 }
757
758 /// Get the grid entry in column `x` and row `y`.
759 ///
760 /// Returns `None` if it's a gutter cell.
761 #[track_caller]
762 pub fn entry(&self, x: usize, y: usize) -> Option<&Entry> {
763 assert!(x < self.cols.len());
764 assert!(y < self.rows.len());
765
766 if self.has_gutter {
767 // Even columns and rows are children, odd ones are gutter.
768 if x % 2 == 0 && y % 2 == 0 {
769 let c = 1 + self.cols.len() / 2;
770 self.entries.get((y / 2) * c + x / 2)
771 } else {
772 None
773 }
774 } else {
775 let c = self.cols.len();
776 self.entries.get(y * c + x)
777 }
778 }
779
780 /// Get the content of the cell in column `x` and row `y`.
781 ///
782 /// Returns `None` if it's a gutter cell or merged position.
783 #[track_caller]
784 pub fn cell(&self, x: usize, y: usize) -> Option<&Cell> {
785 self.entry(x, y).and_then(Entry::as_cell)
786 }
787
788 /// Returns the position of the parent cell of the grid entry at the given
789 /// position. It is guaranteed to have a non-gutter, non-merged cell at
790 /// the returned position, due to how the grid is built.
791 /// - If the entry at the given position is a cell, returns the given
792 /// position.
793 /// - If it is a merged cell, returns the parent cell's position.
794 /// - If it is a gutter cell, returns None.
795 #[track_caller]
796 pub fn parent_cell_position(&self, x: usize, y: usize) -> Option<Axes<usize>> {
797 self.entry(x, y).map(|entry| match entry {
798 Entry::Cell(_) => Axes::new(x, y),
799 Entry::Merged { parent } => {
800 let c = self.non_gutter_column_count();
801 let factor = if self.has_gutter { 2 } else { 1 };
802 Axes::new(factor * (*parent % c), factor * (*parent / c))
803 }
804 })
805 }
806
807 /// Returns the position of the actual parent cell of a merged position,
808 /// even if the given position is gutter, in which case we return the
809 /// parent of the nearest adjacent content cell which could possibly span
810 /// the given gutter position. If the given position is not a gutter cell,
811 /// then this function will return the same as `parent_cell_position` would.
812 /// If the given position is a gutter cell, but no cell spans it, returns
813 /// `None`.
814 ///
815 /// This is useful for lines. A line needs to check if a cell next to it
816 /// has a stroke override - even at a gutter position there could be a
817 /// stroke override, since a cell could be merged with two cells at both
818 /// ends of the gutter cell (e.g. to its left and to its right), and thus
819 /// that cell would impose a stroke under the gutter. This function allows
820 /// getting the position of that cell (which spans the given gutter
821 /// position, if it is gutter), if it exists; otherwise returns None (it's
822 /// gutter and no cell spans it).
823 #[track_caller]
824 pub fn effective_parent_cell_position(
825 &self,
826 x: usize,
827 y: usize,
828 ) -> Option<Axes<usize>> {
829 if self.has_gutter {
830 // If (x, y) is a gutter cell, we skip it (skip a gutter column and
831 // row) to the nearest adjacent content cell, in the direction
832 // which merged cells grow toward (increasing x and increasing y),
833 // such that we can verify if that adjacent cell is merged with the
834 // gutter cell by checking if its parent would come before (x, y).
835 // Otherwise, no cell is merged with this gutter cell, and we
836 // return None.
837 self.parent_cell_position(x + x % 2, y + y % 2)
838 .filter(|&parent| parent.x <= x && parent.y <= y)
839 } else {
840 self.parent_cell_position(x, y)
841 }
842 }
843
844 /// Checks if the track with the given index is gutter.
845 /// Does not check if the index is a valid track.
846 #[inline]
847 pub fn is_gutter_track(&self, index: usize) -> bool {
848 self.has_gutter && index % 2 == 1
849 }
850
851 /// Returns the effective colspan of a cell, considering the gutters it
852 /// might span if the grid has gutters.
853 #[inline]
854 pub fn effective_colspan_of_cell(&self, cell: &Cell) -> usize {
855 if self.has_gutter { 2 * cell.colspan.get() - 1 } else { cell.colspan.get() }
856 }
857
858 /// Returns the effective rowspan of a cell, considering the gutters it
859 /// might span if the grid has gutters.
860 #[inline]
861 pub fn effective_rowspan_of_cell(&self, cell: &Cell) -> usize {
862 if self.has_gutter { 2 * cell.rowspan.get() - 1 } else { cell.rowspan.get() }
863 }
864
865 #[inline]
866 pub fn non_gutter_column_count(&self) -> usize {
867 if self.has_gutter {
868 // Calculation: With gutters, we have
869 // 'cols = 2 * (non-gutter cols) - 1', since there is a gutter
870 // column between each regular column. Therefore,
871 // 'floor(cols / 2)' will be equal to
872 // 'floor(non-gutter cols - 1/2) = non-gutter-cols - 1',
873 // so 'non-gutter cols = 1 + floor(cols / 2)'.
874 1 + self.cols.len() / 2
875 } else {
876 self.cols.len()
877 }
878 }
879
880 #[inline]
881 pub fn non_gutter_row_count(&self) -> usize {
882 if self.has_gutter {
883 // Calculation: With gutters, we have
884 // 'rows = 2 * (non-gutter rows) - 1', since there is a gutter
885 // row between each regular row. Therefore,
886 // 'floor(rows / 2)' will be equal to
887 // 'floor(non-gutter rows - 1/2) = non-gutter-rows - 1',
888 // so 'non-gutter rows = 1 + floor(rows / 2)'.
889 1 + self.rows.len() / 2
890 } else {
891 self.rows.len()
892 }
893 }
894
895 #[inline]
896 pub fn has_repeated_headers(&self) -> bool {
897 self.headers.iter().any(|h| h.repeated)
898 }
899}
900
901/// Resolves and positions all cells in the grid before creating it.
902/// Allows them to keep track of their final properties and positions
903/// and adjust their fields accordingly.
904/// Cells must implement Clone as they will be owned. Additionally, they
905/// must implement Default in order to fill positions in the grid which
906/// weren't explicitly specified by the user with empty cells.
907#[allow(clippy::too_many_arguments)]
908pub fn resolve_cellgrid<'a, T, C, I>(
909 tracks: Axes<&'a [Sizing]>,
910 gutter: Axes<&'a [Sizing]>,
911 children: C,
912 fill: &'a Celled<Option<Paint>>,
913 align: &'a Celled<Smart<Alignment>>,
914 inset: &'a Celled<Sides<Option<Rel<Length>>>>,
915 stroke: &'a ResolvedCelled<Sides<Option<Option<Arc<Stroke>>>>>,
916 engine: &'a mut Engine,
917 styles: StyleChain<'a>,
918 span: Span,
919) -> SourceResult<CellGrid>
920where
921 T: ResolvableCell + Default,
922 I: Iterator<Item = ResolvableGridItem<T>>,
923 C: IntoIterator<Item = ResolvableGridChild<T, I>>,
924 C::IntoIter: ExactSizeIterator,
925{
926 CellGridResolver {
927 tracks,
928 gutter,
929 fill,
930 align,
931 inset,
932 stroke,
933 engine,
934 styles,
935 span,
936 }
937 .resolve(children)
938}
939
940struct CellGridResolver<'a, 'b> {
941 tracks: Axes<&'a [Sizing]>,
942 gutter: Axes<&'a [Sizing]>,
943 fill: &'a Celled<Option<Paint>>,
944 align: &'a Celled<Smart<Alignment>>,
945 inset: &'a Celled<Sides<Option<Rel<Length>>>>,
946 stroke: &'a ResolvedCelled<Sides<Option<Option<Arc<Stroke>>>>>,
947 engine: &'a mut Engine<'b>,
948 styles: StyleChain<'a>,
949 span: Span,
950}
951
952#[derive(Debug, Copy, Clone)]
953enum RowGroupKind {
954 Header,
955 Footer,
956}
957
958impl RowGroupKind {
959 fn name(self) -> &'static str {
960 match self {
961 Self::Header => "header",
962 Self::Footer => "footer",
963 }
964 }
965}
966
967struct RowGroupData {
968 /// The range of rows of cells inside this grid row group. The
969 /// first and last rows are guaranteed to have cells (an exception
970 /// is made when there is gutter, in which case the group range may
971 /// be expanded to include an additional gutter row when there is a
972 /// repeatable header or footer). This is `None` until the first
973 /// cell of the row group is placed, then it is continually adjusted
974 /// to fit the cells inside the row group.
975 ///
976 /// This stays as `None` for fully empty headers and footers.
977 range: Option<Range<usize>>,
978 span: Span,
979 kind: RowGroupKind,
980
981 /// Whether this header or footer may repeat.
982 repeat: bool,
983
984 /// Level of this header or footer.
985 repeatable_level: NonZeroU32,
986
987 /// Start of the range of indices of hlines at the top of the row group.
988 /// This is always the first index after the last hline before we started
989 /// building the row group - any upcoming hlines would appear at least at
990 /// this index.
991 ///
992 /// These hlines were auto-positioned and appeared before any auto-pos
993 /// cells, so they will appear at the first possible row (above the
994 /// first row spanned by the row group).
995 top_hlines_start: usize,
996
997 /// End of the range of indices of hlines at the top of the row group.
998 ///
999 /// This starts as `None`, meaning that, if we stop the loop before we find
1000 /// any auto-pos cells, all auto-pos hlines after the last hline (after the
1001 /// index `top_hlines_start`) should be moved to the top of the row group.
1002 ///
1003 /// It becomes `Some(index of last hline at the top)` when an auto-pos cell
1004 /// is found, as auto-pos hlines after any auto-pos cells appear below
1005 /// them, not at the top of the row group.
1006 top_hlines_end: Option<usize>,
1007}
1008
1009impl CellGridResolver<'_, '_> {
1010 fn resolve<T, C, I>(mut self, children: C) -> SourceResult<CellGrid>
1011 where
1012 T: ResolvableCell + Default,
1013 I: Iterator<Item = ResolvableGridItem<T>>,
1014 C: IntoIterator<Item = ResolvableGridChild<T, I>>,
1015 C::IntoIter: ExactSizeIterator,
1016 {
1017 // Number of content columns: Always at least one.
1018 let columns = self.tracks.x.len().max(1);
1019
1020 // Lists of lines.
1021 // Horizontal lines are only pushed later to be able to check for row
1022 // validity, since the amount of rows isn't known until all items were
1023 // analyzed in the for loop below.
1024 // We keep their spans so we can report errors later.
1025 // The additional boolean indicates whether the hline had an automatic
1026 // 'y' index, and is used to change the index of hlines at the top of a
1027 // header or footer.
1028 let mut pending_hlines: Vec<(Span, Line, bool)> = vec![];
1029
1030 // For consistency, only push vertical lines later as well.
1031 let mut pending_vlines: Vec<(Span, Line)> = vec![];
1032 let has_gutter = self.gutter.any(|tracks| !tracks.is_empty());
1033
1034 let mut headers: Vec<Repeatable<Header>> = vec![];
1035
1036 // Stores where the footer is supposed to end, its span, and the
1037 // actual footer structure.
1038 let mut footer: Option<(usize, Span, Footer)> = None;
1039 let mut repeat_footer = false;
1040
1041 // If true, there has been at least one cell besides headers and
1042 // footers. When false, footers at the end are forced to not repeat.
1043 let mut at_least_one_cell = false;
1044
1045 // We can't just use the cell's index in the 'cells' vector to
1046 // determine its automatic position, since cells could have arbitrary
1047 // positions, so the position of a cell in 'cells' can differ from its
1048 // final position in 'resolved_cells' (see below).
1049 // Therefore, we use a counter, 'auto_index', to determine the position
1050 // of the next cell with (x: auto, y: auto). It is only stepped when
1051 // a cell with (x: auto, y: auto), usually the vast majority, is found.
1052 //
1053 // Note that a separate counter ('local_auto_index') is used within
1054 // headers and footers, as explained above its definition. Outside of
1055 // those (when the table child being processed is a single cell),
1056 // 'local_auto_index' will simply be an alias for 'auto_index', which
1057 // will be updated after that cell is placed, if it is an
1058 // automatically-positioned cell.
1059 let mut auto_index: usize = 0;
1060
1061 // We have to rebuild the grid to account for fixed cell positions.
1062 //
1063 // Create at least 'children.len()' positions, since there could be at
1064 // least 'children.len()' cells (if no explicit lines were specified),
1065 // even though some of them might be placed in fixed positions and thus
1066 // cause the grid to expand.
1067 //
1068 // Additionally, make sure we allocate up to the next multiple of
1069 // 'columns', since each row will have 'columns' cells, even if the
1070 // last few cells weren't explicitly specified by the user.
1071 let children = children.into_iter();
1072 let Some(child_count) = children.len().checked_next_multiple_of(columns) else {
1073 bail!(self.span, "too many cells or lines were given")
1074 };
1075
1076 // Rows in this bitset are occupied by an existing header.
1077 // This allows for efficiently checking whether a cell would collide
1078 // with a header at a certain row. (For footers, it's easy as there is
1079 // only one.)
1080 //
1081 // TODO(subfooters): how to add a footer here while avoiding
1082 // unnecessary allocations?
1083 let mut header_rows: SmallBitSet = SmallBitSet::new();
1084 let mut resolved_cells: Vec<Option<Entry>> = Vec::with_capacity(child_count);
1085 for child in children {
1086 self.resolve_grid_child(
1087 columns,
1088 &mut pending_hlines,
1089 &mut pending_vlines,
1090 &mut header_rows,
1091 &mut headers,
1092 &mut footer,
1093 &mut repeat_footer,
1094 &mut auto_index,
1095 &mut resolved_cells,
1096 &mut at_least_one_cell,
1097 child,
1098 )?;
1099 }
1100
1101 let resolved_cells = self.fixup_cells::<T>(resolved_cells, columns)?;
1102
1103 let row_amount = resolved_cells.len().div_ceil(columns);
1104 let (hlines, vlines) = self.collect_lines(
1105 pending_hlines,
1106 pending_vlines,
1107 has_gutter,
1108 columns,
1109 row_amount,
1110 )?;
1111
1112 let footer = self.finalize_headers_and_footers(
1113 has_gutter,
1114 &mut headers,
1115 footer,
1116 repeat_footer,
1117 row_amount,
1118 at_least_one_cell,
1119 )?;
1120
1121 Ok(CellGrid::new_internal(
1122 self.tracks,
1123 self.gutter,
1124 vlines,
1125 hlines,
1126 headers,
1127 footer,
1128 resolved_cells,
1129 ))
1130 }
1131
1132 /// Resolve a grid child, which can be a header, a footer (both of which
1133 /// are row groups, and thus contain multiple grid items inside them), or
1134 /// a grid item - a cell, an hline or a vline.
1135 ///
1136 /// This process consists of placing the child and any sub-items into
1137 /// appropriate positions in the resolved grid. This is mostly relevant for
1138 /// items without fixed positions, such that they must be placed after the
1139 /// previous one, perhaps skipping existing cells along the way.
1140 #[allow(clippy::too_many_arguments)]
1141 fn resolve_grid_child<T, I>(
1142 &mut self,
1143 columns: usize,
1144 pending_hlines: &mut Vec<(Span, Line, bool)>,
1145 pending_vlines: &mut Vec<(Span, Line)>,
1146 header_rows: &mut SmallBitSet,
1147 headers: &mut Vec<Repeatable<Header>>,
1148 footer: &mut Option<(usize, Span, Footer)>,
1149 repeat_footer: &mut bool,
1150 auto_index: &mut usize,
1151 resolved_cells: &mut Vec<Option<Entry>>,
1152 at_least_one_cell: &mut bool,
1153 child: ResolvableGridChild<T, I>,
1154 ) -> SourceResult<()>
1155 where
1156 T: ResolvableCell + Default,
1157 I: Iterator<Item = ResolvableGridItem<T>>,
1158 {
1159 // Data for the row group in this iteration.
1160 //
1161 // Note that cells outside headers and footers are grid children
1162 // with a single cell inside, and thus not considered row groups,
1163 // in which case this variable remains 'None'.
1164 let mut row_group_data: Option<RowGroupData> = None;
1165
1166 // Usually, the global auto index is stepped only when a cell with
1167 // fully automatic position (no fixed x/y) is placed, advancing one
1168 // position. In that usual case, 'local_auto_index == auto_index'
1169 // holds, as 'local_auto_index' is what the code below actually
1170 // updates.
1171 //
1172 // However, headers and footers must trigger a rowbreak if the
1173 // previous row isn't empty, given they cannot occupy only part of a
1174 // row. Therefore, the initial auto index used by their auto cells
1175 // should be right below the first empty row.
1176 //
1177 // The problem is that we don't know whether the header will actually
1178 // have an auto cell or not, and we don't want the external auto index
1179 // to change (no rowbreak should be triggered) if the header has no
1180 // auto cells (although a fully empty header does count as having
1181 // auto cells, albeit empty).
1182 //
1183 // So, we use a separate auto index counter inside the header. It starts
1184 // below the first non-empty row. If the header only has fixed-position
1185 // cells, the external counter is unchanged. Otherwise (has auto cells
1186 // or is empty), the external counter is synchronized and moved to
1187 // below the header.
1188 //
1189 // This ensures lines and cells specified below the header in the
1190 // source code also appear below it in the final grid/table.
1191 let local_auto_index = if matches!(child, ResolvableGridChild::Item(_)) {
1192 // Re-borrow the original auto index so we can reuse this mutable
1193 // reference later.
1194 &mut *auto_index
1195 } else {
1196 // Although 'usize' is Copy, we need to be explicit here that we
1197 // aren't reborrowing the original auto index but rather making a
1198 // mutable copy of it using 'clone'.
1199 &mut (*auto_index).clone()
1200 };
1201
1202 // The first row in which this table group can fit.
1203 //
1204 // Within headers and footers, this will correspond to the first
1205 // fully empty row available in the grid. This is because headers
1206 // and footers always occupy entire rows, so they cannot occupy
1207 // a non-empty row.
1208 let mut first_available_row = 0;
1209
1210 // The cell kind is currently only used for tagged PDF.
1211 let cell_kind;
1212
1213 let (header_footer_items, simple_item) = match child {
1214 ResolvableGridChild::Header { repeat, level, span, items } => {
1215 cell_kind =
1216 Smart::Custom(TableCellKind::Header(level, TableHeaderScope::Column));
1217
1218 row_group_data = Some(RowGroupData {
1219 range: None,
1220 span,
1221 kind: RowGroupKind::Header,
1222 repeat,
1223 repeatable_level: level,
1224 top_hlines_start: pending_hlines.len(),
1225 top_hlines_end: None,
1226 });
1227
1228 first_available_row =
1229 find_next_empty_row(resolved_cells, *local_auto_index, columns);
1230
1231 // If any cell in the header is automatically positioned,
1232 // have it skip to the next empty row. This is to avoid
1233 // having a header after a partially filled row just add
1234 // cells to that row instead of starting a new one.
1235 //
1236 // Note that the first fully empty row is always after the
1237 // latest auto-position cell, since each auto-position cell
1238 // always occupies the first available position after the
1239 // previous one. Therefore, this will be >= auto_index.
1240 *local_auto_index = first_available_row * columns;
1241
1242 (Some(items), None)
1243 }
1244 ResolvableGridChild::Footer { repeat, span, items } => {
1245 if footer.is_some() {
1246 bail!(span, "cannot have more than one footer");
1247 }
1248
1249 cell_kind = Smart::Custom(TableCellKind::Footer);
1250
1251 row_group_data = Some(RowGroupData {
1252 range: None,
1253 span,
1254 repeat,
1255 kind: RowGroupKind::Footer,
1256 repeatable_level: NonZeroU32::ONE,
1257 top_hlines_start: pending_hlines.len(),
1258 top_hlines_end: None,
1259 });
1260
1261 first_available_row =
1262 find_next_empty_row(resolved_cells, *local_auto_index, columns);
1263
1264 *local_auto_index = first_available_row * columns;
1265
1266 (Some(items), None)
1267 }
1268 ResolvableGridChild::Item(item) => {
1269 cell_kind = Smart::Custom(TableCellKind::Data);
1270
1271 if matches!(item, ResolvableGridItem::Cell(_)) {
1272 *at_least_one_cell = true;
1273 }
1274
1275 (None, Some(item))
1276 }
1277 };
1278
1279 let mut had_auto_cells = false;
1280 let items = header_footer_items.into_iter().flatten().chain(simple_item);
1281
1282 for item in items {
1283 let cell = match item {
1284 ResolvableGridItem::HLine { y, start, end, stroke, span, position } => {
1285 let has_auto_y = y.is_auto();
1286 let y = y.unwrap_or_else(|| {
1287 // Avoid placing the hline inside consecutive
1288 // rowspans occupying all columns, as it'd just
1289 // disappear, at least when there's no column
1290 // gutter.
1291 skip_auto_index_through_fully_merged_rows(
1292 resolved_cells,
1293 local_auto_index,
1294 columns,
1295 );
1296
1297 // When no 'y' is specified for the hline, we place
1298 // it under the latest automatically positioned
1299 // cell.
1300 // The current value of the auto index is always
1301 // the index of the latest automatically positioned
1302 // cell placed plus one (that's what we do in
1303 // 'resolve_cell_position'), so we subtract 1 to
1304 // get that cell's index, and place the hline below
1305 // its row. The exception is when the auto_index is
1306 // 0, meaning no automatically positioned cell was
1307 // placed yet. In that case, we place the hline at
1308 // the top of the table.
1309 //
1310 // Exceptionally, the hline will be placed before
1311 // the minimum auto index if the current auto index
1312 // from previous iterations is smaller than the
1313 // minimum it should have for the current grid
1314 // child. Effectively, this means that a hline at
1315 // the start of a header will always appear above
1316 // that header's first row. Similarly for footers.
1317 local_auto_index
1318 .checked_sub(1)
1319 .map_or(0, |last_auto_index| last_auto_index / columns + 1)
1320 });
1321 if end.is_some_and(|end| end.get() < start) {
1322 bail!(span, "line cannot end before it starts");
1323 }
1324 let line = Line { index: y, start, end, stroke, position };
1325
1326 // Since the amount of rows is dynamic, delay placing
1327 // hlines until after all cells were placed so we can
1328 // properly verify if they are valid. Note that we
1329 // can't place hlines even if we already know they
1330 // would be in a valid row, since it's possible that we
1331 // pushed pending hlines in the same row as this one in
1332 // previous iterations, and we need to ensure that
1333 // hlines from previous iterations are pushed to the
1334 // final vector of hlines first - the order of hlines
1335 // must be kept, as this matters when determining which
1336 // one "wins" in case of conflict. Pushing the current
1337 // hline before we push pending hlines later would
1338 // change their order!
1339 pending_hlines.push((span, line, has_auto_y));
1340 continue;
1341 }
1342 ResolvableGridItem::VLine { x, start, end, stroke, span, position } => {
1343 let x = x.unwrap_or_else(|| {
1344 // When no 'x' is specified for the vline, we place
1345 // it after the latest automatically positioned
1346 // cell.
1347 // The current value of the auto index is always
1348 // the index of the latest automatically positioned
1349 // cell placed plus one (that's what we do in
1350 // 'resolve_cell_position'), so we subtract 1 to
1351 // get that cell's index, and place the vline after
1352 // its column. The exception is when the auto_index
1353 // is 0, meaning no automatically positioned cell
1354 // was placed yet. In that case, we place the vline
1355 // to the left of the table.
1356 //
1357 // Exceptionally, a vline is also placed to the
1358 // left of the table when specified at the start
1359 // of a row group, such as a header or footer, that
1360 // is, when no automatically-positioned cells have
1361 // been specified for that group yet.
1362 // For example, this means that a vline at
1363 // the beginning of a header will be placed to its
1364 // left rather than after the previous
1365 // automatically positioned cell. Same for footers.
1366 local_auto_index
1367 .checked_sub(1)
1368 .filter(|_| *local_auto_index > first_available_row * columns)
1369 .map_or(0, |last_auto_index| last_auto_index % columns + 1)
1370 });
1371 if end.is_some_and(|end| end.get() < start) {
1372 bail!(span, "line cannot end before it starts");
1373 }
1374 let line = Line { index: x, start, end, stroke, position };
1375
1376 // For consistency with hlines, we only push vlines to
1377 // the final vector of vlines after processing every
1378 // cell.
1379 pending_vlines.push((span, line));
1380 continue;
1381 }
1382 ResolvableGridItem::Cell(cell) => cell,
1383 };
1384 let cell_span = cell.span();
1385 let colspan = cell.colspan(self.styles).get();
1386 let rowspan = cell.rowspan(self.styles).get();
1387 // Let's calculate the cell's final position based on its
1388 // requested position.
1389 let resolved_index = {
1390 let cell_x = cell.x(self.styles);
1391 let cell_y = cell.y(self.styles);
1392 had_auto_cells |= cell_x.is_auto() && cell_y.is_auto();
1393 resolve_cell_position(
1394 cell_x,
1395 cell_y,
1396 colspan,
1397 rowspan,
1398 header_rows,
1399 headers,
1400 footer.as_ref(),
1401 resolved_cells,
1402 local_auto_index,
1403 first_available_row,
1404 columns,
1405 row_group_data.is_some(),
1406 )
1407 .at(cell_span)?
1408 };
1409 let x = resolved_index % columns;
1410 let y = resolved_index / columns;
1411
1412 if colspan > columns - x {
1413 bail!(
1414 cell_span,
1415 "cell's colspan would cause it to exceed the available column(s)";
1416 hint: "try placing the cell in another position or reducing its colspan"
1417 )
1418 }
1419
1420 let Some(largest_index) = columns
1421 .checked_mul(rowspan - 1)
1422 .and_then(|full_rowspan_offset| {
1423 resolved_index.checked_add(full_rowspan_offset)
1424 })
1425 .and_then(|last_row_pos| last_row_pos.checked_add(colspan - 1))
1426 else {
1427 bail!(
1428 cell_span,
1429 "cell would span an exceedingly large position";
1430 hint: "try reducing the cell's rowspan or colspan"
1431 )
1432 };
1433
1434 // Cell's header or footer must expand to include the cell's
1435 // occupied positions, if possible.
1436 if let Some(RowGroupData {
1437 range: group_range, kind, top_hlines_end, ..
1438 }) = &mut row_group_data
1439 {
1440 *group_range = Some(
1441 expand_row_group(
1442 resolved_cells,
1443 group_range.as_ref(),
1444 *kind,
1445 first_available_row,
1446 y,
1447 rowspan,
1448 columns,
1449 )
1450 .at(cell_span)?,
1451 );
1452
1453 if top_hlines_end.is_none()
1454 && *local_auto_index > first_available_row * columns
1455 {
1456 // Auto index was moved, so upcoming auto-pos hlines should
1457 // no longer appear at the top.
1458 *top_hlines_end = Some(pending_hlines.len());
1459 }
1460 }
1461
1462 // Let's resolve the cell so it can determine its own fields
1463 // based on its final position.
1464 let cell = self.resolve_cell(cell, x, y, rowspan, cell_kind)?;
1465
1466 if largest_index >= resolved_cells.len() {
1467 // Ensure the length of the vector of resolved cells is
1468 // always a multiple of 'columns' by pushing full rows every
1469 // time. Here, we add enough absent positions (later
1470 // converted to empty cells) to ensure the last row in the
1471 // new vector length is completely filled. This is
1472 // necessary so that those positions, even if not
1473 // explicitly used at the end, are eventually susceptible
1474 // to show rules and receive grid styling, as they will be
1475 // resolved as empty cells in a second loop below.
1476 let Some(new_len) = largest_index
1477 .checked_add(1)
1478 .and_then(|new_len| new_len.checked_next_multiple_of(columns))
1479 else {
1480 bail!(cell_span, "cell position too large")
1481 };
1482
1483 // Here, the cell needs to be placed in a position which
1484 // doesn't exist yet in the grid (out of bounds). We will
1485 // add enough absent positions for this to be possible.
1486 // They must be absent as no cells actually occupy them
1487 // (they can be overridden later); however, if no cells
1488 // occupy them as we finish building the grid, then such
1489 // positions will be replaced by empty cells.
1490 resolved_cells.resize_with(new_len, || None);
1491 }
1492
1493 // The vector is large enough to contain the cell, so we can
1494 // just index it directly to access the position it will be
1495 // placed in. However, we still need to ensure we won't try to
1496 // place a cell where there already is one.
1497 let slot = &mut resolved_cells[resolved_index];
1498 if slot.is_some() {
1499 bail!(
1500 cell_span,
1501 "attempted to place a second cell at column {x}, row {y}";
1502 hint: "try specifying your cells in a different order"
1503 );
1504 }
1505
1506 *slot = Some(Entry::Cell(cell));
1507
1508 // Now, if the cell spans more than one row or column, we fill
1509 // the spanned positions in the grid with Entry::Merged
1510 // pointing to the original cell as its parent.
1511 for rowspan_offset in 0..rowspan {
1512 let spanned_y = y + rowspan_offset;
1513 let first_row_index = resolved_index + columns * rowspan_offset;
1514 for (colspan_offset, slot) in
1515 resolved_cells[first_row_index..][..colspan].iter_mut().enumerate()
1516 {
1517 let spanned_x = x + colspan_offset;
1518 if spanned_x == x && spanned_y == y {
1519 // This is the parent cell.
1520 continue;
1521 }
1522 if slot.is_some() {
1523 bail!(
1524 cell_span,
1525 "cell would span a previously placed cell at column {spanned_x}, row {spanned_y}";
1526 hint: "try specifying your cells in a different order or reducing the cell's rowspan or colspan"
1527 )
1528 }
1529 *slot = Some(Entry::Merged { parent: resolved_index });
1530 }
1531 }
1532 }
1533
1534 if let Some(row_group) = row_group_data {
1535 let group_range = match row_group.range {
1536 Some(group_range) => group_range,
1537
1538 None => {
1539 // Empty header/footer: consider the header/footer to be
1540 // automatically positioned at the next empty row after the
1541 // latest auto index.
1542 *local_auto_index = first_available_row * columns;
1543 had_auto_cells = true;
1544 let group_start = first_available_row;
1545 let group_end = group_start + 1;
1546
1547 if resolved_cells.len() <= columns * group_start {
1548 // Ensure the automatically chosen row actually exists.
1549 resolved_cells.resize_with(columns * (group_start + 1), || None);
1550 }
1551
1552 // Even though this header or footer is fully empty, we add one
1553 // default cell to maintain the invariant that each header and
1554 // footer has at least one 'Some(...)' cell at its first row
1555 // and at least one at its last row (here they are the same
1556 // row, of course). This invariant is important to ensure
1557 // 'find_next_empty_row' will skip through any existing headers
1558 // and footers without having to loop through them each time.
1559 // Cells themselves, unfortunately, still have to.
1560 assert!(resolved_cells[*local_auto_index].is_none());
1561 let kind = match row_group.kind {
1562 RowGroupKind::Header => TableCellKind::Header(
1563 NonZeroU32::ONE,
1564 TableHeaderScope::default(),
1565 ),
1566 RowGroupKind::Footer => TableCellKind::Footer,
1567 };
1568 resolved_cells[*local_auto_index] =
1569 Some(Entry::Cell(self.resolve_cell(
1570 T::default(),
1571 0,
1572 first_available_row,
1573 1,
1574 Smart::Custom(kind),
1575 )?));
1576
1577 group_start..group_end
1578 }
1579 };
1580
1581 let top_hlines_end = row_group.top_hlines_end.unwrap_or(pending_hlines.len());
1582 for (_, top_hline, has_auto_y) in pending_hlines
1583 .get_mut(row_group.top_hlines_start..top_hlines_end)
1584 .unwrap_or(&mut [])
1585 {
1586 if *has_auto_y {
1587 // Move this hline to the top of the child, as it was
1588 // placed before the first automatically positioned cell
1589 // and had an automatic index.
1590 top_hline.index = group_range.start;
1591 }
1592 }
1593
1594 if had_auto_cells {
1595 // Header/footer was automatically positioned (either by having
1596 // auto cells or by virtue of being empty), so trigger a
1597 // rowbreak. Move auto index counter right below it.
1598 *auto_index = group_range.end * columns;
1599 }
1600
1601 match row_group.kind {
1602 RowGroupKind::Header => {
1603 let data = Header {
1604 // Later on, we have to correct this range in case there
1605 // is gutter. But only once all cells have been analyzed
1606 // and the header has fully expanded in the fixup loop
1607 // below.
1608 range: group_range.clone(),
1609
1610 level: row_group.repeatable_level,
1611
1612 // This can only change at a later iteration, if we
1613 // find a conflicting header or footer right away.
1614 short_lived: false,
1615 };
1616
1617 headers.push(Repeatable { inner: data, repeated: row_group.repeat });
1618
1619 for row in group_range {
1620 header_rows.insert(row);
1621 }
1622 }
1623
1624 RowGroupKind::Footer => {
1625 // Only check if the footer is at the end later, once we know
1626 // the final amount of rows.
1627 *footer = Some((
1628 group_range.end,
1629 row_group.span,
1630 Footer {
1631 // Later on, we have to correct this number in case there
1632 // is gutter, but only once all cells have been analyzed
1633 // and the header's and footer's exact boundaries are
1634 // known. That is because the gutter row immediately
1635 // before the footer might not be included as part of
1636 // the footer if it is contained within the header.
1637 start: group_range.start,
1638 end: group_range.end,
1639 level: 1,
1640 },
1641 ));
1642
1643 *repeat_footer = row_group.repeat;
1644 }
1645 }
1646 }
1647
1648 Ok(())
1649 }
1650
1651 /// Fixup phase (final step in cell grid generation):
1652 ///
1653 /// 1. Replace absent entries by resolved empty cells, producing a vector
1654 /// of `Entry` from `Option<Entry>`.
1655 ///
1656 /// 2. Add enough empty cells to the end of the grid such that it has at
1657 /// least the given amount of rows (must be a multiple of `columns`,
1658 /// and all rows before the last cell must have cells, empty or not,
1659 /// even if the user didn't specify those cells).
1660 ///
1661 /// That is necessary, for example, to ensure even unspecified cells
1662 /// can be affected by show rules and grid-wide styling.
1663 fn fixup_cells<T>(
1664 &mut self,
1665 resolved_cells: Vec<Option<Entry>>,
1666 columns: usize,
1667 ) -> SourceResult<Vec<Entry>>
1668 where
1669 T: ResolvableCell + Default,
1670 {
1671 let Some(expected_total_cells) = columns.checked_mul(self.tracks.y.len()) else {
1672 bail!(self.span, "too many rows were specified");
1673 };
1674 let missing_cells = expected_total_cells.saturating_sub(resolved_cells.len());
1675
1676 resolved_cells
1677 .into_iter()
1678 .chain(std::iter::repeat_with(|| None).take(missing_cells))
1679 .enumerate()
1680 .map(|(i, cell)| {
1681 if let Some(cell) = cell {
1682 Ok(cell)
1683 } else {
1684 let x = i % columns;
1685 let y = i / columns;
1686
1687 Ok(Entry::Cell(self.resolve_cell(
1688 T::default(),
1689 x,
1690 y,
1691 1,
1692 Smart::Auto,
1693 )?))
1694 }
1695 })
1696 .collect::<SourceResult<Vec<Entry>>>()
1697 }
1698
1699 /// Takes the list of pending lines and evaluates a final list of hlines
1700 /// and vlines (in that order in the returned tuple), detecting invalid
1701 /// line positions in the process.
1702 ///
1703 /// For each line type (horizontal and vertical respectively), returns a
1704 /// vector containing one inner vector for every group of lines with the
1705 /// same index.
1706 ///
1707 /// For example, an hline above the second row (y = 1) is inside the inner
1708 /// vector at position 1 of the first vector (hlines) returned by this
1709 /// function.
1710 #[allow(clippy::type_complexity)]
1711 fn collect_lines(
1712 &self,
1713 pending_hlines: Vec<(Span, Line, bool)>,
1714 pending_vlines: Vec<(Span, Line)>,
1715 has_gutter: bool,
1716 columns: usize,
1717 row_amount: usize,
1718 ) -> SourceResult<(Vec<Vec<Line>>, Vec<Vec<Line>>)> {
1719 let mut hlines: Vec<Vec<Line>> = vec![];
1720 let mut vlines: Vec<Vec<Line>> = vec![];
1721
1722 for (line_span, line, _) in pending_hlines {
1723 let y = line.index;
1724 if y > row_amount {
1725 bail!(line_span, "cannot place horizontal line at invalid row {y}");
1726 }
1727 if y == row_amount && line.position == LinePosition::After {
1728 bail!(
1729 line_span,
1730 "cannot place horizontal line at the 'bottom' position of the bottom border (y = {y})";
1731 hint: "set the line's position to 'top' or place it at a smaller 'y' index"
1732 );
1733 }
1734 let line = if line.position == LinePosition::After
1735 && (!has_gutter || y + 1 == row_amount)
1736 {
1737 // Just place the line on top of the next row if
1738 // there's no gutter and the line should be placed
1739 // after the one with given index.
1740 //
1741 // Note that placing after the last row is also the same as
1742 // just placing on the grid's bottom border, even with
1743 // gutter.
1744 Line {
1745 index: y + 1,
1746 position: LinePosition::Before,
1747 ..line
1748 }
1749 } else {
1750 line
1751 };
1752 let y = line.index;
1753
1754 if hlines.len() <= y {
1755 hlines.resize_with(y + 1, Vec::new);
1756 }
1757 hlines[y].push(line);
1758 }
1759
1760 for (line_span, line) in pending_vlines {
1761 let x = line.index;
1762 if x > columns {
1763 bail!(line_span, "cannot place vertical line at invalid column {x}");
1764 }
1765 if x == columns && line.position == LinePosition::After {
1766 bail!(
1767 line_span,
1768 "cannot place vertical line at the 'end' position of the end border (x = {columns})";
1769 hint: "set the line's position to 'start' or place it at a smaller 'x' index"
1770 );
1771 }
1772 let line = if line.position == LinePosition::After
1773 && (!has_gutter || x + 1 == columns)
1774 {
1775 // Just place the line before the next column if
1776 // there's no gutter and the line should be placed
1777 // after the one with given index.
1778 //
1779 // Note that placing after the last column is also the
1780 // same as just placing on the grid's end border, even
1781 // with gutter.
1782 Line {
1783 index: x + 1,
1784 position: LinePosition::Before,
1785 ..line
1786 }
1787 } else {
1788 line
1789 };
1790 let x = line.index;
1791
1792 if vlines.len() <= x {
1793 vlines.resize_with(x + 1, Vec::new);
1794 }
1795 vlines[x].push(line);
1796 }
1797
1798 Ok((hlines, vlines))
1799 }
1800
1801 /// Generate the final headers and footers:
1802 ///
1803 /// 1. Convert gutter-ignorant to gutter-aware indices if necessary;
1804 /// 2. Expand the header downwards (or footer upwards) to also include
1805 /// an adjacent gutter row to be repeated alongside that header or
1806 /// footer, if there is gutter;
1807 /// 3. Wrap headers and footers in the correct [`Repeatable`] variant.
1808 #[allow(clippy::type_complexity)]
1809 fn finalize_headers_and_footers(
1810 &self,
1811 has_gutter: bool,
1812 headers: &mut [Repeatable<Header>],
1813 footer: Option<(usize, Span, Footer)>,
1814 repeat_footer: bool,
1815 row_amount: usize,
1816 at_least_one_cell: bool,
1817 ) -> SourceResult<Option<Repeatable<Footer>>> {
1818 headers.sort_unstable_by_key(|h| h.range.start);
1819
1820 // Mark consecutive headers in those positions as short-lived:
1821 // (a) before a header of lower level;
1822 // (b) right before the end of the table or the final footer;
1823 // That's because they would stop repeating immediately, so don't even
1824 // attempt to.
1825 //
1826 // It is important to do this BEFORE we update header and footer ranges
1827 // due to gutter below as 'row_amount' doesn't consider gutter.
1828 //
1829 // TODO(subfooters): take the last footer if it is at the end and
1830 // backtrack through consecutive footers until the first one in the
1831 // sequence is found. If there is no footer at the end, there are no
1832 // headers to turn short-lived.
1833 let mut consecutive_header_start =
1834 footer.as_ref().map(|(_, _, f)| f.start).unwrap_or(row_amount);
1835 let mut last_consec_level = 0;
1836 for header in headers.iter_mut().rev() {
1837 if header.range.end == consecutive_header_start
1838 && header.level.get() >= last_consec_level
1839 {
1840 header.short_lived = true;
1841 } else {
1842 last_consec_level = header.level.get();
1843 }
1844
1845 consecutive_header_start = header.range.start;
1846 }
1847
1848 // Repeat the gutter below a header (hence why we don't
1849 // subtract 1 from the gutter case).
1850 // Don't do this if there are no rows under the header.
1851 if has_gutter {
1852 for header in &mut *headers {
1853 // Index of first y is doubled, as each row before it
1854 // receives a gutter row below.
1855 header.range.start *= 2;
1856
1857 // - 'header.end' is always 'last y + 1'. The header stops
1858 // before that row.
1859 // - Therefore, '2 * header.end' will be 2 * (last y + 1),
1860 // which is the adjusted index of the row before which the
1861 // header stops, meaning it will still stop right before it
1862 // even with gutter thanks to the multiplication below.
1863 // - This means that it will span all rows up to
1864 // '2 * (last y + 1) - 1 = 2 * last y + 1', which equates
1865 // to the index of the gutter row right below the header,
1866 // which is what we want (that gutter spacing should be
1867 // repeated across pages to maintain uniformity).
1868 header.range.end *= 2;
1869
1870 // If the header occupies the entire grid, ensure we don't
1871 // include an extra gutter row when it doesn't exist, since
1872 // the last row of the header is at the very bottom,
1873 // therefore '2 * last y + 1' is not a valid index.
1874 let row_amount = (2 * row_amount).saturating_sub(1);
1875 header.range.end = header.range.end.min(row_amount);
1876 }
1877 }
1878
1879 let footer = footer
1880 .map(|(footer_end, footer_span, mut footer)| {
1881 if footer_end != row_amount {
1882 bail!(footer_span, "footer must end at the last row");
1883 }
1884
1885 // TODO(subfooters): will need a global slice of headers and
1886 // footers for when we have multiple footers
1887 // Alternatively, never include the gutter in the footer's
1888 // range and manually add it later on layout. This would allow
1889 // laying out the gutter as part of both the header and footer,
1890 // and, if the page only has headers, the gutter row below the
1891 // header is automatically removed (as it becomes the last), so
1892 // only the gutter above the footer is kept, ensuring the same
1893 // gutter row isn't laid out two times in a row. When laying
1894 // out the footer for real, the mechanism can be disabled.
1895 let last_header_end = headers.last().map(|header| header.range.end);
1896
1897 if has_gutter {
1898 // Convert the footer's start index to post-gutter coordinates.
1899 footer.start *= 2;
1900
1901 // Include the gutter right before the footer, unless there is
1902 // none, or the gutter is already included in the header (no
1903 // rows between the header and the footer).
1904 if last_header_end != Some(footer.start) {
1905 footer.start = footer.start.saturating_sub(1);
1906 }
1907
1908 // Adapt footer end but DO NOT include the gutter below it,
1909 // if it exists. Calculation:
1910 // - Starts as 'last y + 1'.
1911 // - The result will be
1912 // 2 * (last_y + 1) - 1 = 2 * last_y + 1,
1913 // which is the new index of the last footer row plus one,
1914 // meaning we do exclude any gutter below this way.
1915 //
1916 // It also keeps us within the total amount of rows, so we
1917 // don't need to '.min()' later.
1918 footer.end = (2 * footer.end).saturating_sub(1);
1919 }
1920
1921 Ok(footer)
1922 })
1923 .transpose()?
1924 .map(|footer| {
1925 // Don't repeat footers when the table only has headers and
1926 // footers.
1927 // TODO(subfooters): Switch this to marking the last N
1928 // consecutive footers as short lived.
1929 Repeatable {
1930 inner: footer,
1931 repeated: repeat_footer && at_least_one_cell,
1932 }
1933 });
1934
1935 Ok(footer)
1936 }
1937
1938 /// Resolves the cell's fields based on grid-wide properties.
1939 fn resolve_cell<T>(
1940 &mut self,
1941 cell: T,
1942 x: usize,
1943 y: usize,
1944 rowspan: usize,
1945 kind: Smart<TableCellKind>,
1946 ) -> SourceResult<Cell>
1947 where
1948 T: ResolvableCell + Default,
1949 {
1950 // Resolve the breakability of a cell. Cells that span at least one
1951 // auto-sized row or gutter are considered breakable.
1952 let breakable = {
1953 let auto = Sizing::Auto;
1954 let zero = Sizing::Rel(Rel::zero());
1955 self.tracks
1956 .y
1957 .iter()
1958 .chain(std::iter::repeat(self.tracks.y.last().unwrap_or(&auto)))
1959 .skip(y)
1960 .take(rowspan)
1961 .any(|row| row == &Sizing::Auto)
1962 || self
1963 .gutter
1964 .y
1965 .iter()
1966 .chain(std::iter::repeat(self.gutter.y.last().unwrap_or(&zero)))
1967 .skip(y)
1968 .take(rowspan - 1)
1969 .any(|row_gutter| row_gutter == &Sizing::Auto)
1970 };
1971
1972 Ok(cell.resolve_cell(
1973 x,
1974 y,
1975 &self.fill.resolve(self.engine, self.styles, x, y)?,
1976 self.align.resolve(self.engine, self.styles, x, y)?,
1977 self.inset.resolve(self.engine, self.styles, x, y)?,
1978 self.stroke.resolve(self.engine, self.styles, x, y)?,
1979 breakable,
1980 self.styles,
1981 kind,
1982 ))
1983 }
1984}
1985
1986/// Given the existing range of a row group (header or footer), tries to expand
1987/// it to fit the new cell placed inside it. If the newly-expanded row group
1988/// would conflict with existing cells or other row groups, an error is
1989/// returned. Otherwise, the new `start..end` range of rows in the row group is
1990/// returned.
1991fn expand_row_group(
1992 resolved_cells: &[Option<Entry>],
1993 group_range: Option<&Range<usize>>,
1994 group_kind: RowGroupKind,
1995 first_available_row: usize,
1996 cell_y: usize,
1997 rowspan: usize,
1998 columns: usize,
1999) -> HintedStrResult<Range<usize>> {
2000 // Ensure each cell in a header or footer is fully contained within it by
2001 // expanding the header or footer towards this new cell.
2002 let (new_group_start, new_group_end) = group_range
2003 .map_or((cell_y, cell_y + rowspan), |r| {
2004 (r.start.min(cell_y), r.end.max(cell_y + rowspan))
2005 });
2006
2007 // This check might be unnecessary with the loop below, but let's keep it
2008 // here for full correctness.
2009 //
2010 // Quickly detect the case:
2011 // y = 0 => occupied
2012 // y = 1 => empty
2013 // y = 2 => header
2014 // and header tries to expand to y = 0 - invalid, as
2015 // 'y = 1' is the earliest row it can occupy.
2016 if new_group_start < first_available_row {
2017 bail!(
2018 "cell would cause {} to expand to non-empty row {}",
2019 group_kind.name(),
2020 first_available_row.saturating_sub(1);
2021 hint: "try moving its cells to available rows"
2022 );
2023 }
2024
2025 let new_rows =
2026 group_range.map_or((new_group_start..new_group_end).chain(0..0), |r| {
2027 // NOTE: 'r.end' is one row AFTER the row group's last row, so it
2028 // makes sense to check it if 'new_group_end > r.end', that is, if
2029 // the row group is going to expand. It is NOT a duplicate check,
2030 // as we hadn't checked it before (in a previous run, it was
2031 // 'new_group_end' at the exclusive end of the range)!
2032 //
2033 // NOTE: To keep types the same, we have to always return
2034 // '(range).chain(range)', which justifies chaining an empty
2035 // range above.
2036 (new_group_start..r.start).chain(r.end..new_group_end)
2037 });
2038
2039 // The check above isn't enough, however, even when the header is expanding
2040 // upwards, as it might expand upwards towards an occupied row after the
2041 // first empty row, e.g.
2042 //
2043 // y = 0 => occupied
2044 // y = 1 => empty (first_available_row = 1)
2045 // y = 2 => occupied
2046 // y = 3 => header
2047 //
2048 // Here, we should bail if the header tries to expand upwards, regardless
2049 // of the fact that the conflicting row (y = 2) comes after the first
2050 // available row.
2051 //
2052 // Note that expanding upwards is only possible when row-positioned cells
2053 // are specified, in one of the following cases:
2054 //
2055 // 1. We place e.g. 'table.cell(y: 3)' followed by 'table.cell(y: 2)'
2056 // (earlier row => upwards);
2057 //
2058 // 2. We place e.g. 'table.cell(y: 3)' followed by '[a]' (auto-pos cell
2059 // favors 'first_available_row', so the header tries to expand upwards to
2060 // place the cell at 'y = 1' and conflicts at 'y = 2') or
2061 // 'table.cell(x: 1)' (same deal).
2062 //
2063 // Of course, we also need to check for downward expansion as usual as
2064 // there could be a non-empty row below the header, but the upward case is
2065 // highlighted as it was checked separately before (and also to explain
2066 // what kind of situation we are preventing with this check).
2067 //
2068 // Note that simply checking for non-empty rows like below not only
2069 // prevents conflicts with top-level cells (outside of headers and
2070 // footers), but also prevents conflicts with other headers or footers,
2071 // since we have an invariant that even empty headers and footers must
2072 // contain at least one 'Some(...)' position in 'resolved_cells'. More
2073 // precisely, each header and footer has at least one 'Some(...)' cell at
2074 // 'group_range.start' and at 'group_range.end - 1' - non-empty headers and
2075 // footers don't span any unnecessary rows. Therefore, we don't have to
2076 // loop over headers and footers, only check if the new rows are empty.
2077 for new_y in new_rows {
2078 if let Some(new_row @ [_non_empty, ..]) = resolved_cells
2079 .get(new_y * columns..)
2080 .map(|cells| &cells[..columns.min(cells.len())])
2081 {
2082 if new_row.iter().any(Option::is_some) {
2083 bail!(
2084 "cell would cause {} to expand to non-empty row {new_y}",
2085 group_kind.name();
2086 hint: "try moving its cells to available rows",
2087 )
2088 }
2089 } else {
2090 // Received 'None' or an empty slice, so we are expanding the
2091 // header or footer into new rows, which is always valid and cannot
2092 // conflict with existing cells. (Note that we only resize
2093 // 'resolved_cells' after this function is called, so, if this
2094 // header or footer is at the bottom of the table so far, this loop
2095 // will end quite early, regardless of where this cell was placed
2096 // or of its rowspan value.)
2097 break;
2098 }
2099 }
2100
2101 Ok(new_group_start..new_group_end)
2102}
2103
2104/// Check if a cell's fixed row would conflict with a header or footer.
2105fn check_for_conflicting_cell_row(
2106 header_rows: &SmallBitSet,
2107 headers: &[Repeatable<Header>],
2108 footer: Option<&(usize, Span, Footer)>,
2109 cell_y: usize,
2110 rowspan: usize,
2111) -> HintedStrResult<()> {
2112 if !headers.is_empty()
2113 && let Some(row) =
2114 (cell_y..cell_y + rowspan).find(|&row| header_rows.contains(row))
2115 {
2116 bail!(
2117 "cell would conflict with header also spanning row {row}";
2118 hint: "try moving the cell or the header"
2119 );
2120 }
2121
2122 // NOTE: y + rowspan >, not >=, footer.start, to check if the rowspan
2123 // enters the footer. For example, consider a rowspan of 1: if
2124 // `y + 1 = footer.start` holds, that means `y < footer.start`, and it
2125 // only occupies one row (`y`), so the cell is actually not in
2126 // conflict.
2127 if let Some((_, _, footer)) = footer
2128 && cell_y < footer.end
2129 && cell_y + rowspan > footer.start
2130 {
2131 let row = (cell_y..cell_y + rowspan)
2132 .find(|row| (footer.start..footer.end).contains(row))
2133 .unwrap();
2134
2135 bail!(
2136 "cell would conflict with footer also spanning row {row}";
2137 hint: "try reducing the cell's rowspan or moving the footer"
2138 );
2139 }
2140
2141 Ok(())
2142}
2143
2144/// Given a cell's requested x and y, the vector with the resolved cell
2145/// positions, the `auto_index` counter (determines the position of the next
2146/// `(auto, auto)` cell) and the amount of columns in the grid, returns the
2147/// final index of this cell in the vector of resolved cells.
2148///
2149/// The `first_available_row` parameter is used by headers and footers to
2150/// indicate the first empty row available. Any rows before those should
2151/// not be picked by cells with `auto` row positioning, since headers and
2152/// footers occupy entire rows, and may not conflict with cells outside them.
2153#[allow(clippy::too_many_arguments)]
2154fn resolve_cell_position(
2155 cell_x: Smart<usize>,
2156 cell_y: Smart<usize>,
2157 colspan: usize,
2158 rowspan: usize,
2159 header_rows: &SmallBitSet,
2160 headers: &[Repeatable<Header>],
2161 footer: Option<&(usize, Span, Footer)>,
2162 resolved_cells: &[Option<Entry>],
2163 auto_index: &mut usize,
2164 first_available_row: usize,
2165 columns: usize,
2166 in_row_group: bool,
2167) -> HintedStrResult<usize> {
2168 // Translates a (x, y) position to the equivalent index in the final cell vector.
2169 // Errors if the position would be too large.
2170 let cell_index = |x, y: usize| {
2171 y.checked_mul(columns)
2172 .and_then(|row_index| row_index.checked_add(x))
2173 .ok_or_else(|| HintedString::from(eco_format!("cell position too large")))
2174 };
2175 match (cell_x, cell_y) {
2176 // Fully automatic cell positioning. The cell did not
2177 // request a coordinate.
2178 (Smart::Auto, Smart::Auto) => {
2179 // Let's find the first available position starting from the
2180 // automatic position counter, searching in row-major order.
2181 // Note that the counter ignores any cells with fixed positions,
2182 // but automatically-positioned cells will avoid conflicts by
2183 // simply skipping existing cells, headers and footers.
2184 let resolved_index = find_next_available_position(
2185 header_rows,
2186 footer,
2187 resolved_cells,
2188 columns,
2189 *auto_index,
2190 false,
2191 )?;
2192
2193 // Ensure the next cell with automatic position will be
2194 // placed after this one (maybe not immediately after).
2195 //
2196 // The calculation below also affects the position of the upcoming
2197 // automatically-positioned lines, as they are placed below
2198 // (horizontal lines) or to the right (vertical lines) of the cell
2199 // that would be placed at 'auto_index'.
2200 *auto_index = if colspan == columns {
2201 // The cell occupies all columns, so no cells can be placed
2202 // after it until all of its rows have been spanned.
2203 resolved_index + colspan * rowspan
2204 } else {
2205 // The next cell will have to be placed at least after its
2206 // spanned columns.
2207 resolved_index + colspan
2208 };
2209
2210 Ok(resolved_index)
2211 }
2212 // Cell has chosen at least its column.
2213 (Smart::Custom(cell_x), cell_y) => {
2214 if cell_x >= columns {
2215 return Err(HintedString::from(eco_format!(
2216 "cell could not be placed at invalid column {cell_x}"
2217 )));
2218 }
2219 if let Smart::Custom(cell_y) = cell_y {
2220 // Cell has chosen its exact position.
2221 //
2222 // Ensure it doesn't conflict with an existing header or
2223 // footer (but only if it isn't already in one, otherwise there
2224 // will already be a separate check).
2225 if !in_row_group {
2226 check_for_conflicting_cell_row(
2227 header_rows,
2228 headers,
2229 footer,
2230 cell_y,
2231 rowspan,
2232 )?;
2233 }
2234
2235 cell_index(cell_x, cell_y)
2236 } else {
2237 // Cell has only chosen its column.
2238 // Let's find the first row which has that column available.
2239 // If in a header or footer, start searching by the first empty
2240 // row / the header or footer's first row (specified through
2241 // 'first_available_row'). Otherwise, start searching at the
2242 // first row.
2243 let initial_index = cell_index(cell_x, first_available_row)?;
2244
2245 // Try each row until either we reach an absent position at the
2246 // requested column ('Some(None)') or an out of bounds position
2247 // ('None'), in which case we'd create a new row to place this
2248 // cell in.
2249 find_next_available_position(
2250 header_rows,
2251 footer,
2252 resolved_cells,
2253 columns,
2254 initial_index,
2255 true,
2256 )
2257 }
2258 }
2259 // Cell has only chosen its row, not its column.
2260 (Smart::Auto, Smart::Custom(cell_y)) => {
2261 // Ensure it doesn't conflict with an existing header or
2262 // footer (but only if it isn't already in one, otherwise there
2263 // will already be a separate check).
2264 if !in_row_group {
2265 check_for_conflicting_cell_row(
2266 header_rows,
2267 headers,
2268 footer,
2269 cell_y,
2270 rowspan,
2271 )?;
2272 }
2273
2274 // Let's find the first column which has that row available.
2275 let first_row_pos = cell_index(0, cell_y)?;
2276 let last_row_pos = first_row_pos
2277 .checked_add(columns)
2278 .ok_or_else(|| eco_format!("cell position too large"))?;
2279
2280 (first_row_pos..last_row_pos)
2281 .find(|possible_index| {
2282 // Much like in the previous cases, we skip any occupied
2283 // positions until we either reach an absent position
2284 // (`Some(None)`) or an out of bounds position (`None`),
2285 // in which case we can just expand the vector enough to
2286 // place this cell. In either case, we found an available
2287 // position.
2288 !matches!(resolved_cells.get(*possible_index), Some(Some(_)))
2289 })
2290 .ok_or_else(|| {
2291 eco_format!(
2292 "cell could not be placed in row {cell_y} because it was full"
2293 )
2294 })
2295 .hint("try specifying your cells in a different order")
2296 }
2297 }
2298}
2299
2300/// Finds the first available position after the initial index in the resolved
2301/// grid of cells. Skips any non-absent positions (positions which already
2302/// have cells specified by the user) as well as any headers and footers.
2303///
2304/// When `skip_rows` is true, one row is skipped on each iteration, preserving
2305/// the column. That is used to find a position for a fixed column cell.
2306#[inline]
2307fn find_next_available_position(
2308 header_rows: &SmallBitSet,
2309 footer: Option<&(usize, Span, Footer)>,
2310 resolved_cells: &[Option<Entry>],
2311 columns: usize,
2312 initial_index: usize,
2313 skip_rows: bool,
2314) -> HintedStrResult<usize> {
2315 let mut resolved_index = initial_index;
2316
2317 loop {
2318 if let Some(Some(_)) = resolved_cells.get(resolved_index) {
2319 // Skip any non-absent cell positions (`Some(None)`) to
2320 // determine where this cell will be placed. An out of
2321 // bounds position (thus `None`) is also a valid new
2322 // position (only requires expanding the vector).
2323 if skip_rows {
2324 // Skip one row at a time (cell chose its column, so we don't
2325 // change it).
2326 resolved_index =
2327 resolved_index.checked_add(columns).ok_or_else(|| {
2328 HintedString::from(eco_format!("cell position too large"))
2329 })?;
2330 } else {
2331 // Ensure we don't run unnecessary checks in the hot path
2332 // (for fully automatically-positioned cells). Memory usage
2333 // would become impractically large before this overflows.
2334 resolved_index += 1;
2335 }
2336 } else if header_rows.contains(resolved_index / columns) {
2337 // Skip header rows (can't place a cell inside it from outside it).
2338 if skip_rows {
2339 // Ensure the cell's chosen column is kept after the header.
2340 resolved_index += columns;
2341 } else {
2342 // Skip to the start of the next row.
2343 //
2344 // Add 1 to resolved index to force moving to the next row if
2345 // this is at the start of one. At the end of one, '+ 1'
2346 // already pushes to the next one and 'next_multiple_of' does
2347 // not modify it, so nothing bad happens then either.
2348 resolved_index = (resolved_index + 1).next_multiple_of(columns);
2349 }
2350 } else if let Some((footer_end, _, footer)) = footer
2351 && resolved_index >= footer.start * columns
2352 && resolved_index < *footer_end * columns
2353 {
2354 // Skip footer, for the same reason.
2355 resolved_index = *footer_end * columns;
2356
2357 if skip_rows {
2358 // Ensure the cell's chosen column is kept after the
2359 // footer.
2360 resolved_index += initial_index % columns;
2361 }
2362 } else {
2363 return Ok(resolved_index);
2364 }
2365 }
2366}
2367
2368/// Computes the `y` of the next available empty row, given the auto index as
2369/// an initial index for search, since we know that there are no empty rows
2370/// before automatically-positioned cells, as they are placed sequentially.
2371fn find_next_empty_row(
2372 resolved_cells: &[Option<Entry>],
2373 auto_index: usize,
2374 columns: usize,
2375) -> usize {
2376 let mut resolved_index = auto_index.next_multiple_of(columns);
2377 while resolved_cells
2378 .get(resolved_index..resolved_index + columns)
2379 .is_some_and(|row| row.iter().any(Option::is_some))
2380 {
2381 // Skip non-empty rows.
2382 resolved_index += columns;
2383 }
2384
2385 resolved_index / columns
2386}
2387
2388/// Fully merged rows under the cell of latest auto index indicate rowspans
2389/// occupying all columns, so we skip the auto index until the shortest rowspan
2390/// ends, such that, in the resulting row, we will be able to place an
2391/// automatically positioned cell - and, in particular, hlines under it. The
2392/// idea is that an auto hline will be placed after the shortest such rowspan.
2393/// Otherwise, the hline would just be placed under the first row of those
2394/// rowspans and disappear (except at the presence of column gutter).
2395fn skip_auto_index_through_fully_merged_rows(
2396 resolved_cells: &[Option<Entry>],
2397 auto_index: &mut usize,
2398 columns: usize,
2399) {
2400 // If the auto index isn't currently at the start of a row, that means
2401 // there's still at least one auto position left in the row, ignoring
2402 // cells with manual positions, so we wouldn't have a problem in placing
2403 // further cells or, in this case, hlines here.
2404 if *auto_index % columns == 0 {
2405 while resolved_cells
2406 .get(*auto_index..*auto_index + columns)
2407 .is_some_and(|row| {
2408 row.iter().all(|entry| matches!(entry, Some(Entry::Merged { .. })))
2409 })
2410 {
2411 *auto_index += columns;
2412 }
2413 }
2414}