diff --git a/crates/typst-library/src/layout/grid/resolve.rs b/crates/typst-library/src/layout/grid/resolve.rs index d79f0ae45..c813197c1 100644 --- a/crates/typst-library/src/layout/grid/resolve.rs +++ b/crates/typst-library/src/layout/grid/resolve.rs @@ -1738,13 +1738,44 @@ fn resolve_cell_position( // 'first_available_row'). Otherwise, start searching at the // first row. let mut resolved_y = first_available_row; - while let Some(Some(_)) = - resolved_cells.get(cell_index(cell_x, resolved_y)?) - { - // Try each row until either we reach an absent position - // (`Some(None)`) or an out of bounds position (`None`), - // in which case we'd create a new row to place this cell in. - resolved_y += 1; + if header.is_some() || footer.is_some() { + // There are row groups, so we have to not only skip + // rows where the requested column is occupied to find the + // first suitable row, but also skip rows belonging to + // headers or footers. + loop { + if let Some(Some(_)) = + resolved_cells.get(cell_index(cell_x, resolved_y)?) + { + // Try each row until either we reach an absent position + // (`Some(None)`) or an out of bounds position (`None`), + // in which case we'd create a new row to place this cell in. + resolved_y += 1; + } else if let Some(header) = + header.filter(|header| resolved_y < header.end) + { + // Skip header + resolved_y = header.end; + } else if let Some((footer_end, _, _)) = + footer.filter(|(end, _, footer)| { + resolved_y >= footer.start && resolved_y < *end + }) + { + // Skip footer + resolved_y = *footer_end; + } else { + break; + } + } + } else { + // No row groups to skip, so only skip a row if the + // requested column is occupied, and find the first row + // where it isn't. + while let Some(Some(_)) = + resolved_cells.get(cell_index(cell_x, resolved_y)?) + { + resolved_y += 1; + } } cell_index(cell_x, resolved_y) } diff --git a/tests/ref/grid-header-cell-with-x.png b/tests/ref/grid-header-cell-with-x.png new file mode 100644 index 000000000..659826250 Binary files /dev/null and b/tests/ref/grid-header-cell-with-x.png differ diff --git a/tests/suite/layout/grid/footers.typ b/tests/suite/layout/grid/footers.typ index 422387a62..df7b519ec 100644 --- a/tests/suite/layout/grid/footers.typ +++ b/tests/suite/layout/grid/footers.typ @@ -83,6 +83,18 @@ grid.cell(y: 1)[c], ) +--- grid-footer-cell-with-x --- +#grid( + columns: 2, + stroke: black, + inset: 5pt, + grid.cell(x: 1)[a], + // Error: 3-56 footer must end at the last row + grid.footer(grid.cell(x: 0)[b1], grid.cell(x: 0)[b2]), + // This should skip the footer + grid.cell(x: 1)[c] +) + --- grid-footer-expand --- // Ensure footer properly expands #grid( diff --git a/tests/suite/layout/grid/headers.typ b/tests/suite/layout/grid/headers.typ index 984197174..28f51a214 100644 --- a/tests/suite/layout/grid/headers.typ +++ b/tests/suite/layout/grid/headers.typ @@ -60,6 +60,17 @@ grid.cell(y: 2)[c] ) +--- grid-header-cell-with-x --- +#grid( + columns: 2, + stroke: black, + inset: 5pt, + // grid.cell(x: 1)[a], + grid.header(grid.cell(x: 0)[b1], grid.cell(x: 0)[b2]), + // This should skip the header + grid.cell(x: 1)[c] +) + --- grid-header-last-child --- // When the header is the last grid child, it shouldn't include the gutter row // after it, because there is none.