add levels and ranges to headers and footers

This commit is contained in:
PgBiel 2025-03-03 19:59:41 -03:00
parent 1f1c133878
commit 20179bbe7a
2 changed files with 55 additions and 13 deletions

View File

@ -426,8 +426,24 @@ pub struct Line {
/// A repeatable grid header. Starts at the first row. /// A repeatable grid header. Starts at the first row.
#[derive(Debug)] #[derive(Debug)]
pub struct Header { pub struct Header {
/// The first row included in this header.
pub start: usize,
/// The index after the last row included in this header. /// The index after the last row included in this header.
pub end: usize, pub end: usize,
/// The header's level.
///
/// Higher level headers repeat together with lower level headers. If a
/// lower level header stops repeating, all higher level headers do as
/// well.
pub level: u32,
}
impl Header {
/// The header's range of included rows.
#[inline]
pub fn range(&self) -> Range<usize> {
self.start..self.end
}
} }
/// A repeatable grid footer. Stops at the last row. /// A repeatable grid footer. Stops at the last row.
@ -435,6 +451,20 @@ pub struct Header {
pub struct Footer { pub struct Footer {
/// The first row included in this footer. /// The first row included in this footer.
pub start: usize, pub start: usize,
/// The index after the last row included in this footer.
pub end: usize,
/// The footer's level.
///
/// Used similarly to header level.
pub level: u32,
}
impl Footer {
/// The footer's range of included rows.
#[inline]
pub fn range(&self) -> Range<usize> {
self.start..self.end
}
} }
/// A possibly repeatable grid object. /// A possibly repeatable grid object.
@ -638,10 +668,10 @@ pub struct CellGrid<'a> {
/// Gutter rows are not included. /// Gutter rows are not included.
/// Contains up to 'rows_without_gutter.len() + 1' vectors of lines. /// Contains up to 'rows_without_gutter.len() + 1' vectors of lines.
pub hlines: Vec<Vec<Line>>, pub hlines: Vec<Vec<Line>>,
/// The repeatable header of this grid.
pub header: Option<Repeatable<Header>>,
/// The repeatable footer of this grid. /// The repeatable footer of this grid.
pub footer: Option<Repeatable<Footer>>, pub footer: Option<Repeatable<Footer>>,
/// The repeatable headers of this grid.
pub headers: Vec<Repeatable<Header>>,
/// Whether this grid has gutters. /// Whether this grid has gutters.
pub has_gutter: bool, pub has_gutter: bool,
} }
@ -717,8 +747,8 @@ impl<'a> CellGrid<'a> {
entries, entries,
vlines, vlines,
hlines, hlines,
header,
footer, footer,
headers: header.into_iter().collect(),
has_gutter, has_gutter,
} }
} }
@ -852,6 +882,11 @@ impl<'a> CellGrid<'a> {
self.cols.len() self.cols.len()
} }
} }
#[inline]
pub fn has_repeated_headers(&self) -> bool {
self.headers.iter().any(|h| matches!(h, Repeatable::Repeated(_)))
}
} }
/// Resolves and positions all cells in the grid before creating it. /// Resolves and positions all cells in the grid before creating it.
@ -1492,11 +1527,15 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
} }
*header = Some(Header { *header = Some(Header {
start: group_range.start,
// Later on, we have to correct this number in case there // Later on, we have to correct this number in case there
// is gutter. But only once all cells have been analyzed // is gutter. But only once all cells have been analyzed
// and the header has fully expanded in the fixup loop // and the header has fully expanded in the fixup loop
// below. // below.
end: group_range.end, end: group_range.end,
level: 1,
}); });
} }
@ -1514,6 +1553,8 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
// before the footer might not be included as part of // before the footer might not be included as part of
// the footer if it is contained within the header. // the footer if it is contained within the header.
start: group_range.start, start: group_range.start,
end: group_range.end,
level: 1,
}, },
)); ));
} }
@ -1940,8 +1981,12 @@ fn check_for_conflicting_cell_row(
rowspan: usize, rowspan: usize,
) -> HintedStrResult<()> { ) -> HintedStrResult<()> {
if let Some(header) = header { if let Some(header) = header {
// TODO: check start (right now zero, always satisfied) // NOTE: y + rowspan >, not >=, header.start, to check if the rowspan
if cell_y < header.end { // enters the header. For example, consider a rowspan of 1: if
// `y + 1 = header.start` holds, that means `y < header.start`, and it
// only occupies one row (`y`), so the cell is actually not in
// conflict.
if cell_y < header.end && cell_y + rowspan > header.start {
bail!( bail!(
"cell would conflict with header spanning the same position"; "cell would conflict with header spanning the same position";
hint: "try moving the cell or the header" hint: "try moving the cell or the header"
@ -1949,13 +1994,8 @@ fn check_for_conflicting_cell_row(
} }
} }
if let Some((footer_end, _, footer)) = footer { if let Some((_, _, footer)) = footer {
// NOTE: y + rowspan >, not >=, footer.start, to check if the rowspan if cell_y < footer.end && cell_y + rowspan > footer.start {
// enters the footer. For example, consider a rowspan of 1: if
// `y + 1 = footer.start` holds, that means `y < footer.start`, and it
// only occupies one row (`y`), so the cell is actually not in
// conflict.
if cell_y < *footer_end && cell_y + rowspan > footer.start {
bail!( bail!(
"cell would conflict with footer spanning the same position"; "cell would conflict with footer spanning the same position";
hint: "try reducing the cell's rowspan or moving the footer" hint: "try reducing the cell's rowspan or moving the footer"

View File

@ -296,7 +296,9 @@ fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
let rows = rows.drain(ft.unwrap().start..); let rows = rows.drain(ft.unwrap().start..);
elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row)))) elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row))))
}); });
let header = grid.header.map(|hd| { // TODO: Headers and footers in arbitrary positions
// Right now, only those at either end are accepted
let header = grid.headers.first().filter(|h| h.unwrap().start == 0).map(|hd| {
let rows = rows.drain(..hd.unwrap().end); let rows = rows.drain(..hd.unwrap().end);
elem(tag::thead, Content::sequence(rows.map(|row| tr(tag::th, row)))) elem(tag::thead, Content::sequence(rows.map(|row| tr(tag::th, row))))
}); });