Skip to main content

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 \
1417                           colspan";
1418                )
1419            }
1420
1421            let Some(largest_index) = columns
1422                .checked_mul(rowspan - 1)
1423                .and_then(|full_rowspan_offset| {
1424                    resolved_index.checked_add(full_rowspan_offset)
1425                })
1426                .and_then(|last_row_pos| last_row_pos.checked_add(colspan - 1))
1427            else {
1428                bail!(
1429                    cell_span,
1430                    "cell would span an exceedingly large position";
1431                    hint: "try reducing the cell's rowspan or colspan";
1432                )
1433            };
1434
1435            // Cell's header or footer must expand to include the cell's
1436            // occupied positions, if possible.
1437            if let Some(RowGroupData {
1438                range: group_range, kind, top_hlines_end, ..
1439            }) = &mut row_group_data
1440            {
1441                *group_range = Some(
1442                    expand_row_group(
1443                        resolved_cells,
1444                        group_range.as_ref(),
1445                        *kind,
1446                        first_available_row,
1447                        y,
1448                        rowspan,
1449                        columns,
1450                    )
1451                    .at(cell_span)?,
1452                );
1453
1454                if top_hlines_end.is_none()
1455                    && *local_auto_index > first_available_row * columns
1456                {
1457                    // Auto index was moved, so upcoming auto-pos hlines should
1458                    // no longer appear at the top.
1459                    *top_hlines_end = Some(pending_hlines.len());
1460                }
1461            }
1462
1463            // Let's resolve the cell so it can determine its own fields
1464            // based on its final position.
1465            let cell = self.resolve_cell(cell, x, y, rowspan, cell_kind)?;
1466
1467            if largest_index >= resolved_cells.len() {
1468                // Ensure the length of the vector of resolved cells is
1469                // always a multiple of 'columns' by pushing full rows every
1470                // time. Here, we add enough absent positions (later
1471                // converted to empty cells) to ensure the last row in the
1472                // new vector length is completely filled. This is
1473                // necessary so that those positions, even if not
1474                // explicitly used at the end, are eventually susceptible
1475                // to show rules and receive grid styling, as they will be
1476                // resolved as empty cells in a second loop below.
1477                let Some(new_len) = largest_index
1478                    .checked_add(1)
1479                    .and_then(|new_len| new_len.checked_next_multiple_of(columns))
1480                else {
1481                    bail!(cell_span, "cell position too large")
1482                };
1483
1484                // Here, the cell needs to be placed in a position which
1485                // doesn't exist yet in the grid (out of bounds). We will
1486                // add enough absent positions for this to be possible.
1487                // They must be absent as no cells actually occupy them
1488                // (they can be overridden later); however, if no cells
1489                // occupy them as we finish building the grid, then such
1490                // positions will be replaced by empty cells.
1491                resolved_cells.resize_with(new_len, || None);
1492            }
1493
1494            // The vector is large enough to contain the cell, so we can
1495            // just index it directly to access the position it will be
1496            // placed in. However, we still need to ensure we won't try to
1497            // place a cell where there already is one.
1498            let slot = &mut resolved_cells[resolved_index];
1499            if slot.is_some() {
1500                bail!(
1501                    cell_span,
1502                    "attempted to place a second cell at column {x}, row {y}";
1503                    hint: "try specifying your cells in a different order";
1504                );
1505            }
1506
1507            *slot = Some(Entry::Cell(cell));
1508
1509            // Now, if the cell spans more than one row or column, we fill
1510            // the spanned positions in the grid with Entry::Merged
1511            // pointing to the original cell as its parent.
1512            for rowspan_offset in 0..rowspan {
1513                let spanned_y = y + rowspan_offset;
1514                let first_row_index = resolved_index + columns * rowspan_offset;
1515                for (colspan_offset, slot) in
1516                    resolved_cells[first_row_index..][..colspan].iter_mut().enumerate()
1517                {
1518                    let spanned_x = x + colspan_offset;
1519                    if spanned_x == x && spanned_y == y {
1520                        // This is the parent cell.
1521                        continue;
1522                    }
1523                    if slot.is_some() {
1524                        bail!(
1525                            cell_span,
1526                            "cell would span a previously placed cell at column \
1527                             {spanned_x}, row {spanned_y}";
1528                            hint: "try specifying your cells in a different order or \
1529                                   reducing the cell's rowspan or colspan";
1530                        )
1531                    }
1532                    *slot = Some(Entry::Merged { parent: resolved_index });
1533                }
1534            }
1535        }
1536
1537        if let Some(row_group) = row_group_data {
1538            let group_range = match row_group.range {
1539                Some(group_range) => group_range,
1540
1541                None => {
1542                    // Empty header/footer: consider the header/footer to be
1543                    // automatically positioned at the next empty row after the
1544                    // latest auto index.
1545                    *local_auto_index = first_available_row * columns;
1546                    had_auto_cells = true;
1547                    let group_start = first_available_row;
1548                    let group_end = group_start + 1;
1549
1550                    if resolved_cells.len() <= columns * group_start {
1551                        // Ensure the automatically chosen row actually exists.
1552                        resolved_cells.resize_with(columns * (group_start + 1), || None);
1553                    }
1554
1555                    // Even though this header or footer is fully empty, we add one
1556                    // default cell to maintain the invariant that each header and
1557                    // footer has at least one 'Some(...)' cell at its first row
1558                    // and at least one at its last row (here they are the same
1559                    // row, of course). This invariant is important to ensure
1560                    // 'find_next_empty_row' will skip through any existing headers
1561                    // and footers without having to loop through them each time.
1562                    // Cells themselves, unfortunately, still have to.
1563                    assert!(resolved_cells[*local_auto_index].is_none());
1564                    let kind = match row_group.kind {
1565                        RowGroupKind::Header => TableCellKind::Header(
1566                            NonZeroU32::ONE,
1567                            TableHeaderScope::default(),
1568                        ),
1569                        RowGroupKind::Footer => TableCellKind::Footer,
1570                    };
1571                    resolved_cells[*local_auto_index] =
1572                        Some(Entry::Cell(self.resolve_cell(
1573                            T::default(),
1574                            0,
1575                            first_available_row,
1576                            1,
1577                            Smart::Custom(kind),
1578                        )?));
1579
1580                    group_start..group_end
1581                }
1582            };
1583
1584            let top_hlines_end = row_group.top_hlines_end.unwrap_or(pending_hlines.len());
1585            for (_, top_hline, has_auto_y) in pending_hlines
1586                .get_mut(row_group.top_hlines_start..top_hlines_end)
1587                .unwrap_or(&mut [])
1588            {
1589                if *has_auto_y {
1590                    // Move this hline to the top of the child, as it was
1591                    // placed before the first automatically positioned cell
1592                    // and had an automatic index.
1593                    top_hline.index = group_range.start;
1594                }
1595            }
1596
1597            if had_auto_cells {
1598                // Header/footer was automatically positioned (either by having
1599                // auto cells or by virtue of being empty), so trigger a
1600                // rowbreak. Move auto index counter right below it.
1601                *auto_index = group_range.end * columns;
1602            }
1603
1604            match row_group.kind {
1605                RowGroupKind::Header => {
1606                    let data = Header {
1607                        // Later on, we have to correct this range in case there
1608                        // is gutter. But only once all cells have been analyzed
1609                        // and the header has fully expanded in the fixup loop
1610                        // below.
1611                        range: group_range.clone(),
1612
1613                        level: row_group.repeatable_level,
1614
1615                        // This can only change at a later iteration, if we
1616                        // find a conflicting header or footer right away.
1617                        short_lived: false,
1618                    };
1619
1620                    headers.push(Repeatable { inner: data, repeated: row_group.repeat });
1621
1622                    for row in group_range {
1623                        header_rows.insert(row);
1624                    }
1625                }
1626
1627                RowGroupKind::Footer => {
1628                    // Only check if the footer is at the end later, once we know
1629                    // the final amount of rows.
1630                    *footer = Some((
1631                        group_range.end,
1632                        row_group.span,
1633                        Footer {
1634                            // Later on, we have to correct this number in case there
1635                            // is gutter, but only once all cells have been analyzed
1636                            // and the header's and footer's exact boundaries are
1637                            // known. That is because the gutter row immediately
1638                            // before the footer might not be included as part of
1639                            // the footer if it is contained within the header.
1640                            start: group_range.start,
1641                            end: group_range.end,
1642                            level: 1,
1643                        },
1644                    ));
1645
1646                    *repeat_footer = row_group.repeat;
1647                }
1648            }
1649        }
1650
1651        Ok(())
1652    }
1653
1654    /// Fixup phase (final step in cell grid generation):
1655    ///
1656    /// 1. Replace absent entries by resolved empty cells, producing a vector
1657    ///    of `Entry` from `Option<Entry>`.
1658    ///
1659    /// 2. Add enough empty cells to the end of the grid such that it has at
1660    ///    least the given amount of rows (must be a multiple of `columns`,
1661    ///    and all rows before the last cell must have cells, empty or not,
1662    ///    even if the user didn't specify those cells).
1663    ///
1664    ///    That is necessary, for example, to ensure even unspecified cells
1665    ///    can be affected by show rules and grid-wide styling.
1666    fn fixup_cells<T>(
1667        &mut self,
1668        resolved_cells: Vec<Option<Entry>>,
1669        columns: usize,
1670    ) -> SourceResult<Vec<Entry>>
1671    where
1672        T: ResolvableCell + Default,
1673    {
1674        let Some(expected_total_cells) = columns.checked_mul(self.tracks.y.len()) else {
1675            bail!(self.span, "too many rows were specified");
1676        };
1677        let missing_cells = expected_total_cells.saturating_sub(resolved_cells.len());
1678
1679        resolved_cells
1680            .into_iter()
1681            .chain(std::iter::repeat_with(|| None).take(missing_cells))
1682            .enumerate()
1683            .map(|(i, cell)| {
1684                if let Some(cell) = cell {
1685                    Ok(cell)
1686                } else {
1687                    let x = i % columns;
1688                    let y = i / columns;
1689
1690                    Ok(Entry::Cell(self.resolve_cell(
1691                        T::default(),
1692                        x,
1693                        y,
1694                        1,
1695                        Smart::Auto,
1696                    )?))
1697                }
1698            })
1699            .collect::<SourceResult<Vec<Entry>>>()
1700    }
1701
1702    /// Takes the list of pending lines and evaluates a final list of hlines
1703    /// and vlines (in that order in the returned tuple), detecting invalid
1704    /// line positions in the process.
1705    ///
1706    /// For each line type (horizontal and vertical respectively), returns a
1707    /// vector containing one inner vector for every group of lines with the
1708    /// same index.
1709    ///
1710    /// For example, an hline above the second row (y = 1) is inside the inner
1711    /// vector at position 1 of the first vector (hlines) returned by this
1712    /// function.
1713    #[allow(clippy::type_complexity)]
1714    fn collect_lines(
1715        &self,
1716        pending_hlines: Vec<(Span, Line, bool)>,
1717        pending_vlines: Vec<(Span, Line)>,
1718        has_gutter: bool,
1719        columns: usize,
1720        row_amount: usize,
1721    ) -> SourceResult<(Vec<Vec<Line>>, Vec<Vec<Line>>)> {
1722        let mut hlines: Vec<Vec<Line>> = vec![];
1723        let mut vlines: Vec<Vec<Line>> = vec![];
1724
1725        for (line_span, line, _) in pending_hlines {
1726            let y = line.index;
1727            if y > row_amount {
1728                bail!(line_span, "cannot place horizontal line at invalid row {y}");
1729            }
1730            if y == row_amount && line.position == LinePosition::After {
1731                bail!(
1732                    line_span,
1733                    "cannot place horizontal line at the 'bottom' position of the \
1734                     bottom border (y = {y})";
1735                    hint: "set the line's position to 'top' or place it at a smaller \
1736                           'y' index";
1737                );
1738            }
1739            let line = if line.position == LinePosition::After
1740                && (!has_gutter || y + 1 == row_amount)
1741            {
1742                // Just place the line on top of the next row if
1743                // there's no gutter and the line should be placed
1744                // after the one with given index.
1745                //
1746                // Note that placing after the last row is also the same as
1747                // just placing on the grid's bottom border, even with
1748                // gutter.
1749                Line {
1750                    index: y + 1,
1751                    position: LinePosition::Before,
1752                    ..line
1753                }
1754            } else {
1755                line
1756            };
1757            let y = line.index;
1758
1759            if hlines.len() <= y {
1760                hlines.resize_with(y + 1, Vec::new);
1761            }
1762            hlines[y].push(line);
1763        }
1764
1765        for (line_span, line) in pending_vlines {
1766            let x = line.index;
1767            if x > columns {
1768                bail!(line_span, "cannot place vertical line at invalid column {x}");
1769            }
1770            if x == columns && line.position == LinePosition::After {
1771                bail!(
1772                    line_span,
1773                    "cannot place vertical line at the 'end' position of the end border \
1774                     (x = {columns})";
1775                    hint: "set the line's position to 'start' or place it at a smaller \
1776                           'x' index";
1777                );
1778            }
1779            let line = if line.position == LinePosition::After
1780                && (!has_gutter || x + 1 == columns)
1781            {
1782                // Just place the line before the next column if
1783                // there's no gutter and the line should be placed
1784                // after the one with given index.
1785                //
1786                // Note that placing after the last column is also the
1787                // same as just placing on the grid's end border, even
1788                // with gutter.
1789                Line {
1790                    index: x + 1,
1791                    position: LinePosition::Before,
1792                    ..line
1793                }
1794            } else {
1795                line
1796            };
1797            let x = line.index;
1798
1799            if vlines.len() <= x {
1800                vlines.resize_with(x + 1, Vec::new);
1801            }
1802            vlines[x].push(line);
1803        }
1804
1805        Ok((hlines, vlines))
1806    }
1807
1808    /// Generate the final headers and footers:
1809    ///
1810    /// 1. Convert gutter-ignorant to gutter-aware indices if necessary;
1811    /// 2. Expand the header downwards (or footer upwards) to also include
1812    ///    an adjacent gutter row to be repeated alongside that header or
1813    ///    footer, if there is gutter;
1814    /// 3. Wrap headers and footers in the correct [`Repeatable`] variant.
1815    #[allow(clippy::type_complexity)]
1816    fn finalize_headers_and_footers(
1817        &self,
1818        has_gutter: bool,
1819        headers: &mut [Repeatable<Header>],
1820        footer: Option<(usize, Span, Footer)>,
1821        repeat_footer: bool,
1822        row_amount: usize,
1823        at_least_one_cell: bool,
1824    ) -> SourceResult<Option<Repeatable<Footer>>> {
1825        headers.sort_unstable_by_key(|h| h.range.start);
1826
1827        // Mark consecutive headers in those positions as short-lived:
1828        // (a) before a header of lower level;
1829        // (b) right before the end of the table or the final footer;
1830        // That's because they would stop repeating immediately, so don't even
1831        // attempt to.
1832        //
1833        // It is important to do this BEFORE we update header and footer ranges
1834        // due to gutter below as 'row_amount' doesn't consider gutter.
1835        //
1836        // TODO(subfooters): take the last footer if it is at the end and
1837        // backtrack through consecutive footers until the first one in the
1838        // sequence is found. If there is no footer at the end, there are no
1839        // headers to turn short-lived.
1840        let mut consecutive_header_start =
1841            footer.as_ref().map(|(_, _, f)| f.start).unwrap_or(row_amount);
1842        let mut last_consec_level = 0;
1843        for header in headers.iter_mut().rev() {
1844            if header.range.end == consecutive_header_start
1845                && header.level.get() >= last_consec_level
1846            {
1847                header.short_lived = true;
1848            } else {
1849                last_consec_level = header.level.get();
1850            }
1851
1852            consecutive_header_start = header.range.start;
1853        }
1854
1855        // Repeat the gutter below a header (hence why we don't
1856        // subtract 1 from the gutter case).
1857        // Don't do this if there are no rows under the header.
1858        if has_gutter {
1859            for header in &mut *headers {
1860                // Index of first y is doubled, as each row before it
1861                // receives a gutter row below.
1862                header.range.start *= 2;
1863
1864                // - 'header.end' is always 'last y + 1'. The header stops
1865                // before that row.
1866                // - Therefore, '2 * header.end' will be 2 * (last y + 1),
1867                // which is the adjusted index of the row before which the
1868                // header stops, meaning it will still stop right before it
1869                // even with gutter thanks to the multiplication below.
1870                // - This means that it will span all rows up to
1871                // '2 * (last y + 1) - 1 = 2 * last y + 1', which equates
1872                // to the index of the gutter row right below the header,
1873                // which is what we want (that gutter spacing should be
1874                // repeated across pages to maintain uniformity).
1875                header.range.end *= 2;
1876
1877                // If the header occupies the entire grid, ensure we don't
1878                // include an extra gutter row when it doesn't exist, since
1879                // the last row of the header is at the very bottom,
1880                // therefore '2 * last y + 1' is not a valid index.
1881                let row_amount = (2 * row_amount).saturating_sub(1);
1882                header.range.end = header.range.end.min(row_amount);
1883            }
1884        }
1885
1886        let footer = footer
1887            .map(|(footer_end, footer_span, mut footer)| {
1888                if footer_end != row_amount {
1889                    bail!(footer_span, "footer must end at the last row");
1890                }
1891
1892                // TODO(subfooters): will need a global slice of headers and
1893                // footers for when we have multiple footers
1894                // Alternatively, never include the gutter in the footer's
1895                // range and manually add it later on layout. This would allow
1896                // laying out the gutter as part of both the header and footer,
1897                // and, if the page only has headers, the gutter row below the
1898                // header is automatically removed (as it becomes the last), so
1899                // only the gutter above the footer is kept, ensuring the same
1900                // gutter row isn't laid out two times in a row. When laying
1901                // out the footer for real, the mechanism can be disabled.
1902                let last_header_end = headers.last().map(|header| header.range.end);
1903
1904                if has_gutter {
1905                    // Convert the footer's start index to post-gutter coordinates.
1906                    footer.start *= 2;
1907
1908                    // Include the gutter right before the footer, unless there is
1909                    // none, or the gutter is already included in the header (no
1910                    // rows between the header and the footer).
1911                    if last_header_end != Some(footer.start) {
1912                        footer.start = footer.start.saturating_sub(1);
1913                    }
1914
1915                    // Adapt footer end but DO NOT include the gutter below it,
1916                    // if it exists. Calculation:
1917                    // - Starts as 'last y + 1'.
1918                    // - The result will be
1919                    // 2 * (last_y + 1) - 1 = 2 * last_y + 1,
1920                    // which is the new index of the last footer row plus one,
1921                    // meaning we do exclude any gutter below this way.
1922                    //
1923                    // It also keeps us within the total amount of rows, so we
1924                    // don't need to '.min()' later.
1925                    footer.end = (2 * footer.end).saturating_sub(1);
1926                }
1927
1928                Ok(footer)
1929            })
1930            .transpose()?
1931            .map(|footer| {
1932                // Don't repeat footers when the table only has headers and
1933                // footers.
1934                // TODO(subfooters): Switch this to marking the last N
1935                // consecutive footers as short lived.
1936                Repeatable {
1937                    inner: footer,
1938                    repeated: repeat_footer && at_least_one_cell,
1939                }
1940            });
1941
1942        Ok(footer)
1943    }
1944
1945    /// Resolves the cell's fields based on grid-wide properties.
1946    fn resolve_cell<T>(
1947        &mut self,
1948        cell: T,
1949        x: usize,
1950        y: usize,
1951        rowspan: usize,
1952        kind: Smart<TableCellKind>,
1953    ) -> SourceResult<Cell>
1954    where
1955        T: ResolvableCell + Default,
1956    {
1957        // Resolve the breakability of a cell. Cells that span at least one
1958        // auto-sized row or gutter are considered breakable.
1959        let breakable = {
1960            let auto = Sizing::Auto;
1961            let zero = Sizing::Rel(Rel::zero());
1962            self.tracks
1963                .y
1964                .iter()
1965                .chain(std::iter::repeat(self.tracks.y.last().unwrap_or(&auto)))
1966                .skip(y)
1967                .take(rowspan)
1968                .any(|row| row == &Sizing::Auto)
1969                || self
1970                    .gutter
1971                    .y
1972                    .iter()
1973                    .chain(std::iter::repeat(self.gutter.y.last().unwrap_or(&zero)))
1974                    .skip(y)
1975                    .take(rowspan - 1)
1976                    .any(|row_gutter| row_gutter == &Sizing::Auto)
1977        };
1978
1979        Ok(cell.resolve_cell(
1980            x,
1981            y,
1982            &self.fill.resolve(self.engine, self.styles, x, y)?,
1983            self.align.resolve(self.engine, self.styles, x, y)?,
1984            self.inset.resolve(self.engine, self.styles, x, y)?,
1985            self.stroke.resolve(self.engine, self.styles, x, y)?,
1986            breakable,
1987            self.styles,
1988            kind,
1989        ))
1990    }
1991}
1992
1993/// Given the existing range of a row group (header or footer), tries to expand
1994/// it to fit the new cell placed inside it. If the newly-expanded row group
1995/// would conflict with existing cells or other row groups, an error is
1996/// returned. Otherwise, the new `start..end` range of rows in the row group is
1997/// returned.
1998fn expand_row_group(
1999    resolved_cells: &[Option<Entry>],
2000    group_range: Option<&Range<usize>>,
2001    group_kind: RowGroupKind,
2002    first_available_row: usize,
2003    cell_y: usize,
2004    rowspan: usize,
2005    columns: usize,
2006) -> HintedStrResult<Range<usize>> {
2007    // Ensure each cell in a header or footer is fully contained within it by
2008    // expanding the header or footer towards this new cell.
2009    let (new_group_start, new_group_end) = group_range
2010        .map_or((cell_y, cell_y + rowspan), |r| {
2011            (r.start.min(cell_y), r.end.max(cell_y + rowspan))
2012        });
2013
2014    // This check might be unnecessary with the loop below, but let's keep it
2015    // here for full correctness.
2016    //
2017    // Quickly detect the case:
2018    // y = 0 => occupied
2019    // y = 1 => empty
2020    // y = 2 => header
2021    // and header tries to expand to y = 0 - invalid, as
2022    // 'y = 1' is the earliest row it can occupy.
2023    if new_group_start < first_available_row {
2024        bail!(
2025            "cell would cause {} to expand to non-empty row {}",
2026            group_kind.name(),
2027            first_available_row.saturating_sub(1);
2028            hint: "try moving its cells to available rows";
2029        );
2030    }
2031
2032    let new_rows =
2033        group_range.map_or((new_group_start..new_group_end).chain(0..0), |r| {
2034            // NOTE: 'r.end' is one row AFTER the row group's last row, so it
2035            // makes sense to check it if 'new_group_end > r.end', that is, if
2036            // the row group is going to expand. It is NOT a duplicate check,
2037            // as we hadn't checked it before (in a previous run, it was
2038            // 'new_group_end' at the exclusive end of the range)!
2039            //
2040            // NOTE: To keep types the same, we have to always return
2041            // '(range).chain(range)', which justifies chaining an empty
2042            // range above.
2043            (new_group_start..r.start).chain(r.end..new_group_end)
2044        });
2045
2046    // The check above isn't enough, however, even when the header is expanding
2047    // upwards, as it might expand upwards towards an occupied row after the
2048    // first empty row, e.g.
2049    //
2050    // y = 0 => occupied
2051    // y = 1 => empty (first_available_row = 1)
2052    // y = 2 => occupied
2053    // y = 3 => header
2054    //
2055    // Here, we should bail if the header tries to expand upwards, regardless
2056    // of the fact that the conflicting row (y = 2) comes after the first
2057    // available row.
2058    //
2059    // Note that expanding upwards is only possible when row-positioned cells
2060    // are specified, in one of the following cases:
2061    //
2062    // 1. We place e.g. 'table.cell(y: 3)' followed by 'table.cell(y: 2)'
2063    // (earlier row => upwards);
2064    //
2065    // 2. We place e.g. 'table.cell(y: 3)' followed by '[a]' (auto-pos cell
2066    // favors 'first_available_row', so the header tries to expand upwards to
2067    // place the cell at 'y = 1' and conflicts at 'y = 2') or
2068    // 'table.cell(x: 1)' (same deal).
2069    //
2070    // Of course, we also need to check for downward expansion as usual as
2071    // there could be a non-empty row below the header, but the upward case is
2072    // highlighted as it was checked separately before (and also to explain
2073    // what kind of situation we are preventing with this check).
2074    //
2075    // Note that simply checking for non-empty rows like below not only
2076    // prevents conflicts with top-level cells (outside of headers and
2077    // footers), but also prevents conflicts with other headers or footers,
2078    // since we have an invariant that even empty headers and footers must
2079    // contain at least one 'Some(...)' position in 'resolved_cells'. More
2080    // precisely, each header and footer has at least one 'Some(...)' cell at
2081    // 'group_range.start' and at 'group_range.end - 1' - non-empty headers and
2082    // footers don't span any unnecessary rows. Therefore, we don't have to
2083    // loop over headers and footers, only check if the new rows are empty.
2084    for new_y in new_rows {
2085        if let Some(new_row @ [_non_empty, ..]) = resolved_cells
2086            .get(new_y * columns..)
2087            .map(|cells| &cells[..columns.min(cells.len())])
2088        {
2089            if new_row.iter().any(Option::is_some) {
2090                bail!(
2091                    "cell would cause {} to expand to non-empty row {new_y}",
2092                    group_kind.name();
2093                    hint: "try moving its cells to available rows";
2094                )
2095            }
2096        } else {
2097            // Received 'None' or an empty slice, so we are expanding the
2098            // header or footer into new rows, which is always valid and cannot
2099            // conflict with existing cells. (Note that we only resize
2100            // 'resolved_cells' after this function is called, so, if this
2101            // header or footer is at the bottom of the table so far, this loop
2102            // will end quite early, regardless of where this cell was placed
2103            // or of its rowspan value.)
2104            break;
2105        }
2106    }
2107
2108    Ok(new_group_start..new_group_end)
2109}
2110
2111/// Check if a cell's fixed row would conflict with a header or footer.
2112fn check_for_conflicting_cell_row(
2113    header_rows: &SmallBitSet,
2114    headers: &[Repeatable<Header>],
2115    footer: Option<&(usize, Span, Footer)>,
2116    cell_y: usize,
2117    rowspan: usize,
2118) -> HintedStrResult<()> {
2119    if !headers.is_empty()
2120        && let Some(row) =
2121            (cell_y..cell_y + rowspan).find(|&row| header_rows.contains(row))
2122    {
2123        bail!(
2124            "cell would conflict with header also spanning row {row}";
2125            hint: "try moving the cell or the header";
2126        );
2127    }
2128
2129    // NOTE: y + rowspan >, not >=, footer.start, to check if the rowspan
2130    // enters the footer. For example, consider a rowspan of 1: if
2131    // `y + 1 = footer.start` holds, that means `y < footer.start`, and it
2132    // only occupies one row (`y`), so the cell is actually not in
2133    // conflict.
2134    if let Some((_, _, footer)) = footer
2135        && cell_y < footer.end
2136        && cell_y + rowspan > footer.start
2137    {
2138        let row = (cell_y..cell_y + rowspan)
2139            .find(|row| (footer.start..footer.end).contains(row))
2140            .unwrap();
2141
2142        bail!(
2143            "cell would conflict with footer also spanning row {row}";
2144            hint: "try reducing the cell's rowspan or moving the footer";
2145        );
2146    }
2147
2148    Ok(())
2149}
2150
2151/// Given a cell's requested x and y, the vector with the resolved cell
2152/// positions, the `auto_index` counter (determines the position of the next
2153/// `(auto, auto)` cell) and the amount of columns in the grid, returns the
2154/// final index of this cell in the vector of resolved cells.
2155///
2156/// The `first_available_row` parameter is used by headers and footers to
2157/// indicate the first empty row available. Any rows before those should
2158/// not be picked by cells with `auto` row positioning, since headers and
2159/// footers occupy entire rows, and may not conflict with cells outside them.
2160#[allow(clippy::too_many_arguments)]
2161fn resolve_cell_position(
2162    cell_x: Smart<usize>,
2163    cell_y: Smart<usize>,
2164    colspan: usize,
2165    rowspan: usize,
2166    header_rows: &SmallBitSet,
2167    headers: &[Repeatable<Header>],
2168    footer: Option<&(usize, Span, Footer)>,
2169    resolved_cells: &[Option<Entry>],
2170    auto_index: &mut usize,
2171    first_available_row: usize,
2172    columns: usize,
2173    in_row_group: bool,
2174) -> HintedStrResult<usize> {
2175    // Translates a (x, y) position to the equivalent index in the final cell vector.
2176    // Errors if the position would be too large.
2177    let cell_index = |x, y: usize| {
2178        y.checked_mul(columns)
2179            .and_then(|row_index| row_index.checked_add(x))
2180            .ok_or_else(|| HintedString::from(eco_format!("cell position too large")))
2181    };
2182    match (cell_x, cell_y) {
2183        // Fully automatic cell positioning. The cell did not
2184        // request a coordinate.
2185        (Smart::Auto, Smart::Auto) => {
2186            // Let's find the first available position starting from the
2187            // automatic position counter, searching in row-major order.
2188            // Note that the counter ignores any cells with fixed positions,
2189            // but automatically-positioned cells will avoid conflicts by
2190            // simply skipping existing cells, headers and footers.
2191            let resolved_index = find_next_available_position(
2192                header_rows,
2193                footer,
2194                resolved_cells,
2195                columns,
2196                *auto_index,
2197                false,
2198            )?;
2199
2200            // Ensure the next cell with automatic position will be
2201            // placed after this one (maybe not immediately after).
2202            //
2203            // The calculation below also affects the position of the upcoming
2204            // automatically-positioned lines, as they are placed below
2205            // (horizontal lines) or to the right (vertical lines) of the cell
2206            // that would be placed at 'auto_index'.
2207            *auto_index = if colspan == columns {
2208                // The cell occupies all columns, so no cells can be placed
2209                // after it until all of its rows have been spanned.
2210                resolved_index + colspan * rowspan
2211            } else {
2212                // The next cell will have to be placed at least after its
2213                // spanned columns.
2214                resolved_index + colspan
2215            };
2216
2217            Ok(resolved_index)
2218        }
2219        // Cell has chosen at least its column.
2220        (Smart::Custom(cell_x), cell_y) => {
2221            if cell_x >= columns {
2222                return Err(HintedString::from(eco_format!(
2223                    "cell could not be placed at invalid column {cell_x}"
2224                )));
2225            }
2226            if let Smart::Custom(cell_y) = cell_y {
2227                // Cell has chosen its exact position.
2228                //
2229                // Ensure it doesn't conflict with an existing header or
2230                // footer (but only if it isn't already in one, otherwise there
2231                // will already be a separate check).
2232                if !in_row_group {
2233                    check_for_conflicting_cell_row(
2234                        header_rows,
2235                        headers,
2236                        footer,
2237                        cell_y,
2238                        rowspan,
2239                    )?;
2240                }
2241
2242                cell_index(cell_x, cell_y)
2243            } else {
2244                // Cell has only chosen its column.
2245                // Let's find the first row which has that column available.
2246                // If in a header or footer, start searching by the first empty
2247                // row / the header or footer's first row (specified through
2248                // 'first_available_row'). Otherwise, start searching at the
2249                // first row.
2250                let initial_index = cell_index(cell_x, first_available_row)?;
2251
2252                // Try each row until either we reach an absent position at the
2253                // requested column ('Some(None)') or an out of bounds position
2254                // ('None'), in which case we'd create a new row to place this
2255                // cell in.
2256                find_next_available_position(
2257                    header_rows,
2258                    footer,
2259                    resolved_cells,
2260                    columns,
2261                    initial_index,
2262                    true,
2263                )
2264            }
2265        }
2266        // Cell has only chosen its row, not its column.
2267        (Smart::Auto, Smart::Custom(cell_y)) => {
2268            // Ensure it doesn't conflict with an existing header or
2269            // footer (but only if it isn't already in one, otherwise there
2270            // will already be a separate check).
2271            if !in_row_group {
2272                check_for_conflicting_cell_row(
2273                    header_rows,
2274                    headers,
2275                    footer,
2276                    cell_y,
2277                    rowspan,
2278                )?;
2279            }
2280
2281            // Let's find the first column which has that row available.
2282            let first_row_pos = cell_index(0, cell_y)?;
2283            let last_row_pos = first_row_pos
2284                .checked_add(columns)
2285                .ok_or_else(|| eco_format!("cell position too large"))?;
2286
2287            (first_row_pos..last_row_pos)
2288                .find(|possible_index| {
2289                    // Much like in the previous cases, we skip any occupied
2290                    // positions until we either reach an absent position
2291                    // (`Some(None)`) or an out of bounds position (`None`),
2292                    // in which case we can just expand the vector enough to
2293                    // place this cell. In either case, we found an available
2294                    // position.
2295                    !matches!(resolved_cells.get(*possible_index), Some(Some(_)))
2296                })
2297                .ok_or_else(|| {
2298                    eco_format!(
2299                        "cell could not be placed in row {cell_y} because it was full"
2300                    )
2301                })
2302                .hint("try specifying your cells in a different order")
2303        }
2304    }
2305}
2306
2307/// Finds the first available position after the initial index in the resolved
2308/// grid of cells. Skips any non-absent positions (positions which already
2309/// have cells specified by the user) as well as any headers and footers.
2310///
2311/// When `skip_rows` is true, one row is skipped on each iteration, preserving
2312/// the column. That is used to find a position for a fixed column cell.
2313#[inline]
2314fn find_next_available_position(
2315    header_rows: &SmallBitSet,
2316    footer: Option<&(usize, Span, Footer)>,
2317    resolved_cells: &[Option<Entry>],
2318    columns: usize,
2319    initial_index: usize,
2320    skip_rows: bool,
2321) -> HintedStrResult<usize> {
2322    let mut resolved_index = initial_index;
2323
2324    loop {
2325        if let Some(Some(_)) = resolved_cells.get(resolved_index) {
2326            // Skip any non-absent cell positions (`Some(None)`) to
2327            // determine where this cell will be placed. An out of
2328            // bounds position (thus `None`) is also a valid new
2329            // position (only requires expanding the vector).
2330            if skip_rows {
2331                // Skip one row at a time (cell chose its column, so we don't
2332                // change it).
2333                resolved_index =
2334                    resolved_index.checked_add(columns).ok_or_else(|| {
2335                        HintedString::from(eco_format!("cell position too large"))
2336                    })?;
2337            } else {
2338                // Ensure we don't run unnecessary checks in the hot path
2339                // (for fully automatically-positioned cells). Memory usage
2340                // would become impractically large before this overflows.
2341                resolved_index += 1;
2342            }
2343        } else if header_rows.contains(resolved_index / columns) {
2344            // Skip header rows (can't place a cell inside it from outside it).
2345            if skip_rows {
2346                // Ensure the cell's chosen column is kept after the header.
2347                resolved_index += columns;
2348            } else {
2349                // Skip to the start of the next row.
2350                //
2351                // Add 1 to resolved index to force moving to the next row if
2352                // this is at the start of one. At the end of one, '+ 1'
2353                // already pushes to the next one and 'next_multiple_of' does
2354                // not modify it, so nothing bad happens then either.
2355                resolved_index = (resolved_index + 1).next_multiple_of(columns);
2356            }
2357        } else if let Some((footer_end, _, footer)) = footer
2358            && resolved_index >= footer.start * columns
2359            && resolved_index < *footer_end * columns
2360        {
2361            // Skip footer, for the same reason.
2362            resolved_index = *footer_end * columns;
2363
2364            if skip_rows {
2365                // Ensure the cell's chosen column is kept after the
2366                // footer.
2367                resolved_index += initial_index % columns;
2368            }
2369        } else {
2370            return Ok(resolved_index);
2371        }
2372    }
2373}
2374
2375/// Computes the `y` of the next available empty row, given the auto index as
2376/// an initial index for search, since we know that there are no empty rows
2377/// before automatically-positioned cells, as they are placed sequentially.
2378fn find_next_empty_row(
2379    resolved_cells: &[Option<Entry>],
2380    auto_index: usize,
2381    columns: usize,
2382) -> usize {
2383    let mut resolved_index = auto_index.next_multiple_of(columns);
2384    while resolved_cells
2385        .get(resolved_index..resolved_index + columns)
2386        .is_some_and(|row| row.iter().any(Option::is_some))
2387    {
2388        // Skip non-empty rows.
2389        resolved_index += columns;
2390    }
2391
2392    resolved_index / columns
2393}
2394
2395/// Fully merged rows under the cell of latest auto index indicate rowspans
2396/// occupying all columns, so we skip the auto index until the shortest rowspan
2397/// ends, such that, in the resulting row, we will be able to place an
2398/// automatically positioned cell - and, in particular, hlines under it. The
2399/// idea is that an auto hline will be placed after the shortest such rowspan.
2400/// Otherwise, the hline would just be placed under the first row of those
2401/// rowspans and disappear (except at the presence of column gutter).
2402fn skip_auto_index_through_fully_merged_rows(
2403    resolved_cells: &[Option<Entry>],
2404    auto_index: &mut usize,
2405    columns: usize,
2406) {
2407    // If the auto index isn't currently at the start of a row, that means
2408    // there's still at least one auto position left in the row, ignoring
2409    // cells with manual positions, so we wouldn't have a problem in placing
2410    // further cells or, in this case, hlines here.
2411    if *auto_index % columns == 0 {
2412        while resolved_cells
2413            .get(*auto_index..*auto_index + columns)
2414            .is_some_and(|row| {
2415                row.iter().all(|entry| matches!(entry, Some(Entry::Merged { .. })))
2416            })
2417        {
2418            *auto_index += columns;
2419        }
2420    }
2421}