From c2cc09e71ab97f752cd07acad7c38443c58cf64f Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 9 Dec 2024 06:55:58 -0300 Subject: Forbid footnote migration in pending floats (#5497) Co-authored-by: Laurenz --- crates/typst-layout/src/flow/compose.rs | 32 +++++++++++++++++++++++++----- crates/typst-layout/src/flow/distribute.rs | 16 +++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) (limited to 'crates/typst-layout/src/flow') diff --git a/crates/typst-layout/src/flow/compose.rs b/crates/typst-layout/src/flow/compose.rs index 9650a7bc..32645675 100644 --- a/crates/typst-layout/src/flow/compose.rs +++ b/crates/typst-layout/src/flow/compose.rs @@ -214,6 +214,13 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { } /// Lay out the inner contents of a column. + /// + /// Pending floats and footnotes are also laid out at this step. For those, + /// however, we forbid footnote migration (moving the frame containing the + /// footnote reference if the corresponding entry doesn't fit), allowing + /// the footnote invariant to be broken, as it would require handling a + /// [`Stop::Finish`] at this point, but that is exclusively handled by the + /// distributor. fn column_contents(&mut self, regions: Regions) -> FlowResult { // Process pending footnotes. for note in std::mem::take(&mut self.work.footnotes) { @@ -222,7 +229,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { // Process pending floats. for placed in std::mem::take(&mut self.work.floats) { - self.float(placed, ®ions, false)?; + self.float(placed, ®ions, false, false)?; } distribute(self, regions) @@ -236,13 +243,21 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { /// (depending on `placed.scope`). /// /// When the float does not fit, it is queued into `work.floats`. The - /// value of `clearance` that between the float and flow content is needed - /// --- it is set if there are already distributed items. + /// value of `clearance` indicates that between the float and flow content + /// is needed --- it is set if there are already distributed items. + /// + /// The value of `migratable` determines whether footnotes within the float + /// should be allowed to prompt its migration if they don't fit in order to + /// respect the footnote invariant (entries in the same page as the + /// references), triggering [`Stop::Finish`]. This is usually `true` within + /// the distributor, as it can handle that particular flow event, and + /// `false` elsewhere. pub fn float( &mut self, placed: &'b PlacedChild<'a>, regions: &Regions, clearance: bool, + migratable: bool, ) -> FlowResult<()> { // If the float is already processed, skip it. let loc = placed.location(); @@ -291,7 +306,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { } // Handle footnotes in the float. - self.footnotes(regions, &frame, need, false)?; + self.footnotes(regions, &frame, need, false, migratable)?; // Determine the float's vertical alignment. We can unwrap the inner // `Option` because `Custom(None)` is checked for during collection. @@ -326,12 +341,19 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { /// Lays out footnotes in the `frame` if this is the root flow and there are /// any. The value of `breakable` indicates whether the element that /// produced the frame is breakable. If not, the frame is treated as atomic. + /// + /// The value of `migratable` indicates whether footnote migration should be + /// possible (at least for the first footnote found in the frame, as it is + /// forbidden for the second footnote onwards). It is usually `true` within + /// the distributor and `false` elsewhere, as the distributor can handle + /// [`Stop::Finish`] which is returned when migration is requested. pub fn footnotes( &mut self, regions: &Regions, frame: &Frame, flow_need: Abs, breakable: bool, + migratable: bool, ) -> FlowResult<()> { // Footnotes are only supported at the root level. if !self.config.root { @@ -352,7 +374,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { let mut relayout = false; let mut regions = *regions; - let mut migratable = !breakable && regions.may_progress(); + let mut migratable = migratable && !breakable && regions.may_progress(); for (y, elem) in notes { // The amount of space used by the in-flow content that contains the diff --git a/crates/typst-layout/src/flow/distribute.rs b/crates/typst-layout/src/flow/distribute.rs index 5b293d35..7a1cf426 100644 --- a/crates/typst-layout/src/flow/distribute.rs +++ b/crates/typst-layout/src/flow/distribute.rs @@ -240,7 +240,8 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { // Handle fractionally sized blocks. if let Some(fr) = single.fr { - self.composer.footnotes(&self.regions, &frame, Abs::zero(), false)?; + self.composer + .footnotes(&self.regions, &frame, Abs::zero(), false, true)?; self.flush_tags(); self.items.push(Item::Fr(fr, Some(single))); return Ok(()); @@ -323,8 +324,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { } // Handle footnotes. - self.composer - .footnotes(&self.regions, &frame, frame.height(), breakable)?; + self.composer.footnotes( + &self.regions, + &frame, + frame.height(), + breakable, + true, + )?; // Push an item for the frame. self.regions.size.y -= frame.height(); @@ -347,11 +353,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { placed, &self.regions, self.items.iter().any(|item| matches!(item, Item::Frame(..))), + true, )?; self.regions.size.y -= weak_spacing; } else { let frame = placed.layout(self.composer.engine, self.regions.base())?; - self.composer.footnotes(&self.regions, &frame, Abs::zero(), true)?; + self.composer + .footnotes(&self.regions, &frame, Abs::zero(), true, true)?; self.flush_tags(); self.items.push(Item::Placed(frame, placed)); } -- cgit v1.2.3