diff --git a/crates/typst-library/src/layout/grid/resolve.rs b/crates/typst-library/src/layout/grid/resolve.rs index e2c0ad0c3..69d55c62e 100644 --- a/crates/typst-library/src/layout/grid/resolve.rs +++ b/crates/typst-library/src/layout/grid/resolve.rs @@ -1101,6 +1101,14 @@ impl<'x> CellGridResolver<'_, '_, 'x> { // a non-empty row. let mut first_available_row = 0; + // Indices of hlines at the top of the row group. + // + // These hlines were auto-positioned and appeared before any auto-pos + // cells, so they will appear at the first possible row (above the + // first row spanned by the row group). + let mut top_hlines_start = 0; + let mut top_hlines_end = None; + let (header_footer_items, simple_item) = match child { ResolvableGridChild::Header { repeat, span, items, .. } => { if header.is_some() { @@ -1115,6 +1123,15 @@ impl<'x> CellGridResolver<'_, '_, 'x> { first_available_row = find_next_empty_row(resolved_cells, local_auto_index, columns); + // No hlines yet, so 'end' starts as 'None'. However, its + // starting bound indicates where any potential top hlines + // would start (they would appear after the latest hline placed + // in previous iterations), so we only extend the end bound as + // necessary once more hlines are added, until we reach the + // first auto-positioned cell. Then, further auto-pos hlines + // will NOT be at the top. + top_hlines_start = pending_hlines.len(); + // If any cell in the header is automatically positioned, // have it skip to the next empty row. This is to avoid // having a header after a partially filled row just add @@ -1141,6 +1158,8 @@ impl<'x> CellGridResolver<'_, '_, 'x> { first_available_row = find_next_empty_row(resolved_cells, local_auto_index, columns); + top_hlines_start = pending_hlines.len(); + local_auto_index = first_available_row * columns; (Some(items), None) @@ -1316,6 +1335,14 @@ impl<'x> CellGridResolver<'_, '_, 'x> { ) .at(cell_span)?, ); + + if top_hlines_end.is_none() + && local_auto_index > first_available_row * columns + { + // Auto index was moved, so upcoming auto-pos hlines should + // no longer appear at the top. + top_hlines_end = Some(pending_hlines.len()); + } } // Let's resolve the cell so it can determine its own fields @@ -1428,6 +1455,19 @@ impl<'x> CellGridResolver<'_, '_, 'x> { } }; + let top_hlines_end = top_hlines_end.unwrap_or(pending_hlines.len()); + for (_, top_hline, has_auto_y) in pending_hlines + .get_mut(top_hlines_start..top_hlines_end) + .unwrap_or(&mut []) + { + if *has_auto_y { + // Move this hline to the top of the child, as it was + // placed before the first automatically positioned cell + // and had an automatic index. + top_hline.index = group_range.start; + } + } + match row_group.kind { RowGroupKind::Header => { if group_range.start != 0 { diff --git a/tests/ref/grid-footer-top-hlines-with-only-row-pos-cell.png b/tests/ref/grid-footer-top-hlines-with-only-row-pos-cell.png new file mode 100644 index 000000000..f78e80c17 Binary files /dev/null and b/tests/ref/grid-footer-top-hlines-with-only-row-pos-cell.png differ diff --git a/tests/ref/grid-footer-top-hlines-with-row-and-auto-pos-cell.png b/tests/ref/grid-footer-top-hlines-with-row-and-auto-pos-cell.png new file mode 100644 index 000000000..fda05a3cd Binary files /dev/null and b/tests/ref/grid-footer-top-hlines-with-row-and-auto-pos-cell.png differ diff --git a/tests/suite/layout/grid/footers.typ b/tests/suite/layout/grid/footers.typ index 1d01a041b..f7f1deb0a 100644 --- a/tests/suite/layout/grid/footers.typ +++ b/tests/suite/layout/grid/footers.typ @@ -437,6 +437,38 @@ ) ) +--- grid-footer-top-hlines-with-only-row-pos-cell --- +// Top hlines should attach to the top of the footer. +#set page(margin: 2pt) +#set text(6pt) +#table( + columns: 3, + inset: 2.5pt, + table.footer( + table.hline(stroke: red), + table.vline(stroke: blue), + table.cell(x: 2, y: 2)[a], + table.hline(stroke: 3pt), + table.vline(stroke: 3pt), + ) +) + +--- grid-footer-top-hlines-with-row-and-auto-pos-cell --- +#set page(margin: 2pt) +#set text(6pt) +#table( + columns: 3, + inset: 2.5pt, + table.footer( + table.hline(stroke: red), + table.vline(stroke: blue), + table.cell(x: 2, y: 2)[a], + [b], + table.hline(stroke: 3pt), + table.vline(stroke: 3pt), + ) +) + --- grid-footer-below-rowspans --- // Footer should go below the rowspans. #set page(margin: 2pt)