typst_library/layout/grid/
resolve.rs

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