mirror of
https://github.com/typst/typst
synced 2025-08-13 14:47:54 +08:00
use child_range instead of child_start/end
This commit is contained in:
parent
4103be5138
commit
838bdc0b89
@ -1,4 +1,5 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
use std::ops::Range;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::eco_format;
|
use ecow::eco_format;
|
||||||
@ -778,11 +779,16 @@ impl<'a> CellGrid<'a> {
|
|||||||
// instead, and use a separate counter outside them.
|
// instead, and use a separate counter outside them.
|
||||||
let mut local_auto_index = auto_index;
|
let mut local_auto_index = auto_index;
|
||||||
|
|
||||||
// The index of the first cell within this child in the grid. Note
|
// The range of rows of cells inside this child in the grid. The
|
||||||
// that cells outside headers and footers are children with a
|
// first and last rows are guaranteed to have cells (an exception
|
||||||
// single cell inside, in which case 'child_start == child_end'.
|
// is made when there is gutter, in which case the child range may
|
||||||
let mut child_start = usize::MAX;
|
// be expanded to include an additional gutter row when there is a
|
||||||
let mut child_end = 0;
|
// repeatable header or footer).
|
||||||
|
//
|
||||||
|
// Note that cells outside headers and footers are children
|
||||||
|
// with a single cell inside, in which case the range has a single
|
||||||
|
// row.
|
||||||
|
let mut child_range: Option<Range<usize>> = None;
|
||||||
let mut child_span = Span::detached();
|
let mut child_span = Span::detached();
|
||||||
|
|
||||||
// The first row in which this table child can fit.
|
// The first row in which this table child can fit.
|
||||||
@ -1036,8 +1042,10 @@ impl<'a> CellGrid<'a> {
|
|||||||
// Ensure each cell in a header or footer is fully
|
// Ensure each cell in a header or footer is fully
|
||||||
// contained within it by expanding the header or footer
|
// contained within it by expanding the header or footer
|
||||||
// towards this new cell.
|
// towards this new cell.
|
||||||
let new_child_start = child_start.min(y);
|
let (new_child_start, new_child_end) =
|
||||||
let new_child_end = child_end.max(y + rowspan);
|
child_range.clone().map_or((y, y + rowspan), |r| {
|
||||||
|
(r.start.min(y), r.end.max(y + rowspan))
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: Maybe remove this check and just keep the loop,
|
// TODO: Maybe remove this check and just keep the loop,
|
||||||
// we will loop in the "good case" anyway
|
// we will loop in the "good case" anyway
|
||||||
@ -1079,8 +1087,15 @@ impl<'a> CellGrid<'a> {
|
|||||||
// Of course, we also need to check for downward expansion
|
// Of course, we also need to check for downward expansion
|
||||||
// as there could be a non-empty row below the header, but
|
// as there could be a non-empty row below the header, but
|
||||||
// the upward case is highlighted due to its differences.
|
// the upward case is highlighted due to its differences.
|
||||||
let new_rows = (new_child_start..child_start.min(new_child_end))
|
let new_rows = child_range.clone().map_or(
|
||||||
.chain(child_end.max(new_child_start + 1)..new_child_end);
|
(new_child_start..new_child_end).chain(0..0),
|
||||||
|
|r| {
|
||||||
|
// NOTE: To keep types the same, we have to always
|
||||||
|
// return (range).chain(range), which justifies
|
||||||
|
// chaining an empty range above.
|
||||||
|
(new_child_start..r.start).chain(r.end..new_child_end)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Note that simply checking for non-empty rows like below
|
// Note that simply checking for non-empty rows like below
|
||||||
// not only prevents conflicts with top-level cells
|
// not only prevents conflicts with top-level cells
|
||||||
@ -1089,9 +1104,9 @@ impl<'a> CellGrid<'a> {
|
|||||||
// an invariant that even empty headers and footers must
|
// an invariant that even empty headers and footers must
|
||||||
// contain at least one 'Some(...)' position in
|
// contain at least one 'Some(...)' position in
|
||||||
// 'resolved_cells'. More precisely, each header and footer
|
// 'resolved_cells'. More precisely, each header and footer
|
||||||
// has at least one 'Some(...)' cell at 'child_start' and
|
// has at least one 'Some(...)' cell at 'child_range.start'
|
||||||
// at 'child_end - 1' - non-empty headers and footers don't
|
// and at 'child_range.end - 1' - non-empty headers and
|
||||||
// span any unnecessary rows.
|
// footers don't span any unnecessary rows.
|
||||||
for new_y in new_rows {
|
for new_y in new_rows {
|
||||||
if let Some(new_row @ [_non_empty, ..]) = resolved_cells
|
if let Some(new_row @ [_non_empty, ..]) = resolved_cells
|
||||||
.get(new_y * c..)
|
.get(new_y * c..)
|
||||||
@ -1104,7 +1119,7 @@ impl<'a> CellGrid<'a> {
|
|||||||
// - Detect when header or footer collided with
|
// - Detect when header or footer collided with
|
||||||
// another header or footer and provide a
|
// another header or footer and provide a
|
||||||
// better error message if so.
|
// better error message if so.
|
||||||
if new_y < child_start.min(new_child_end) {
|
if child_range.map_or(true, |r| new_y < r.start) {
|
||||||
bail!(
|
bail!(
|
||||||
cell_span,
|
cell_span,
|
||||||
"cell would cause header or footer to expand to non-empty row {new_y}";
|
"cell would cause header or footer to expand to non-empty row {new_y}";
|
||||||
@ -1132,8 +1147,7 @@ impl<'a> CellGrid<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
child_start = new_child_start;
|
child_range = Some(new_child_start..new_child_end);
|
||||||
child_end = new_child_end;
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: Perhaps do this during cell resolving?
|
// TODO: Perhaps do this during cell resolving?
|
||||||
// This is unnecessary for non-row-positioned cells, as
|
// This is unnecessary for non-row-positioned cells, as
|
||||||
@ -1234,79 +1248,85 @@ impl<'a> CellGrid<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_row_group && child_start == usize::MAX {
|
if is_row_group {
|
||||||
// Empty header/footer: consider the header/footer to be
|
let child_range = match child_range {
|
||||||
// at the next empty row after the latest auto index.
|
Some(child_range) => child_range,
|
||||||
local_auto_index = first_available_row * c;
|
|
||||||
child_start = first_available_row;
|
|
||||||
child_end = child_start + 1;
|
|
||||||
|
|
||||||
if resolved_cells.len() <= c * child_start {
|
None => {
|
||||||
// Ensure the automatically chosen row actually exists.
|
// Empty header/footer: consider the header/footer to be
|
||||||
resolved_cells.resize_with(c * (child_start + 1), || None);
|
// at the next empty row after the latest auto index.
|
||||||
}
|
local_auto_index = first_available_row * c;
|
||||||
|
let child_start = first_available_row;
|
||||||
|
let child_end = child_start + 1;
|
||||||
|
|
||||||
// Even though this header or footer is fully empty, we add one
|
if resolved_cells.len() <= c * child_start {
|
||||||
// default cell to maintain the invariant that each header and
|
// Ensure the automatically chosen row actually exists.
|
||||||
// footer has at least one `Some(...)` cell at its first row
|
resolved_cells.resize_with(c * (child_start + 1), || None);
|
||||||
// and at least one at its last row (here they are the same
|
}
|
||||||
// row, of course). This invariant is important to ensure
|
|
||||||
// `find_next_empty_row` will skip through any existing headers
|
|
||||||
// and footers without having to loop through them each time.
|
|
||||||
// Cells themselves, unfortunately, still have to.
|
|
||||||
assert!(resolved_cells[local_auto_index].is_none());
|
|
||||||
let (first_x, first_y) = (0, first_available_row);
|
|
||||||
resolved_cells[local_auto_index] =
|
|
||||||
Some(Entry::Cell(T::default().resolve_cell(
|
|
||||||
first_x,
|
|
||||||
first_y,
|
|
||||||
&fill.resolve(engine, styles, first_x, first_y)?,
|
|
||||||
align.resolve(engine, styles, first_x, first_y)?,
|
|
||||||
inset.resolve(engine, styles, first_x, first_y)?,
|
|
||||||
stroke.resolve(engine, styles, first_x, first_y)?,
|
|
||||||
resolve_breakable(first_y, 1),
|
|
||||||
locator.next(&()),
|
|
||||||
styles,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_header {
|
// Even though this header or footer is fully empty, we add one
|
||||||
if child_start != 0 {
|
// default cell to maintain the invariant that each header and
|
||||||
bail!(
|
// footer has at least one `Some(...)` cell at its first row
|
||||||
child_span,
|
// and at least one at its last row (here they are the same
|
||||||
"header must start at the first row";
|
// row, of course). This invariant is important to ensure
|
||||||
hint: "remove any rows before the header"
|
// `find_next_empty_row` will skip through any existing headers
|
||||||
);
|
// and footers without having to loop through them each time.
|
||||||
}
|
// Cells themselves, unfortunately, still have to.
|
||||||
|
assert!(resolved_cells[local_auto_index].is_none());
|
||||||
|
let (first_x, first_y) = (0, first_available_row);
|
||||||
|
resolved_cells[local_auto_index] =
|
||||||
|
Some(Entry::Cell(T::default().resolve_cell(
|
||||||
|
first_x,
|
||||||
|
first_y,
|
||||||
|
&fill.resolve(engine, styles, first_x, first_y)?,
|
||||||
|
align.resolve(engine, styles, first_x, first_y)?,
|
||||||
|
inset.resolve(engine, styles, first_x, first_y)?,
|
||||||
|
stroke.resolve(engine, styles, first_x, first_y)?,
|
||||||
|
resolve_breakable(first_y, 1),
|
||||||
|
locator.next(&()),
|
||||||
|
styles,
|
||||||
|
)));
|
||||||
|
|
||||||
header = Some(Header {
|
child_start..child_end
|
||||||
// Later on, we have to correct this number in case there
|
}
|
||||||
// is gutter. But only once all cells have been analyzed
|
};
|
||||||
// and the header has fully expanded in the fixup loop
|
|
||||||
// below.
|
|
||||||
end: child_end,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_footer {
|
if is_header {
|
||||||
// Only check if the footer is at the end later, once we know
|
if child_range.start != 0 {
|
||||||
// the final amount of rows.
|
bail!(
|
||||||
footer = Some((
|
child_span,
|
||||||
child_end,
|
"header must start at the first row";
|
||||||
child_span,
|
hint: "remove any rows before the header"
|
||||||
Footer {
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
header = Some(Header {
|
||||||
// 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's and footer's exact boundaries are
|
// and the header has fully expanded in the fixup loop
|
||||||
// known. That is because the gutter row immediately
|
// below.
|
||||||
// before the footer might not be included as part of
|
end: child_range.end,
|
||||||
// the footer if it is contained within the header.
|
});
|
||||||
start: child_start,
|
}
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_row_group {
|
if is_footer {
|
||||||
|
// Only check if the footer is at the end later, once we know
|
||||||
|
// the final amount of rows.
|
||||||
|
footer = Some((
|
||||||
|
child_range.end,
|
||||||
|
child_span,
|
||||||
|
Footer {
|
||||||
|
// Later on, we have to correct this number in case there
|
||||||
|
// is gutter, but only once all cells have been analyzed
|
||||||
|
// and the header's and footer's exact boundaries are
|
||||||
|
// known. That is because the gutter row immediately
|
||||||
|
// before the footer might not be included as part of
|
||||||
|
// the footer if it is contained within the header.
|
||||||
|
start: child_range.start,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// The child was a single cell outside headers or footers.
|
// The child was a single cell outside headers or footers.
|
||||||
// Therefore, 'local_auto_index' for this table child was
|
// Therefore, 'local_auto_index' for this table child was
|
||||||
// simply an alias for 'auto_index', so we update it as needed.
|
// simply an alias for 'auto_index', so we update it as needed.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user