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}