summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst/src/layout/flow/collect.rs25
-rw-r--r--tests/ref/issue-5024-spill-backlog.pngbin0 -> 270 bytes
-rw-r--r--tests/suite/layout/flow/flow.typ4
3 files changed, 23 insertions, 6 deletions
diff --git a/crates/typst/src/layout/flow/collect.rs b/crates/typst/src/layout/flow/collect.rs
index 9cd9bdc4..9fc64f06 100644
--- a/crates/typst/src/layout/flow/collect.rs
+++ b/crates/typst/src/layout/flow/collect.rs
@@ -403,6 +403,7 @@ impl<'a> MultiChild<'a> {
full: regions.full,
first: regions.size.y,
backlog: vec![],
+ min_backlog_len: regions.backlog.len(),
});
}
@@ -474,6 +475,7 @@ pub struct MultiSpill<'a, 'b> {
first: Abs,
full: Abs,
backlog: Vec<Abs>,
+ min_backlog_len: usize,
}
impl MultiSpill<'_, '_> {
@@ -483,17 +485,18 @@ impl MultiSpill<'_, '_> {
engine: &mut Engine,
regions: Regions,
) -> SourceResult<(Frame, Option<Self>)> {
- // We build regions for the whole `MultiChild` with the sizes passed to
- // earlier parts of it plus the new regions. Then, we layout the
- // complete block, but extract only the suffix that interests us.
+ // The first region becomes unchangable and committed to our backlog.
self.backlog.push(regions.size.y);
+ // The remaining regions are ephemeral and may be replaced.
let mut backlog: Vec<_> =
self.backlog.iter().chain(regions.backlog).copied().collect();
- // Remove unnecessary backlog items (also to prevent it from growing
- // unnecessarily, which would change the region's hash).
- while !backlog.is_empty() && backlog.last().copied() == regions.last {
+ // Remove unnecessary backlog items to prevent it from growing
+ // unnecessarily, changing the region's hash.
+ while backlog.len() > self.min_backlog_len
+ && backlog.last().copied() == regions.last
+ {
backlog.pop();
}
@@ -513,6 +516,16 @@ impl MultiSpill<'_, '_> {
.into_iter()
.skip(self.backlog.len());
+ // Ensure that the backlog never shrinks, so that unwrapping below is at
+ // least fairly safe. Note that the whole region juggling here is
+ // fundamentally not ideal: It is a compatibility layer between the old
+ // (all regions provided upfront) & new (each region provided on-demand,
+ // like an iterator) layout model. This approach is not 100% correct, as
+ // in the old model later regions could have an effect on earlier
+ // frames, but it's the best we can do for now, until the multi
+ // layouters are refactored to the new model.
+ self.min_backlog_len = self.min_backlog_len.max(backlog.len());
+
// Save the first frame.
let frame = frames.next().unwrap();
diff --git a/tests/ref/issue-5024-spill-backlog.png b/tests/ref/issue-5024-spill-backlog.png
new file mode 100644
index 00000000..76fd9fbb
--- /dev/null
+++ b/tests/ref/issue-5024-spill-backlog.png
Binary files differ
diff --git a/tests/suite/layout/flow/flow.typ b/tests/suite/layout/flow/flow.typ
index fcbc005b..5a5e8404 100644
--- a/tests/suite/layout/flow/flow.typ
+++ b/tests/suite/layout/flow/flow.typ
@@ -76,3 +76,7 @@ Hi
#block(rect(width: 80%, height: 80pt), breakable: false)
#lines(6)
]
+
+--- issue-5024-spill-backlog ---
+#set page(columns: 2, height: 50pt)
+#columns(2)[Hello]