typst_library/layout/grid/
resolve.rs

1use std::num::NonZeroUsize;
2use std::sync::Arc;
3
4use ecow::eco_format;
5use typst_library::diag::{
6    bail, At, Hint, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint,
7};
8use typst_library::engine::Engine;
9use typst_library::foundations::{Content, Fold, Packed, Smart, StyleChain};
10use typst_library::introspection::Locator;
11use typst_library::layout::{
12    Abs, Alignment, Axes, Celled, GridCell, GridChild, GridElem, GridItem, Length,
13    OuterHAlignment, OuterVAlignment, Rel, ResolvedCelled, Sides, Sizing,
14};
15use typst_library::model::{TableCell, TableChild, TableElem, TableItem};
16use typst_library::text::TextElem;
17use typst_library::visualize::{Paint, Stroke};
18use typst_library::Dir;
19
20use typst_syntax::Span;
21use typst_utils::NonZeroExt;
22
23/// Convert a grid to a cell grid.
24#[typst_macros::time(span = elem.span())]
25pub fn grid_to_cellgrid<'a>(
26    elem: &Packed<GridElem>,
27    engine: &mut Engine,
28    locator: Locator<'a>,
29    styles: StyleChain,
30) -> SourceResult<CellGrid<'a>> {
31    let inset = elem.inset(styles);
32    let align = elem.align(styles);
33    let columns = elem.columns(styles);
34    let rows = elem.rows(styles);
35    let column_gutter = elem.column_gutter(styles);
36    let row_gutter = elem.row_gutter(styles);
37    let fill = elem.fill(styles);
38    let stroke = elem.stroke(styles);
39
40    let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
41    let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
42    // Use trace to link back to the grid when a specific cell errors
43    let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
44    let resolve_item = |item: &GridItem| grid_item_to_resolvable(item, styles);
45    let children = elem.children.iter().map(|child| match child {
46        GridChild::Header(header) => ResolvableGridChild::Header {
47            repeat: header.repeat(styles),
48            span: header.span(),
49            items: header.children.iter().map(resolve_item),
50        },
51        GridChild::Footer(footer) => ResolvableGridChild::Footer {
52            repeat: footer.repeat(styles),
53            span: footer.span(),
54            items: footer.children.iter().map(resolve_item),
55        },
56        GridChild::Item(item) => {
57            ResolvableGridChild::Item(grid_item_to_resolvable(item, styles))
58        }
59    });
60    CellGrid::resolve(
61        tracks,
62        gutter,
63        locator,
64        children,
65        fill,
66        align,
67        &inset,
68        &stroke,
69        engine,
70        styles,
71        elem.span(),
72    )
73    .trace(engine.world, tracepoint, elem.span())
74}
75
76/// Convert a table to a cell grid.
77#[typst_macros::time(span = elem.span())]
78pub fn table_to_cellgrid<'a>(
79    elem: &Packed<TableElem>,
80    engine: &mut Engine,
81    locator: Locator<'a>,
82    styles: StyleChain,
83) -> SourceResult<CellGrid<'a>> {
84    let inset = elem.inset(styles);
85    let align = elem.align(styles);
86    let columns = elem.columns(styles);
87    let rows = elem.rows(styles);
88    let column_gutter = elem.column_gutter(styles);
89    let row_gutter = elem.row_gutter(styles);
90    let fill = elem.fill(styles);
91    let stroke = elem.stroke(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(styles),
101            span: header.span(),
102            items: header.children.iter().map(resolve_item),
103        },
104        TableChild::Footer(footer) => ResolvableGridChild::Footer {
105            repeat: footer.repeat(styles),
106            span: footer.span(),
107            items: footer.children.iter().map(resolve_item),
108        },
109        TableChild::Item(item) => {
110            ResolvableGridChild::Item(table_item_to_resolvable(item, styles))
111        }
112    });
113    CellGrid::resolve(
114        tracks,
115        gutter,
116        locator,
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(styles),
136            start: hline.start(styles),
137            end: hline.end(styles),
138            stroke: hline.stroke(styles),
139            span: hline.span(),
140            position: match hline.position(styles) {
141                OuterVAlignment::Top => LinePosition::Before,
142                OuterVAlignment::Bottom => LinePosition::After,
143            },
144        },
145        GridItem::VLine(vline) => ResolvableGridItem::VLine {
146            x: vline.x(styles),
147            start: vline.start(styles),
148            end: vline.end(styles),
149            stroke: vline.stroke(styles),
150            span: vline.span(),
151            position: match vline.position(styles) {
152                OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
153                    LinePosition::After
154                }
155                OuterHAlignment::Right if TextElem::dir_in(styles) == 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(styles),
173            start: hline.start(styles),
174            end: hline.end(styles),
175            stroke: hline.stroke(styles),
176            span: hline.span(),
177            position: match hline.position(styles) {
178                OuterVAlignment::Top => LinePosition::Before,
179                OuterVAlignment::Bottom => LinePosition::After,
180            },
181        },
182        TableItem::VLine(vline) => ResolvableGridItem::VLine {
183            x: vline.x(styles),
184            start: vline.start(styles),
185            end: vline.end(styles),
186            stroke: vline.stroke(styles),
187            span: vline.span(),
188            position: match vline.position(styles) {
189                OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
190                    LinePosition::After
191                }
192                OuterHAlignment::Right if TextElem::dir_in(styles) == 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<'a>(
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        locator: Locator<'a>,
214        styles: StyleChain,
215    ) -> Cell<'a> {
216        let cell = &mut *self;
217        let colspan = cell.colspan(styles);
218        let rowspan = cell.rowspan(styles);
219        let breakable = cell.breakable(styles).unwrap_or(breakable);
220        let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
221
222        let cell_stroke = cell.stroke(styles);
223        let stroke_overridden =
224            cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
225
226        // Using a typical 'Sides' fold, an unspecified side loses to a
227        // specified side. Additionally, when both are specified, an inner
228        // None wins over the outer Some, and vice-versa. When both are
229        // specified and Some, fold occurs, which, remarkably, leads to an Arc
230        // clone.
231        //
232        // In the end, we flatten because, for layout purposes, an unspecified
233        // cell stroke is the same as specifying 'none', so we equate the two
234        // concepts.
235        let stroke = cell_stroke.fold(stroke).map(Option::flatten);
236        cell.push_x(Smart::Custom(x));
237        cell.push_y(Smart::Custom(y));
238        cell.push_fill(Smart::Custom(fill.clone()));
239        cell.push_align(match align {
240            Smart::Custom(align) => {
241                Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
242            }
243            // Don't fold if the table is using outer alignment. Use the
244            // cell's alignment instead (which, in the end, will fold with
245            // the outer alignment when it is effectively displayed).
246            Smart::Auto => cell.align(styles),
247        });
248        cell.push_inset(Smart::Custom(
249            cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
250        ));
251        cell.push_stroke(
252            // Here we convert the resolved stroke to a regular stroke, however
253            // with resolved units (that is, 'em' converted to absolute units).
254            // We also convert any stroke unspecified by both the cell and the
255            // outer stroke ('None' in the folded stroke) to 'none', that is,
256            // all sides are present in the resulting Sides object accessible
257            // by show rules on table cells.
258            stroke.as_ref().map(|side| {
259                Some(side.as_ref().map(|cell_stroke| {
260                    Arc::new((**cell_stroke).clone().map(Length::from))
261                }))
262            }),
263        );
264        cell.push_breakable(Smart::Custom(breakable));
265        Cell {
266            body: self.pack(),
267            locator,
268            fill,
269            colspan,
270            rowspan,
271            stroke,
272            stroke_overridden,
273            breakable,
274        }
275    }
276
277    fn x(&self, styles: StyleChain) -> Smart<usize> {
278        (**self).x(styles)
279    }
280
281    fn y(&self, styles: StyleChain) -> Smart<usize> {
282        (**self).y(styles)
283    }
284
285    fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
286        (**self).colspan(styles)
287    }
288
289    fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
290        (**self).rowspan(styles)
291    }
292
293    fn span(&self) -> Span {
294        Packed::span(self)
295    }
296}
297
298impl ResolvableCell for Packed<GridCell> {
299    fn resolve_cell<'a>(
300        mut self,
301        x: usize,
302        y: usize,
303        fill: &Option<Paint>,
304        align: Smart<Alignment>,
305        inset: Sides<Option<Rel<Length>>>,
306        stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
307        breakable: bool,
308        locator: Locator<'a>,
309        styles: StyleChain,
310    ) -> Cell<'a> {
311        let cell = &mut *self;
312        let colspan = cell.colspan(styles);
313        let rowspan = cell.rowspan(styles);
314        let breakable = cell.breakable(styles).unwrap_or(breakable);
315        let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
316
317        let cell_stroke = cell.stroke(styles);
318        let stroke_overridden =
319            cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
320
321        // Using a typical 'Sides' fold, an unspecified side loses to a
322        // specified side. Additionally, when both are specified, an inner
323        // None wins over the outer Some, and vice-versa. When both are
324        // specified and Some, fold occurs, which, remarkably, leads to an Arc
325        // clone.
326        //
327        // In the end, we flatten because, for layout purposes, an unspecified
328        // cell stroke is the same as specifying 'none', so we equate the two
329        // concepts.
330        let stroke = cell_stroke.fold(stroke).map(Option::flatten);
331        cell.push_x(Smart::Custom(x));
332        cell.push_y(Smart::Custom(y));
333        cell.push_fill(Smart::Custom(fill.clone()));
334        cell.push_align(match align {
335            Smart::Custom(align) => {
336                Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
337            }
338            // Don't fold if the grid is using outer alignment. Use the
339            // cell's alignment instead (which, in the end, will fold with
340            // the outer alignment when it is effectively displayed).
341            Smart::Auto => cell.align(styles),
342        });
343        cell.push_inset(Smart::Custom(
344            cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
345        ));
346        cell.push_stroke(
347            // Here we convert the resolved stroke to a regular stroke, however
348            // with resolved units (that is, 'em' converted to absolute units).
349            // We also convert any stroke unspecified by both the cell and the
350            // outer stroke ('None' in the folded stroke) to 'none', that is,
351            // all sides are present in the resulting Sides object accessible
352            // by show rules on grid cells.
353            stroke.as_ref().map(|side| {
354                Some(side.as_ref().map(|cell_stroke| {
355                    Arc::new((**cell_stroke).clone().map(Length::from))
356                }))
357            }),
358        );
359        cell.push_breakable(Smart::Custom(breakable));
360        Cell {
361            body: self.pack(),
362            locator,
363            fill,
364            colspan,
365            rowspan,
366            stroke,
367            stroke_overridden,
368            breakable,
369        }
370    }
371
372    fn x(&self, styles: StyleChain) -> Smart<usize> {
373        (**self).x(styles)
374    }
375
376    fn y(&self, styles: StyleChain) -> Smart<usize> {
377        (**self).y(styles)
378    }
379
380    fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
381        (**self).colspan(styles)
382    }
383
384    fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
385        (**self).rowspan(styles)
386    }
387
388    fn span(&self) -> Span {
389        Packed::span(self)
390    }
391}
392
393/// Represents an explicit grid line (horizontal or vertical) specified by the
394/// user.
395pub struct Line {
396    /// The index of the track after this line. This will be the index of the
397    /// row a horizontal line is above of, or of the column right after a
398    /// vertical line.
399    ///
400    /// Must be within `0..=tracks.len()` (where `tracks` is either `grid.cols`
401    /// or `grid.rows`, ignoring gutter tracks, as appropriate).
402    pub index: usize,
403    /// The index of the track at which this line starts being drawn.
404    /// This is the first column a horizontal line appears in, or the first row
405    /// a vertical line appears in.
406    ///
407    /// Must be within `0..tracks.len()` minus gutter tracks.
408    pub start: usize,
409    /// The index after the last track through which the line is drawn.
410    /// Thus, the line is drawn through tracks `start..end` (note that `end` is
411    /// exclusive).
412    ///
413    /// Must be within `1..=tracks.len()` minus gutter tracks.
414    /// `None` indicates the line should go all the way to the end.
415    pub end: Option<NonZeroUsize>,
416    /// The line's stroke. This is `None` when the line is explicitly used to
417    /// override a previously specified line.
418    pub stroke: Option<Arc<Stroke<Abs>>>,
419    /// The line's position in relation to the track with its index.
420    pub position: LinePosition,
421}
422
423/// A repeatable grid header. Starts at the first row.
424pub struct Header {
425    /// The index after the last row included in this header.
426    pub end: usize,
427}
428
429/// A repeatable grid footer. Stops at the last row.
430pub struct Footer {
431    /// The first row included in this footer.
432    pub start: usize,
433}
434
435/// A possibly repeatable grid object.
436/// It still exists even when not repeatable, but must not have additional
437/// considerations by grid layout, other than for consistency (such as making
438/// a certain group of rows unbreakable).
439pub enum Repeatable<T> {
440    Repeated(T),
441    NotRepeated(T),
442}
443
444impl<T> Repeatable<T> {
445    /// Gets the value inside this repeatable, regardless of whether
446    /// it repeats.
447    pub fn unwrap(&self) -> &T {
448        match self {
449            Self::Repeated(repeated) => repeated,
450            Self::NotRepeated(not_repeated) => not_repeated,
451        }
452    }
453
454    /// Returns `Some` if the value is repeated, `None` otherwise.
455    pub fn as_repeated(&self) -> Option<&T> {
456        match self {
457            Self::Repeated(repeated) => Some(repeated),
458            Self::NotRepeated(_) => None,
459        }
460    }
461}
462
463/// Used for cell-like elements which are aware of their final properties in
464/// the table, and may have property overrides.
465pub trait ResolvableCell {
466    /// Resolves the cell's fields, given its coordinates and default grid-wide
467    /// fill, align, inset and stroke properties, plus the expected value of
468    /// the `breakable` field.
469    /// Returns a final Cell.
470    #[allow(clippy::too_many_arguments)]
471    fn resolve_cell<'a>(
472        self,
473        x: usize,
474        y: usize,
475        fill: &Option<Paint>,
476        align: Smart<Alignment>,
477        inset: Sides<Option<Rel<Length>>>,
478        stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
479        breakable: bool,
480        locator: Locator<'a>,
481        styles: StyleChain,
482    ) -> Cell<'a>;
483
484    /// Returns this cell's column override.
485    fn x(&self, styles: StyleChain) -> Smart<usize>;
486
487    /// Returns this cell's row override.
488    fn y(&self, styles: StyleChain) -> Smart<usize>;
489
490    /// The amount of columns spanned by this cell.
491    fn colspan(&self, styles: StyleChain) -> NonZeroUsize;
492
493    /// The amount of rows spanned by this cell.
494    fn rowspan(&self, styles: StyleChain) -> NonZeroUsize;
495
496    /// The cell's span, for errors.
497    fn span(&self) -> Span;
498}
499
500/// A grid item, possibly affected by automatic cell positioning. Can be either
501/// a line or a cell.
502pub enum ResolvableGridItem<T: ResolvableCell> {
503    /// A horizontal line in the grid.
504    HLine {
505        /// The row above which the horizontal line is drawn.
506        y: Smart<usize>,
507        start: usize,
508        end: Option<NonZeroUsize>,
509        stroke: Option<Arc<Stroke<Abs>>>,
510        /// The span of the corresponding line element.
511        span: Span,
512        /// The line's position. "before" here means on top of row `y`, while
513        /// "after" means below it.
514        position: LinePosition,
515    },
516    /// A vertical line in the grid.
517    VLine {
518        /// The column before which the vertical line is drawn.
519        x: Smart<usize>,
520        start: usize,
521        end: Option<NonZeroUsize>,
522        stroke: Option<Arc<Stroke<Abs>>>,
523        /// The span of the corresponding line element.
524        span: Span,
525        /// The line's position. "before" here means to the left of column `x`,
526        /// while "after" means to its right (both considering LTR).
527        position: LinePosition,
528    },
529    /// A cell in the grid.
530    Cell(T),
531}
532
533/// Represents a cell in CellGrid, to be laid out by GridLayouter.
534pub struct Cell<'a> {
535    /// The cell's body.
536    pub body: Content,
537    /// The cell's locator.
538    pub locator: Locator<'a>,
539    /// The cell's fill.
540    pub fill: Option<Paint>,
541    /// The amount of columns spanned by the cell.
542    pub colspan: NonZeroUsize,
543    /// The amount of rows spanned by the cell.
544    pub rowspan: NonZeroUsize,
545    /// The cell's stroke.
546    ///
547    /// We use an Arc to avoid unnecessary space usage when all sides are the
548    /// same, or when the strokes come from a common source.
549    pub stroke: Sides<Option<Arc<Stroke<Abs>>>>,
550    /// Which stroke sides were explicitly overridden by the cell, over the
551    /// grid's global stroke setting.
552    ///
553    /// This is used to define whether or not this cell's stroke sides should
554    /// have priority over adjacent cells' stroke sides, if those don't
555    /// override their own stroke properties (and thus have less priority when
556    /// defining with which stroke to draw grid lines around this cell).
557    pub stroke_overridden: Sides<bool>,
558    /// Whether rows spanned by this cell can be placed in different pages.
559    /// By default, a cell spanning only fixed-size rows is unbreakable, while
560    /// a cell spanning at least one `auto`-sized row is breakable.
561    pub breakable: bool,
562}
563
564impl<'a> Cell<'a> {
565    /// Create a simple cell given its body and its locator.
566    pub fn new(body: Content, locator: Locator<'a>) -> Self {
567        Self {
568            body,
569            locator,
570            fill: None,
571            colspan: NonZeroUsize::ONE,
572            rowspan: NonZeroUsize::ONE,
573            stroke: Sides::splat(None),
574            stroke_overridden: Sides::splat(false),
575            breakable: true,
576        }
577    }
578}
579
580/// Indicates whether the line should be drawn before or after the track with
581/// its index. This is mostly only relevant when gutter is used, since, then,
582/// the position after a track is not the same as before the next
583/// non-gutter track.
584#[derive(Copy, Clone, PartialEq, Eq)]
585pub enum LinePosition {
586    /// The line should be drawn before its track (e.g. hline on top of a row).
587    Before,
588    /// The line should be drawn after its track (e.g. hline below a row).
589    After,
590}
591
592/// A grid entry.
593pub enum Entry<'a> {
594    /// An entry which holds a cell.
595    Cell(Cell<'a>),
596    /// An entry which is merged with another cell.
597    Merged {
598        /// The index of the cell this entry is merged with.
599        parent: usize,
600    },
601}
602
603impl<'a> Entry<'a> {
604    /// Obtains the cell inside this entry, if this is not a merged cell.
605    pub fn as_cell(&self) -> Option<&Cell<'a>> {
606        match self {
607            Self::Cell(cell) => Some(cell),
608            Self::Merged { .. } => None,
609        }
610    }
611}
612
613/// Any grid child, which can be either a header or an item.
614pub enum ResolvableGridChild<T: ResolvableCell, I> {
615    Header { repeat: bool, span: Span, items: I },
616    Footer { repeat: bool, span: Span, items: I },
617    Item(ResolvableGridItem<T>),
618}
619
620/// A grid of cells, including the columns, rows, and cell data.
621pub struct CellGrid<'a> {
622    /// The grid cells.
623    pub entries: Vec<Entry<'a>>,
624    /// The column tracks including gutter tracks.
625    pub cols: Vec<Sizing>,
626    /// The row tracks including gutter tracks.
627    pub rows: Vec<Sizing>,
628    /// The vertical lines before each column, or on the end border.
629    /// Gutter columns are not included.
630    /// Contains up to 'cols_without_gutter.len() + 1' vectors of lines.
631    pub vlines: Vec<Vec<Line>>,
632    /// The horizontal lines on top of each row, or on the bottom border.
633    /// Gutter rows are not included.
634    /// Contains up to 'rows_without_gutter.len() + 1' vectors of lines.
635    pub hlines: Vec<Vec<Line>>,
636    /// The repeatable header of this grid.
637    pub header: Option<Repeatable<Header>>,
638    /// The repeatable footer of this grid.
639    pub footer: Option<Repeatable<Footer>>,
640    /// Whether this grid has gutters.
641    pub has_gutter: bool,
642}
643
644impl<'a> CellGrid<'a> {
645    /// Generates the cell grid, given the tracks and cells.
646    pub fn new(
647        tracks: Axes<&[Sizing]>,
648        gutter: Axes<&[Sizing]>,
649        cells: impl IntoIterator<Item = Cell<'a>>,
650    ) -> Self {
651        let entries = cells.into_iter().map(Entry::Cell).collect();
652        Self::new_internal(tracks, gutter, vec![], vec![], None, None, entries)
653    }
654
655    /// Resolves and positions all cells in the grid before creating it.
656    /// Allows them to keep track of their final properties and positions
657    /// and adjust their fields accordingly.
658    /// Cells must implement Clone as they will be owned. Additionally, they
659    /// must implement Default in order to fill positions in the grid which
660    /// weren't explicitly specified by the user with empty cells.
661    #[allow(clippy::too_many_arguments)]
662    pub fn resolve<T, C, I>(
663        tracks: Axes<&[Sizing]>,
664        gutter: Axes<&[Sizing]>,
665        locator: Locator<'a>,
666        children: C,
667        fill: &Celled<Option<Paint>>,
668        align: &Celled<Smart<Alignment>>,
669        inset: &Celled<Sides<Option<Rel<Length>>>>,
670        stroke: &ResolvedCelled<Sides<Option<Option<Arc<Stroke>>>>>,
671        engine: &mut Engine,
672        styles: StyleChain,
673        span: Span,
674    ) -> SourceResult<Self>
675    where
676        T: ResolvableCell + Default,
677        I: Iterator<Item = ResolvableGridItem<T>>,
678        C: IntoIterator<Item = ResolvableGridChild<T, I>>,
679        C::IntoIter: ExactSizeIterator,
680    {
681        let mut locator = locator.split();
682
683        // Number of content columns: Always at least one.
684        let c = tracks.x.len().max(1);
685
686        // Lists of lines.
687        // Horizontal lines are only pushed later to be able to check for row
688        // validity, since the amount of rows isn't known until all items were
689        // analyzed in the for loop below.
690        // We keep their spans so we can report errors later.
691        // The additional boolean indicates whether the hline had an automatic
692        // 'y' index, and is used to change the index of hlines at the top of a
693        // header or footer.
694        let mut pending_hlines: Vec<(Span, Line, bool)> = vec![];
695
696        // For consistency, only push vertical lines later as well.
697        let mut pending_vlines: Vec<(Span, Line)> = vec![];
698        let has_gutter = gutter.any(|tracks| !tracks.is_empty());
699
700        let mut header: Option<Header> = None;
701        let mut repeat_header = false;
702
703        // Stores where the footer is supposed to end, its span, and the
704        // actual footer structure.
705        let mut footer: Option<(usize, Span, Footer)> = None;
706        let mut repeat_footer = false;
707
708        // Resolves the breakability of a cell. Cells that span at least one
709        // auto-sized row or gutter are considered breakable.
710        let resolve_breakable = |y, rowspan| {
711            let auto = Sizing::Auto;
712            let zero = Sizing::Rel(Rel::zero());
713            tracks
714                .y
715                .iter()
716                .chain(std::iter::repeat(tracks.y.last().unwrap_or(&auto)))
717                .skip(y)
718                .take(rowspan)
719                .any(|row| row == &Sizing::Auto)
720                || gutter
721                    .y
722                    .iter()
723                    .chain(std::iter::repeat(gutter.y.last().unwrap_or(&zero)))
724                    .skip(y)
725                    .take(rowspan - 1)
726                    .any(|row_gutter| row_gutter == &Sizing::Auto)
727        };
728
729        // We can't just use the cell's index in the 'cells' vector to
730        // determine its automatic position, since cells could have arbitrary
731        // positions, so the position of a cell in 'cells' can differ from its
732        // final position in 'resolved_cells' (see below).
733        // Therefore, we use a counter, 'auto_index', to determine the position
734        // of the next cell with (x: auto, y: auto). It is only stepped when
735        // a cell with (x: auto, y: auto), usually the vast majority, is found.
736        let mut auto_index: usize = 0;
737
738        // We have to rebuild the grid to account for arbitrary positions.
739        // Create at least 'children.len()' positions, since there could be at
740        // least 'children.len()' cells (if no explicit lines were specified),
741        // even though some of them might be placed in arbitrary positions and
742        // thus cause the grid to expand.
743        // Additionally, make sure we allocate up to the next multiple of 'c',
744        // since each row will have 'c' cells, even if the last few cells
745        // weren't explicitly specified by the user.
746        // We apply '% c' twice so that the amount of cells potentially missing
747        // is zero when 'children.len()' is already a multiple of 'c' (thus
748        // 'children.len() % c' would be zero).
749        let children = children.into_iter();
750        let Some(child_count) = children.len().checked_add((c - children.len() % c) % c)
751        else {
752            bail!(span, "too many cells or lines were given")
753        };
754        let mut resolved_cells: Vec<Option<Entry>> = Vec::with_capacity(child_count);
755        for child in children {
756            let mut is_header = false;
757            let mut is_footer = false;
758            let mut child_start = usize::MAX;
759            let mut child_end = 0;
760            let mut child_span = Span::detached();
761            let mut start_new_row = false;
762            let mut first_index_of_top_hlines = usize::MAX;
763            let mut first_index_of_non_top_hlines = usize::MAX;
764
765            let (header_footer_items, simple_item) = match child {
766                ResolvableGridChild::Header { repeat, span, items, .. } => {
767                    if header.is_some() {
768                        bail!(span, "cannot have more than one header");
769                    }
770
771                    is_header = true;
772                    child_span = span;
773                    repeat_header = repeat;
774
775                    // If any cell in the header is automatically positioned,
776                    // have it skip to the next row. This is to avoid having a
777                    // header after a partially filled row just add cells to
778                    // that row instead of starting a new one.
779                    // FIXME: Revise this approach when headers can start from
780                    // arbitrary rows.
781                    start_new_row = true;
782
783                    // Any hlines at the top of the header will start at this
784                    // index.
785                    first_index_of_top_hlines = pending_hlines.len();
786
787                    (Some(items), None)
788                }
789                ResolvableGridChild::Footer { repeat, span, items, .. } => {
790                    if footer.is_some() {
791                        bail!(span, "cannot have more than one footer");
792                    }
793
794                    is_footer = true;
795                    child_span = span;
796                    repeat_footer = repeat;
797
798                    // If any cell in the footer is automatically positioned,
799                    // have it skip to the next row. This is to avoid having a
800                    // footer after a partially filled row just add cells to
801                    // that row instead of starting a new one.
802                    start_new_row = true;
803
804                    // Any hlines at the top of the footer will start at this
805                    // index.
806                    first_index_of_top_hlines = pending_hlines.len();
807
808                    (Some(items), None)
809                }
810                ResolvableGridChild::Item(item) => (None, Some(item)),
811            };
812
813            let items = header_footer_items
814                .into_iter()
815                .flatten()
816                .chain(simple_item.into_iter());
817            for item in items {
818                let cell = match item {
819                    ResolvableGridItem::HLine {
820                        y,
821                        start,
822                        end,
823                        stroke,
824                        span,
825                        position,
826                    } => {
827                        let has_auto_y = y.is_auto();
828                        let y = y.unwrap_or_else(|| {
829                            // Avoid placing the hline inside consecutive
830                            // rowspans occupying all columns, as it'd just
831                            // disappear, at least when there's no column
832                            // gutter.
833                            skip_auto_index_through_fully_merged_rows(
834                                &resolved_cells,
835                                &mut auto_index,
836                                c,
837                            );
838
839                            // When no 'y' is specified for the hline, we place
840                            // it under the latest automatically positioned
841                            // cell.
842                            // The current value of the auto index is always
843                            // the index of the latest automatically positioned
844                            // cell placed plus one (that's what we do in
845                            // 'resolve_cell_position'), so we subtract 1 to
846                            // get that cell's index, and place the hline below
847                            // its row. The exception is when the auto_index is
848                            // 0, meaning no automatically positioned cell was
849                            // placed yet. In that case, we place the hline at
850                            // the top of the table.
851                            //
852                            // Exceptionally, the hline will be placed before
853                            // the minimum auto index if the current auto index
854                            // from previous iterations is smaller than the
855                            // minimum it should have for the current grid
856                            // child. Effectively, this means that a hline at
857                            // the start of a header will always appear above
858                            // that header's first row. Similarly for footers.
859                            auto_index
860                                .checked_sub(1)
861                                .map_or(0, |last_auto_index| last_auto_index / c + 1)
862                        });
863                        if end.is_some_and(|end| end.get() < start) {
864                            bail!(span, "line cannot end before it starts");
865                        }
866                        let line = Line { index: y, start, end, stroke, position };
867
868                        // Since the amount of rows is dynamic, delay placing
869                        // hlines until after all cells were placed so we can
870                        // properly verify if they are valid. Note that we
871                        // can't place hlines even if we already know they
872                        // would be in a valid row, since it's possible that we
873                        // pushed pending hlines in the same row as this one in
874                        // previous iterations, and we need to ensure that
875                        // hlines from previous iterations are pushed to the
876                        // final vector of hlines first - the order of hlines
877                        // must be kept, as this matters when determining which
878                        // one "wins" in case of conflict. Pushing the current
879                        // hline before we push pending hlines later would
880                        // change their order!
881                        pending_hlines.push((span, line, has_auto_y));
882                        continue;
883                    }
884                    ResolvableGridItem::VLine {
885                        x,
886                        start,
887                        end,
888                        stroke,
889                        span,
890                        position,
891                    } => {
892                        let x = x.unwrap_or_else(|| {
893                            // When no 'x' is specified for the vline, we place
894                            // it after the latest automatically positioned
895                            // cell.
896                            // The current value of the auto index is always
897                            // the index of the latest automatically positioned
898                            // cell placed plus one (that's what we do in
899                            // 'resolve_cell_position'), so we subtract 1 to
900                            // get that cell's index, and place the vline after
901                            // its column. The exception is when the auto_index
902                            // is 0, meaning no automatically positioned cell
903                            // was placed yet. In that case, we place the vline
904                            // to the left of the table.
905                            //
906                            // Exceptionally, a vline is also placed to the
907                            // left of the table if we should start a new row
908                            // for the next automatically positioned cell.
909                            // For example, this means that a vline at
910                            // the beginning of a header will be placed to its
911                            // left rather than after the previous
912                            // automatically positioned cell. Same for footers.
913                            auto_index
914                                .checked_sub(1)
915                                .filter(|_| !start_new_row)
916                                .map_or(0, |last_auto_index| last_auto_index % c + 1)
917                        });
918                        if end.is_some_and(|end| end.get() < start) {
919                            bail!(span, "line cannot end before it starts");
920                        }
921                        let line = Line { index: x, start, end, stroke, position };
922
923                        // For consistency with hlines, we only push vlines to
924                        // the final vector of vlines after processing every
925                        // cell.
926                        pending_vlines.push((span, line));
927                        continue;
928                    }
929                    ResolvableGridItem::Cell(cell) => cell,
930                };
931                let cell_span = cell.span();
932                let colspan = cell.colspan(styles).get();
933                let rowspan = cell.rowspan(styles).get();
934                // Let's calculate the cell's final position based on its
935                // requested position.
936                let resolved_index = {
937                    let cell_x = cell.x(styles);
938                    let cell_y = cell.y(styles);
939                    resolve_cell_position(
940                        cell_x,
941                        cell_y,
942                        colspan,
943                        rowspan,
944                        &resolved_cells,
945                        &mut auto_index,
946                        &mut start_new_row,
947                        c,
948                    )
949                    .at(cell_span)?
950                };
951                let x = resolved_index % c;
952                let y = resolved_index / c;
953
954                if colspan > c - x {
955                    bail!(
956                        cell_span,
957                        "cell's colspan would cause it to exceed the available column(s)";
958                        hint: "try placing the cell in another position or reducing its colspan"
959                    )
960                }
961
962                let Some(largest_index) = c
963                    .checked_mul(rowspan - 1)
964                    .and_then(|full_rowspan_offset| {
965                        resolved_index.checked_add(full_rowspan_offset)
966                    })
967                    .and_then(|last_row_pos| last_row_pos.checked_add(colspan - 1))
968                else {
969                    bail!(
970                        cell_span,
971                        "cell would span an exceedingly large position";
972                        hint: "try reducing the cell's rowspan or colspan"
973                    )
974                };
975
976                // Let's resolve the cell so it can determine its own fields
977                // based on its final position.
978                let cell = cell.resolve_cell(
979                    x,
980                    y,
981                    &fill.resolve(engine, styles, x, y)?,
982                    align.resolve(engine, styles, x, y)?,
983                    inset.resolve(engine, styles, x, y)?,
984                    stroke.resolve(engine, styles, x, y)?,
985                    resolve_breakable(y, rowspan),
986                    locator.next(&cell_span),
987                    styles,
988                );
989
990                if largest_index >= resolved_cells.len() {
991                    // Ensure the length of the vector of resolved cells is
992                    // always a multiple of 'c' by pushing full rows every
993                    // time. Here, we add enough absent positions (later
994                    // converted to empty cells) to ensure the last row in the
995                    // new vector length is completely filled. This is
996                    // necessary so that those positions, even if not
997                    // explicitly used at the end, are eventually susceptible
998                    // to show rules and receive grid styling, as they will be
999                    // resolved as empty cells in a second loop below.
1000                    let Some(new_len) = largest_index
1001                        .checked_add(1)
1002                        .and_then(|new_len| new_len.checked_add((c - new_len % c) % c))
1003                    else {
1004                        bail!(cell_span, "cell position too large")
1005                    };
1006
1007                    // Here, the cell needs to be placed in a position which
1008                    // doesn't exist yet in the grid (out of bounds). We will
1009                    // add enough absent positions for this to be possible.
1010                    // They must be absent as no cells actually occupy them
1011                    // (they can be overridden later); however, if no cells
1012                    // occupy them as we finish building the grid, then such
1013                    // positions will be replaced by empty cells.
1014                    resolved_cells.resize_with(new_len, || None);
1015                }
1016
1017                // The vector is large enough to contain the cell, so we can
1018                // just index it directly to access the position it will be
1019                // placed in. However, we still need to ensure we won't try to
1020                // place a cell where there already is one.
1021                let slot = &mut resolved_cells[resolved_index];
1022                if slot.is_some() {
1023                    bail!(
1024                        cell_span,
1025                        "attempted to place a second cell at column {x}, row {y}";
1026                        hint: "try specifying your cells in a different order"
1027                    );
1028                }
1029
1030                *slot = Some(Entry::Cell(cell));
1031
1032                // Now, if the cell spans more than one row or column, we fill
1033                // the spanned positions in the grid with Entry::Merged
1034                // pointing to the original cell as its parent.
1035                for rowspan_offset in 0..rowspan {
1036                    let spanned_y = y + rowspan_offset;
1037                    let first_row_index = resolved_index + c * rowspan_offset;
1038                    for (colspan_offset, slot) in resolved_cells[first_row_index..]
1039                        [..colspan]
1040                        .iter_mut()
1041                        .enumerate()
1042                    {
1043                        let spanned_x = x + colspan_offset;
1044                        if spanned_x == x && spanned_y == y {
1045                            // This is the parent cell.
1046                            continue;
1047                        }
1048                        if slot.is_some() {
1049                            bail!(
1050                                cell_span,
1051                                "cell would span a previously placed cell at column {spanned_x}, row {spanned_y}";
1052                                hint: "try specifying your cells in a different order or reducing the cell's rowspan or colspan"
1053                            )
1054                        }
1055                        *slot = Some(Entry::Merged { parent: resolved_index });
1056                    }
1057                }
1058
1059                if is_header || is_footer {
1060                    // Ensure each cell in a header or footer is fully
1061                    // contained within it.
1062                    child_start = child_start.min(y);
1063                    child_end = child_end.max(y + rowspan);
1064
1065                    if start_new_row && child_start <= auto_index.div_ceil(c) {
1066                        // No need to start a new row as we already include
1067                        // the row of the next automatically positioned cell in
1068                        // the header or footer.
1069                        start_new_row = false;
1070                    }
1071
1072                    if !start_new_row {
1073                        // From now on, upcoming hlines won't be at the top of
1074                        // the child, as the first automatically positioned
1075                        // cell was placed.
1076                        first_index_of_non_top_hlines =
1077                            first_index_of_non_top_hlines.min(pending_hlines.len());
1078                    }
1079                }
1080            }
1081
1082            if (is_header || is_footer) && child_start == usize::MAX {
1083                // Empty header/footer: consider the header/footer to be
1084                // at the next empty row after the latest auto index.
1085                auto_index = find_next_empty_row(&resolved_cells, auto_index, c);
1086                child_start = auto_index.div_ceil(c);
1087                child_end = child_start + 1;
1088
1089                if resolved_cells.len() <= c * child_start {
1090                    // Ensure the automatically chosen row actually exists.
1091                    resolved_cells.resize_with(c * (child_start + 1), || None);
1092                }
1093            }
1094
1095            if is_header {
1096                if child_start != 0 {
1097                    bail!(
1098                        child_span,
1099                        "header must start at the first row";
1100                        hint: "remove any rows before the header"
1101                    );
1102                }
1103
1104                header = Some(Header {
1105                    // Later on, we have to correct this number in case there
1106                    // is gutter. But only once all cells have been analyzed
1107                    // and the header has fully expanded in the fixup loop
1108                    // below.
1109                    end: child_end,
1110                });
1111            }
1112
1113            if is_footer {
1114                // Only check if the footer is at the end later, once we know
1115                // the final amount of rows.
1116                footer = Some((
1117                    child_end,
1118                    child_span,
1119                    Footer {
1120                        // Later on, we have to correct this number in case there
1121                        // is gutter, but only once all cells have been analyzed
1122                        // and the header's and footer's exact boundaries are
1123                        // known. That is because the gutter row immediately
1124                        // before the footer might not be included as part of
1125                        // the footer if it is contained within the header.
1126                        start: child_start,
1127                    },
1128                ));
1129            }
1130
1131            if is_header || is_footer {
1132                let amount_hlines = pending_hlines.len();
1133                for (_, top_hline, has_auto_y) in pending_hlines
1134                    .get_mut(
1135                        first_index_of_top_hlines
1136                            ..first_index_of_non_top_hlines.min(amount_hlines),
1137                    )
1138                    .unwrap_or(&mut [])
1139                {
1140                    if *has_auto_y {
1141                        // Move this hline to the top of the child, as it was
1142                        // placed before the first automatically positioned cell
1143                        // and had an automatic index.
1144                        top_hline.index = child_start;
1145                    }
1146                }
1147
1148                // Next automatically positioned cell goes under this header.
1149                // FIXME: Consider only doing this if the header has any fully
1150                // automatically positioned cells. Otherwise,
1151                // `resolve_cell_position` should be smart enough to skip
1152                // upcoming headers.
1153                // Additionally, consider that cells with just an 'x' override
1154                // could end up going too far back and making previous
1155                // non-header rows into header rows (maybe they should be
1156                // placed at the first row that is fully empty or something).
1157                // Nothing we can do when both 'x' and 'y' were overridden, of
1158                // course.
1159                // None of the above are concerns for now, as headers must
1160                // start at the first row.
1161                auto_index = auto_index.max(c * child_end);
1162            }
1163        }
1164
1165        // If the user specified cells occupying less rows than the given rows,
1166        // we shall expand the grid so that it has at least the given amount of
1167        // rows.
1168        let Some(expected_total_cells) = c.checked_mul(tracks.y.len()) else {
1169            bail!(span, "too many rows were specified");
1170        };
1171        let missing_cells = expected_total_cells.saturating_sub(resolved_cells.len());
1172
1173        // Fixup phase (final step in cell grid generation):
1174        // 1. Replace absent entries by resolved empty cells, and produce a
1175        // vector of 'Entry' from 'Option<Entry>'.
1176        // 2. Add enough empty cells to the end of the grid such that it has at
1177        // least the given amount of rows.
1178        // 3. If any cells were added to the header's rows after the header's
1179        // creation, ensure the header expands enough to accommodate them
1180        // across all of their spanned rows. Same for the footer.
1181        // 4. If any cells before the footer try to span it, error.
1182        let resolved_cells = resolved_cells
1183            .into_iter()
1184            .chain(std::iter::repeat_with(|| None).take(missing_cells))
1185            .enumerate()
1186            .map(|(i, cell)| {
1187                if let Some(cell) = cell {
1188                    if let Some(parent_cell) = cell.as_cell() {
1189                        if let Some(header) = &mut header
1190                        {
1191                            let y = i / c;
1192                            if y < header.end {
1193                                // Ensure the header expands enough such that
1194                                // all cells inside it, even those added later,
1195                                // are fully contained within the header.
1196                                // FIXME: check if start < y < end when start can
1197                                // be != 0.
1198                                // FIXME: when start can be != 0, decide what
1199                                // happens when a cell after the header placed
1200                                // above it tries to span the header (either
1201                                // error or expand upwards).
1202                                header.end = header.end.max(y + parent_cell.rowspan.get());
1203                            }
1204                        }
1205
1206                        if let Some((end, footer_span, footer)) = &mut footer {
1207                            let x = i % c;
1208                            let y = i / c;
1209                            let cell_end = y + parent_cell.rowspan.get();
1210                            if y < footer.start && cell_end > footer.start {
1211                                // Don't allow a cell before the footer to span
1212                                // it. Surely, we could move the footer to
1213                                // start at where this cell starts, so this is
1214                                // more of a design choice, as it's unlikely
1215                                // for the user to intentionally include a cell
1216                                // before the footer spanning it but not
1217                                // being repeated with it.
1218                                bail!(
1219                                    *footer_span,
1220                                    "footer would conflict with a cell placed before it at column {x} row {y}";
1221                                    hint: "try reducing that cell's rowspan or moving the footer"
1222                                );
1223                            }
1224                            if y >= footer.start && y < *end {
1225                                // Expand the footer to include all rows
1226                                // spanned by this cell, as it is inside the
1227                                // footer.
1228                                *end = (*end).max(cell_end);
1229                            }
1230                        }
1231                    }
1232
1233                    Ok(cell)
1234                } else {
1235                    let x = i % c;
1236                    let y = i / c;
1237
1238                    // Ensure all absent entries are affected by show rules and
1239                    // grid styling by turning them into resolved empty cells.
1240                    let new_cell = T::default().resolve_cell(
1241                        x,
1242                        y,
1243                        &fill.resolve(engine, styles, x, y)?,
1244                        align.resolve(engine, styles, x, y)?,
1245                        inset.resolve(engine, styles, x, y)?,
1246                        stroke.resolve(engine, styles, x, y)?,
1247                        resolve_breakable(y, 1),
1248                        locator.next(&()),
1249                        styles,
1250                    );
1251                    Ok(Entry::Cell(new_cell))
1252                }
1253            })
1254            .collect::<SourceResult<Vec<Entry>>>()?;
1255
1256        // Populate the final lists of lines.
1257        // For each line type (horizontal or vertical), we keep a vector for
1258        // every group of lines with the same index.
1259        let mut vlines: Vec<Vec<Line>> = vec![];
1260        let mut hlines: Vec<Vec<Line>> = vec![];
1261        let row_amount = resolved_cells.len().div_ceil(c);
1262
1263        for (line_span, line, _) in pending_hlines {
1264            let y = line.index;
1265            if y > row_amount {
1266                bail!(line_span, "cannot place horizontal line at invalid row {y}");
1267            }
1268            if y == row_amount && line.position == LinePosition::After {
1269                bail!(
1270                    line_span,
1271                    "cannot place horizontal line at the 'bottom' position of the bottom border (y = {y})";
1272                    hint: "set the line's position to 'top' or place it at a smaller 'y' index"
1273                );
1274            }
1275            let line = if line.position == LinePosition::After
1276                && (!has_gutter || y + 1 == row_amount)
1277            {
1278                // Just place the line on top of the next row if
1279                // there's no gutter and the line should be placed
1280                // after the one with given index.
1281                //
1282                // Note that placing after the last row is also the same as
1283                // just placing on the grid's bottom border, even with
1284                // gutter.
1285                Line {
1286                    index: y + 1,
1287                    position: LinePosition::Before,
1288                    ..line
1289                }
1290            } else {
1291                line
1292            };
1293            let y = line.index;
1294
1295            if hlines.len() <= y {
1296                hlines.resize_with(y + 1, Vec::new);
1297            }
1298            hlines[y].push(line);
1299        }
1300
1301        for (line_span, line) in pending_vlines {
1302            let x = line.index;
1303            if x > c {
1304                bail!(line_span, "cannot place vertical line at invalid column {x}");
1305            }
1306            if x == c && line.position == LinePosition::After {
1307                bail!(
1308                    line_span,
1309                    "cannot place vertical line at the 'end' position of the end border (x = {c})";
1310                    hint: "set the line's position to 'start' or place it at a smaller 'x' index"
1311                );
1312            }
1313            let line =
1314                if line.position == LinePosition::After && (!has_gutter || x + 1 == c) {
1315                    // Just place the line before the next column if
1316                    // there's no gutter and the line should be placed
1317                    // after the one with given index.
1318                    //
1319                    // Note that placing after the last column is also the
1320                    // same as just placing on the grid's end border, even
1321                    // with gutter.
1322                    Line {
1323                        index: x + 1,
1324                        position: LinePosition::Before,
1325                        ..line
1326                    }
1327                } else {
1328                    line
1329                };
1330            let x = line.index;
1331
1332            if vlines.len() <= x {
1333                vlines.resize_with(x + 1, Vec::new);
1334            }
1335            vlines[x].push(line);
1336        }
1337
1338        let header = header
1339            .map(|mut header| {
1340                // Repeat the gutter below a header (hence why we don't
1341                // subtract 1 from the gutter case).
1342                // Don't do this if there are no rows under the header.
1343                if has_gutter {
1344                    // - 'header.end' is always 'last y + 1'. The header stops
1345                    // before that row.
1346                    // - Therefore, '2 * header.end' will be 2 * (last y + 1),
1347                    // which is the adjusted index of the row before which the
1348                    // header stops, meaning it will still stop right before it
1349                    // even with gutter thanks to the multiplication below.
1350                    // - This means that it will span all rows up to
1351                    // '2 * (last y + 1) - 1 = 2 * last y + 1', which equates
1352                    // to the index of the gutter row right below the header,
1353                    // which is what we want (that gutter spacing should be
1354                    // repeated across pages to maintain uniformity).
1355                    header.end *= 2;
1356
1357                    // If the header occupies the entire grid, ensure we don't
1358                    // include an extra gutter row when it doesn't exist, since
1359                    // the last row of the header is at the very bottom,
1360                    // therefore '2 * last y + 1' is not a valid index.
1361                    let row_amount = (2 * row_amount).saturating_sub(1);
1362                    header.end = header.end.min(row_amount);
1363                }
1364                header
1365            })
1366            .map(|header| {
1367                if repeat_header {
1368                    Repeatable::Repeated(header)
1369                } else {
1370                    Repeatable::NotRepeated(header)
1371                }
1372            });
1373
1374        let footer = footer
1375            .map(|(footer_end, footer_span, mut footer)| {
1376                if footer_end != row_amount {
1377                    bail!(footer_span, "footer must end at the last row");
1378                }
1379
1380                let header_end =
1381                    header.as_ref().map(Repeatable::unwrap).map(|header| header.end);
1382
1383                if has_gutter {
1384                    // Convert the footer's start index to post-gutter coordinates.
1385                    footer.start *= 2;
1386
1387                    // Include the gutter right before the footer, unless there is
1388                    // none, or the gutter is already included in the header (no
1389                    // rows between the header and the footer).
1390                    if header_end.map_or(true, |header_end| header_end != footer.start) {
1391                        footer.start = footer.start.saturating_sub(1);
1392                    }
1393                }
1394
1395                if header_end.is_some_and(|header_end| header_end > footer.start) {
1396                    bail!(footer_span, "header and footer must not have common rows");
1397                }
1398
1399                Ok(footer)
1400            })
1401            .transpose()?
1402            .map(|footer| {
1403                if repeat_footer {
1404                    Repeatable::Repeated(footer)
1405                } else {
1406                    Repeatable::NotRepeated(footer)
1407                }
1408            });
1409
1410        Ok(Self::new_internal(
1411            tracks,
1412            gutter,
1413            vlines,
1414            hlines,
1415            header,
1416            footer,
1417            resolved_cells,
1418        ))
1419    }
1420
1421    /// Generates the cell grid, given the tracks and resolved entries.
1422    pub fn new_internal(
1423        tracks: Axes<&[Sizing]>,
1424        gutter: Axes<&[Sizing]>,
1425        vlines: Vec<Vec<Line>>,
1426        hlines: Vec<Vec<Line>>,
1427        header: Option<Repeatable<Header>>,
1428        footer: Option<Repeatable<Footer>>,
1429        entries: Vec<Entry<'a>>,
1430    ) -> Self {
1431        let mut cols = vec![];
1432        let mut rows = vec![];
1433
1434        // Number of content columns: Always at least one.
1435        let c = tracks.x.len().max(1);
1436
1437        // Number of content rows: At least as many as given, but also at least
1438        // as many as needed to place each item.
1439        let r = {
1440            let len = entries.len();
1441            let given = tracks.y.len();
1442            let needed = len / c + (len % c).clamp(0, 1);
1443            given.max(needed)
1444        };
1445
1446        let has_gutter = gutter.any(|tracks| !tracks.is_empty());
1447        let auto = Sizing::Auto;
1448        let zero = Sizing::Rel(Rel::zero());
1449        let get_or = |tracks: &[_], idx, default| {
1450            tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
1451        };
1452
1453        // Collect content and gutter columns.
1454        for x in 0..c {
1455            cols.push(get_or(tracks.x, x, auto));
1456            if has_gutter {
1457                cols.push(get_or(gutter.x, x, zero));
1458            }
1459        }
1460
1461        // Collect content and gutter rows.
1462        for y in 0..r {
1463            rows.push(get_or(tracks.y, y, auto));
1464            if has_gutter {
1465                rows.push(get_or(gutter.y, y, zero));
1466            }
1467        }
1468
1469        // Remove superfluous gutter tracks.
1470        if has_gutter {
1471            cols.pop();
1472            rows.pop();
1473        }
1474
1475        Self {
1476            cols,
1477            rows,
1478            entries,
1479            vlines,
1480            hlines,
1481            header,
1482            footer,
1483            has_gutter,
1484        }
1485    }
1486
1487    /// Get the grid entry in column `x` and row `y`.
1488    ///
1489    /// Returns `None` if it's a gutter cell.
1490    #[track_caller]
1491    pub fn entry(&self, x: usize, y: usize) -> Option<&Entry<'a>> {
1492        assert!(x < self.cols.len());
1493        assert!(y < self.rows.len());
1494
1495        if self.has_gutter {
1496            // Even columns and rows are children, odd ones are gutter.
1497            if x % 2 == 0 && y % 2 == 0 {
1498                let c = 1 + self.cols.len() / 2;
1499                self.entries.get((y / 2) * c + x / 2)
1500            } else {
1501                None
1502            }
1503        } else {
1504            let c = self.cols.len();
1505            self.entries.get(y * c + x)
1506        }
1507    }
1508
1509    /// Get the content of the cell in column `x` and row `y`.
1510    ///
1511    /// Returns `None` if it's a gutter cell or merged position.
1512    #[track_caller]
1513    pub fn cell(&self, x: usize, y: usize) -> Option<&Cell<'a>> {
1514        self.entry(x, y).and_then(Entry::as_cell)
1515    }
1516
1517    /// Returns the position of the parent cell of the grid entry at the given
1518    /// position. It is guaranteed to have a non-gutter, non-merged cell at
1519    /// the returned position, due to how the grid is built.
1520    /// - If the entry at the given position is a cell, returns the given
1521    ///   position.
1522    /// - If it is a merged cell, returns the parent cell's position.
1523    /// - If it is a gutter cell, returns None.
1524    #[track_caller]
1525    pub fn parent_cell_position(&self, x: usize, y: usize) -> Option<Axes<usize>> {
1526        self.entry(x, y).map(|entry| match entry {
1527            Entry::Cell(_) => Axes::new(x, y),
1528            Entry::Merged { parent } => {
1529                let c = self.non_gutter_column_count();
1530                let factor = if self.has_gutter { 2 } else { 1 };
1531                Axes::new(factor * (*parent % c), factor * (*parent / c))
1532            }
1533        })
1534    }
1535
1536    /// Returns the position of the actual parent cell of a merged position,
1537    /// even if the given position is gutter, in which case we return the
1538    /// parent of the nearest adjacent content cell which could possibly span
1539    /// the given gutter position. If the given position is not a gutter cell,
1540    /// then this function will return the same as `parent_cell_position` would.
1541    /// If the given position is a gutter cell, but no cell spans it, returns
1542    /// `None`.
1543    ///
1544    /// This is useful for lines. A line needs to check if a cell next to it
1545    /// has a stroke override - even at a gutter position there could be a
1546    /// stroke override, since a cell could be merged with two cells at both
1547    /// ends of the gutter cell (e.g. to its left and to its right), and thus
1548    /// that cell would impose a stroke under the gutter. This function allows
1549    /// getting the position of that cell (which spans the given gutter
1550    /// position, if it is gutter), if it exists; otherwise returns None (it's
1551    /// gutter and no cell spans it).
1552    #[track_caller]
1553    pub fn effective_parent_cell_position(
1554        &self,
1555        x: usize,
1556        y: usize,
1557    ) -> Option<Axes<usize>> {
1558        if self.has_gutter {
1559            // If (x, y) is a gutter cell, we skip it (skip a gutter column and
1560            // row) to the nearest adjacent content cell, in the direction
1561            // which merged cells grow toward (increasing x and increasing y),
1562            // such that we can verify if that adjacent cell is merged with the
1563            // gutter cell by checking if its parent would come before (x, y).
1564            // Otherwise, no cell is merged with this gutter cell, and we
1565            // return None.
1566            self.parent_cell_position(x + x % 2, y + y % 2)
1567                .filter(|&parent| parent.x <= x && parent.y <= y)
1568        } else {
1569            self.parent_cell_position(x, y)
1570        }
1571    }
1572
1573    /// Checks if the track with the given index is gutter.
1574    /// Does not check if the index is a valid track.
1575    #[inline]
1576    pub fn is_gutter_track(&self, index: usize) -> bool {
1577        self.has_gutter && index % 2 == 1
1578    }
1579
1580    /// Returns the effective colspan of a cell, considering the gutters it
1581    /// might span if the grid has gutters.
1582    #[inline]
1583    pub fn effective_colspan_of_cell(&self, cell: &Cell) -> usize {
1584        if self.has_gutter {
1585            2 * cell.colspan.get() - 1
1586        } else {
1587            cell.colspan.get()
1588        }
1589    }
1590
1591    /// Returns the effective rowspan of a cell, considering the gutters it
1592    /// might span if the grid has gutters.
1593    #[inline]
1594    pub fn effective_rowspan_of_cell(&self, cell: &Cell) -> usize {
1595        if self.has_gutter {
1596            2 * cell.rowspan.get() - 1
1597        } else {
1598            cell.rowspan.get()
1599        }
1600    }
1601
1602    #[inline]
1603    pub fn non_gutter_column_count(&self) -> usize {
1604        if self.has_gutter {
1605            // Calculation: With gutters, we have
1606            // 'cols = 2 * (non-gutter cols) - 1', since there is a gutter
1607            // column between each regular column. Therefore,
1608            // 'floor(cols / 2)' will be equal to
1609            // 'floor(non-gutter cols - 1/2) = non-gutter-cols - 1',
1610            // so 'non-gutter cols = 1 + floor(cols / 2)'.
1611            1 + self.cols.len() / 2
1612        } else {
1613            self.cols.len()
1614        }
1615    }
1616}
1617
1618/// Given a cell's requested x and y, the vector with the resolved cell
1619/// positions, the `auto_index` counter (determines the position of the next
1620/// `(auto, auto)` cell) and the amount of columns in the grid, returns the
1621/// final index of this cell in the vector of resolved cells.
1622///
1623/// The `start_new_row` parameter is used to ensure that, if this cell is
1624/// fully automatically positioned, it should start a new, empty row. This is
1625/// useful for headers and footers, which must start at their own rows, without
1626/// interference from previous cells.
1627#[allow(clippy::too_many_arguments)]
1628fn resolve_cell_position(
1629    cell_x: Smart<usize>,
1630    cell_y: Smart<usize>,
1631    colspan: usize,
1632    rowspan: usize,
1633    resolved_cells: &[Option<Entry>],
1634    auto_index: &mut usize,
1635    start_new_row: &mut bool,
1636    columns: usize,
1637) -> HintedStrResult<usize> {
1638    // Translates a (x, y) position to the equivalent index in the final cell vector.
1639    // Errors if the position would be too large.
1640    let cell_index = |x, y: usize| {
1641        y.checked_mul(columns)
1642            .and_then(|row_index| row_index.checked_add(x))
1643            .ok_or_else(|| HintedString::from(eco_format!("cell position too large")))
1644    };
1645    match (cell_x, cell_y) {
1646        // Fully automatic cell positioning. The cell did not
1647        // request a coordinate.
1648        (Smart::Auto, Smart::Auto) => {
1649            // Let's find the first available position starting from the
1650            // automatic position counter, searching in row-major order.
1651            let mut resolved_index = *auto_index;
1652            if *start_new_row {
1653                resolved_index =
1654                    find_next_empty_row(resolved_cells, resolved_index, columns);
1655
1656                // Next cell won't have to start a new row if we just did that,
1657                // in principle.
1658                *start_new_row = false;
1659            } else {
1660                while let Some(Some(_)) = resolved_cells.get(resolved_index) {
1661                    // Skip any non-absent cell positions (`Some(None)`) to
1662                    // determine where this cell will be placed. An out of
1663                    // bounds position (thus `None`) is also a valid new
1664                    // position (only requires expanding the vector).
1665                    resolved_index += 1;
1666                }
1667            }
1668
1669            // Ensure the next cell with automatic position will be
1670            // placed after this one (maybe not immediately after).
1671            //
1672            // The calculation below also affects the position of the upcoming
1673            // automatically-positioned lines.
1674            *auto_index = if colspan == columns {
1675                // The cell occupies all columns, so no cells can be placed
1676                // after it until all of its rows have been spanned.
1677                resolved_index + colspan * rowspan
1678            } else {
1679                // The next cell will have to be placed at least after its
1680                // spanned columns.
1681                resolved_index + colspan
1682            };
1683
1684            Ok(resolved_index)
1685        }
1686        // Cell has chosen at least its column.
1687        (Smart::Custom(cell_x), cell_y) => {
1688            if cell_x >= columns {
1689                return Err(HintedString::from(eco_format!(
1690                    "cell could not be placed at invalid column {cell_x}"
1691                )));
1692            }
1693            if let Smart::Custom(cell_y) = cell_y {
1694                // Cell has chosen its exact position.
1695                cell_index(cell_x, cell_y)
1696            } else {
1697                // Cell has only chosen its column.
1698                // Let's find the first row which has that column available.
1699                let mut resolved_y = 0;
1700                while let Some(Some(_)) =
1701                    resolved_cells.get(cell_index(cell_x, resolved_y)?)
1702                {
1703                    // Try each row until either we reach an absent position
1704                    // (`Some(None)`) or an out of bounds position (`None`),
1705                    // in which case we'd create a new row to place this cell in.
1706                    resolved_y += 1;
1707                }
1708                cell_index(cell_x, resolved_y)
1709            }
1710        }
1711        // Cell has only chosen its row, not its column.
1712        (Smart::Auto, Smart::Custom(cell_y)) => {
1713            // Let's find the first column which has that row available.
1714            let first_row_pos = cell_index(0, cell_y)?;
1715            let last_row_pos = first_row_pos
1716                .checked_add(columns)
1717                .ok_or_else(|| eco_format!("cell position too large"))?;
1718
1719            (first_row_pos..last_row_pos)
1720                .find(|possible_index| {
1721                    // Much like in the previous cases, we skip any occupied
1722                    // positions until we either reach an absent position
1723                    // (`Some(None)`) or an out of bounds position (`None`),
1724                    // in which case we can just expand the vector enough to
1725                    // place this cell. In either case, we found an available
1726                    // position.
1727                    !matches!(resolved_cells.get(*possible_index), Some(Some(_)))
1728                })
1729                .ok_or_else(|| {
1730                    eco_format!(
1731                        "cell could not be placed in row {cell_y} because it was full"
1732                    )
1733                })
1734                .hint("try specifying your cells in a different order")
1735        }
1736    }
1737}
1738
1739/// Computes the index of the first cell in the next empty row in the grid,
1740/// starting with the given initial index.
1741fn find_next_empty_row(
1742    resolved_cells: &[Option<Entry>],
1743    initial_index: usize,
1744    columns: usize,
1745) -> usize {
1746    let mut resolved_index = initial_index.next_multiple_of(columns);
1747    while resolved_cells
1748        .get(resolved_index..resolved_index + columns)
1749        .is_some_and(|row| row.iter().any(Option::is_some))
1750    {
1751        // Skip non-empty rows.
1752        resolved_index += columns;
1753    }
1754
1755    resolved_index
1756}
1757
1758/// Fully merged rows under the cell of latest auto index indicate rowspans
1759/// occupying all columns, so we skip the auto index until the shortest rowspan
1760/// ends, such that, in the resulting row, we will be able to place an
1761/// automatically positioned cell - and, in particular, hlines under it. The
1762/// idea is that an auto hline will be placed after the shortest such rowspan.
1763/// Otherwise, the hline would just be placed under the first row of those
1764/// rowspans and disappear (except at the presence of column gutter).
1765fn skip_auto_index_through_fully_merged_rows(
1766    resolved_cells: &[Option<Entry>],
1767    auto_index: &mut usize,
1768    columns: usize,
1769) {
1770    // If the auto index isn't currently at the start of a row, that means
1771    // there's still at least one auto position left in the row, ignoring
1772    // cells with manual positions, so we wouldn't have a problem in placing
1773    // further cells or, in this case, hlines here.
1774    if *auto_index % columns == 0 {
1775        while resolved_cells
1776            .get(*auto_index..*auto_index + columns)
1777            .is_some_and(|row| {
1778                row.iter().all(|entry| matches!(entry, Some(Entry::Merged { .. })))
1779            })
1780        {
1781            *auto_index += columns;
1782        }
1783    }
1784}