use vector of maybe repeatable headers when resolving

This commit is contained in:
PgBiel 2025-04-18 19:33:45 -03:00
parent 13b4677270
commit fe75a29488

View File

@ -481,6 +481,7 @@ pub enum Repeatable<T> {
impl<T> Repeatable<T> { impl<T> Repeatable<T> {
/// Gets the value inside this repeatable, regardless of whether /// Gets the value inside this repeatable, regardless of whether
/// it repeats. /// it repeats.
#[inline]
pub fn unwrap(&self) -> &T { pub fn unwrap(&self) -> &T {
match self { match self {
Self::Repeated(repeated) => repeated, Self::Repeated(repeated) => repeated,
@ -488,7 +489,18 @@ impl<T> Repeatable<T> {
} }
} }
/// Gets the value inside this repeatable, regardless of whether
/// it repeats.
#[inline]
pub fn unwrap_mut(&mut self) -> &mut T {
match self {
Self::Repeated(repeated) => repeated,
Self::NotRepeated(not_repeated) => not_repeated,
}
}
/// Returns `Some` if the value is repeated, `None` otherwise. /// Returns `Some` if the value is repeated, `None` otherwise.
#[inline]
pub fn as_repeated(&self) -> Option<&T> { pub fn as_repeated(&self) -> Option<&T> {
match self { match self {
Self::Repeated(repeated) => Some(repeated), Self::Repeated(repeated) => Some(repeated),
@ -974,6 +986,9 @@ struct RowGroupData {
span: Span, span: Span,
kind: RowGroupKind, kind: RowGroupKind,
/// Whether this header or footer may repeat.
repeat: bool,
/// Level of this header or footer. /// Level of this header or footer.
repeatable_level: NonZeroU32, repeatable_level: NonZeroU32,
@ -1024,8 +1039,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
let mut pending_vlines: Vec<(Span, Line)> = vec![]; let mut pending_vlines: Vec<(Span, Line)> = vec![];
let has_gutter = self.gutter.any(|tracks| !tracks.is_empty()); let has_gutter = self.gutter.any(|tracks| !tracks.is_empty());
let mut headers: Vec<Header> = vec![]; let mut headers: Vec<Repeatable<Header>> = vec![];
let mut repeat_header = false;
// Stores where the footer is supposed to end, its span, and the // Stores where the footer is supposed to end, its span, and the
// actual footer structure. // actual footer structure.
@ -1069,7 +1083,6 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
&mut pending_hlines, &mut pending_hlines,
&mut pending_vlines, &mut pending_vlines,
&mut headers, &mut headers,
&mut repeat_header,
&mut footer, &mut footer,
&mut repeat_footer, &mut repeat_footer,
&mut auto_index, &mut auto_index,
@ -1089,10 +1102,9 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
row_amount, row_amount,
)?; )?;
let (headers, footer) = self.finalize_headers_and_footers( let footer = self.finalize_headers_and_footers(
has_gutter, has_gutter,
headers, &mut headers,
repeat_header,
footer, footer,
repeat_footer, repeat_footer,
row_amount, row_amount,
@ -1123,8 +1135,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
columns: usize, columns: usize,
pending_hlines: &mut Vec<(Span, Line, bool)>, pending_hlines: &mut Vec<(Span, Line, bool)>,
pending_vlines: &mut Vec<(Span, Line)>, pending_vlines: &mut Vec<(Span, Line)>,
headers: &mut Vec<Header>, headers: &mut Vec<Repeatable<Header>>,
repeat_header: &mut bool,
footer: &mut Option<(usize, Span, Footer)>, footer: &mut Option<(usize, Span, Footer)>,
repeat_footer: &mut bool, repeat_footer: &mut bool,
auto_index: &mut usize, auto_index: &mut usize,
@ -1168,13 +1179,12 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
range: None, range: None,
span, span,
kind: RowGroupKind::Header, kind: RowGroupKind::Header,
repeat,
repeatable_level: level, repeatable_level: level,
top_hlines_start: pending_hlines.len(), top_hlines_start: pending_hlines.len(),
top_hlines_end: None, top_hlines_end: None,
}); });
*repeat_header = repeat;
first_available_row = first_available_row =
find_next_empty_row(resolved_cells, local_auto_index, columns); find_next_empty_row(resolved_cells, local_auto_index, columns);
@ -1199,14 +1209,13 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
row_group_data = Some(RowGroupData { row_group_data = Some(RowGroupData {
range: None, range: None,
span, span,
repeat,
kind: RowGroupKind::Footer, kind: RowGroupKind::Footer,
repeatable_level: NonZeroU32::ONE, repeatable_level: NonZeroU32::ONE,
top_hlines_start: pending_hlines.len(), top_hlines_start: pending_hlines.len(),
top_hlines_end: None, top_hlines_end: None,
}); });
*repeat_footer = repeat;
first_available_row = first_available_row =
find_next_empty_row(resolved_cells, local_auto_index, columns); find_next_empty_row(resolved_cells, local_auto_index, columns);
@ -1521,7 +1530,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
match row_group.kind { match row_group.kind {
RowGroupKind::Header => { RowGroupKind::Header => {
headers.push(Header { let data = Header {
start: group_range.start, 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
@ -1531,6 +1540,12 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
end: group_range.end, end: group_range.end,
level: row_group.repeatable_level.get(), level: row_group.repeatable_level.get(),
};
headers.push(if row_group.repeat {
Repeatable::Repeated(data)
} else {
Repeatable::NotRepeated(data)
}); });
} }
@ -1552,6 +1567,8 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
level: 1, level: 1,
}, },
)); ));
*repeat_footer = row_group.repeat;
} }
} }
} else { } else {
@ -1725,19 +1742,18 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
fn finalize_headers_and_footers( fn finalize_headers_and_footers(
&self, &self,
has_gutter: bool, has_gutter: bool,
headers: Vec<Header>, headers: &mut [Repeatable<Header>],
repeat_header: bool,
footer: Option<(usize, Span, Footer)>, footer: Option<(usize, Span, Footer)>,
repeat_footer: bool, repeat_footer: bool,
row_amount: usize, row_amount: usize,
) -> SourceResult<(Vec<Repeatable<Header>>, Option<Repeatable<Footer>>)> { ) -> SourceResult<Option<Repeatable<Footer>>> {
let headers: Vec<Repeatable<Header>> = headers
.into_iter()
.map(|mut header| {
// Repeat the gutter below a header (hence why we don't // Repeat the gutter below a header (hence why we don't
// subtract 1 from the gutter case). // subtract 1 from the gutter case).
// Don't do this if there are no rows under the header. // Don't do this if there are no rows under the header.
if has_gutter { if has_gutter {
for header in &mut *headers {
let header = header.unwrap_mut();
// Index of first y is doubled, as each row before it // Index of first y is doubled, as each row before it
// receives a gutter row below. // receives a gutter row below.
header.start *= 2; header.start *= 2;
@ -1762,16 +1778,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
let row_amount = (2 * row_amount).saturating_sub(1); let row_amount = (2 * row_amount).saturating_sub(1);
header.end = header.end.min(row_amount); header.end = header.end.min(row_amount);
} }
header
})
.map(|header| {
if repeat_header {
Repeatable::Repeated(header)
} else {
Repeatable::NotRepeated(header)
} }
})
.collect();
let footer = footer let footer = footer
.map(|(footer_end, footer_span, mut footer)| { .map(|(footer_end, footer_span, mut footer)| {
@ -1819,7 +1826,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
} }
}); });
Ok((headers, footer)) Ok(footer)
} }
/// Resolves the cell's fields based on grid-wide properties. /// Resolves the cell's fields based on grid-wide properties.
@ -1990,7 +1997,7 @@ fn expand_row_group(
/// Check if a cell's fixed row would conflict with a header or footer. /// Check if a cell's fixed row would conflict with a header or footer.
fn check_for_conflicting_cell_row( fn check_for_conflicting_cell_row(
headers: &[Header], headers: &[Repeatable<Header>],
footer: Option<&(usize, Span, Footer)>, footer: Option<&(usize, Span, Footer)>,
cell_y: usize, cell_y: usize,
rowspan: usize, rowspan: usize,
@ -2001,10 +2008,9 @@ fn check_for_conflicting_cell_row(
// `y + 1 = header.start` holds, that means `y < header.start`, and it // `y + 1 = header.start` holds, that means `y < header.start`, and it
// only occupies one row (`y`), so the cell is actually not in // only occupies one row (`y`), so the cell is actually not in
// conflict. // conflict.
if headers if headers.iter().any(|header| {
.iter() cell_y < header.unwrap().end && cell_y + rowspan > header.unwrap().start
.any(|header| 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"
@ -2038,7 +2044,7 @@ fn resolve_cell_position(
cell_y: Smart<usize>, cell_y: Smart<usize>,
colspan: usize, colspan: usize,
rowspan: usize, rowspan: usize,
headers: &[Header], headers: &[Repeatable<Header>],
footer: Option<&(usize, Span, Footer)>, footer: Option<&(usize, Span, Footer)>,
resolved_cells: &[Option<Entry>], resolved_cells: &[Option<Entry>],
auto_index: &mut usize, auto_index: &mut usize,
@ -2169,7 +2175,7 @@ fn resolve_cell_position(
/// have cells specified by the user) as well as any headers and footers. /// have cells specified by the user) as well as any headers and footers.
#[inline] #[inline]
fn find_next_available_position<const SKIP_ROWS: bool>( fn find_next_available_position<const SKIP_ROWS: bool>(
headers: &[Header], headers: &[Repeatable<Header>],
footer: Option<&(usize, Span, Footer)>, footer: Option<&(usize, Span, Footer)>,
resolved_cells: &[Option<Entry<'_>>], resolved_cells: &[Option<Entry<'_>>],
columns: usize, columns: usize,
@ -2196,9 +2202,13 @@ fn find_next_available_position<const SKIP_ROWS: bool>(
// would become impractically large before this overflows. // would become impractically large before this overflows.
resolved_index += 1; resolved_index += 1;
} }
} else if let Some(header) = headers.iter().find(|header| { // TODO: consider keeping vector of upcoming headers to make this check
// non-quadratic (O(cells) instead of O(headers * cells)).
} else if let Some(header) =
headers.iter().map(Repeatable::unwrap).find(|header| {
(header.start * columns..header.end * columns).contains(&resolved_index) (header.start * columns..header.end * columns).contains(&resolved_index)
}) { })
{
// Skip header (can't place a cell inside it from outside it). // Skip header (can't place a cell inside it from outside it).
resolved_index = header.end * columns; resolved_index = header.end * columns;