mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Forbid footnote migration in pending floats (#5497)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
73dd5a085e
commit
c2cc09e71a
@ -214,6 +214,13 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lay out the inner contents of a column.
|
/// 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<Frame> {
|
fn column_contents(&mut self, regions: Regions) -> FlowResult<Frame> {
|
||||||
// Process pending footnotes.
|
// Process pending footnotes.
|
||||||
for note in std::mem::take(&mut self.work.footnotes) {
|
for note in std::mem::take(&mut self.work.footnotes) {
|
||||||
@ -222,7 +229,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
|
|
||||||
// Process pending floats.
|
// Process pending floats.
|
||||||
for placed in std::mem::take(&mut self.work.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)
|
distribute(self, regions)
|
||||||
@ -236,13 +243,21 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
/// (depending on `placed.scope`).
|
/// (depending on `placed.scope`).
|
||||||
///
|
///
|
||||||
/// When the float does not fit, it is queued into `work.floats`. The
|
/// 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
|
/// value of `clearance` indicates that between the float and flow content
|
||||||
/// --- it is set if there are already distributed items.
|
/// 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(
|
pub fn float(
|
||||||
&mut self,
|
&mut self,
|
||||||
placed: &'b PlacedChild<'a>,
|
placed: &'b PlacedChild<'a>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
clearance: bool,
|
clearance: bool,
|
||||||
|
migratable: bool,
|
||||||
) -> FlowResult<()> {
|
) -> FlowResult<()> {
|
||||||
// If the float is already processed, skip it.
|
// If the float is already processed, skip it.
|
||||||
let loc = placed.location();
|
let loc = placed.location();
|
||||||
@ -291,7 +306,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle footnotes in the float.
|
// 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
|
// Determine the float's vertical alignment. We can unwrap the inner
|
||||||
// `Option` because `Custom(None)` is checked for during collection.
|
// `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
|
/// 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
|
/// any. The value of `breakable` indicates whether the element that
|
||||||
/// produced the frame is breakable. If not, the frame is treated as atomic.
|
/// 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(
|
pub fn footnotes(
|
||||||
&mut self,
|
&mut self,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
frame: &Frame,
|
frame: &Frame,
|
||||||
flow_need: Abs,
|
flow_need: Abs,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
|
migratable: bool,
|
||||||
) -> FlowResult<()> {
|
) -> FlowResult<()> {
|
||||||
// Footnotes are only supported at the root level.
|
// Footnotes are only supported at the root level.
|
||||||
if !self.config.root {
|
if !self.config.root {
|
||||||
@ -352,7 +374,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
|
|
||||||
let mut relayout = false;
|
let mut relayout = false;
|
||||||
let mut regions = *regions;
|
let mut regions = *regions;
|
||||||
let mut migratable = !breakable && regions.may_progress();
|
let mut migratable = migratable && !breakable && regions.may_progress();
|
||||||
|
|
||||||
for (y, elem) in notes {
|
for (y, elem) in notes {
|
||||||
// The amount of space used by the in-flow content that contains the
|
// The amount of space used by the in-flow content that contains the
|
||||||
|
@ -240,7 +240,8 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
|
|
||||||
// Handle fractionally sized blocks.
|
// Handle fractionally sized blocks.
|
||||||
if let Some(fr) = single.fr {
|
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.flush_tags();
|
||||||
self.items.push(Item::Fr(fr, Some(single)));
|
self.items.push(Item::Fr(fr, Some(single)));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -323,8 +324,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle footnotes.
|
// Handle footnotes.
|
||||||
self.composer
|
self.composer.footnotes(
|
||||||
.footnotes(&self.regions, &frame, frame.height(), breakable)?;
|
&self.regions,
|
||||||
|
&frame,
|
||||||
|
frame.height(),
|
||||||
|
breakable,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Push an item for the frame.
|
// Push an item for the frame.
|
||||||
self.regions.size.y -= frame.height();
|
self.regions.size.y -= frame.height();
|
||||||
@ -347,11 +353,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
placed,
|
placed,
|
||||||
&self.regions,
|
&self.regions,
|
||||||
self.items.iter().any(|item| matches!(item, Item::Frame(..))),
|
self.items.iter().any(|item| matches!(item, Item::Frame(..))),
|
||||||
|
true,
|
||||||
)?;
|
)?;
|
||||||
self.regions.size.y -= weak_spacing;
|
self.regions.size.y -= weak_spacing;
|
||||||
} else {
|
} else {
|
||||||
let frame = placed.layout(self.composer.engine, self.regions.base())?;
|
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.flush_tags();
|
||||||
self.items.push(Item::Placed(frame, placed));
|
self.items.push(Item::Placed(frame, placed));
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ pub fn collect<'a>(
|
|||||||
|
|
||||||
// The initial styles for the next page are ours unless this is a
|
// The initial styles for the next page are ours unless this is a
|
||||||
// "boundary" pagebreak. Such a pagebreak is generated at the end of
|
// "boundary" pagebreak. Such a pagebreak is generated at the end of
|
||||||
// the scope of a page set rule to ensure a page boundary. It's
|
// the scope of a page set rule to ensure a page boundary. Its
|
||||||
// styles correspond to the styles _before_ the page set rule, so we
|
// styles correspond to the styles _before_ the page set rule, so we
|
||||||
// don't want to apply it to a potential empty page.
|
// don't want to apply it to a potential empty page.
|
||||||
if !pagebreak.boundary(styles) {
|
if !pagebreak.boundary(styles) {
|
||||||
|
@ -194,7 +194,7 @@ cast! {
|
|||||||
/// before any page content, typically at the very start of the document.
|
/// before any page content, typically at the very start of the document.
|
||||||
#[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)]
|
#[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)]
|
||||||
pub struct FootnoteEntry {
|
pub struct FootnoteEntry {
|
||||||
/// The footnote for this entry. It's location can be used to determine
|
/// The footnote for this entry. Its location can be used to determine
|
||||||
/// the footnote counter state.
|
/// the footnote counter state.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
|
BIN
tests/ref/issue-5435-footnote-migration-in-floats.png
Normal file
BIN
tests/ref/issue-5435-footnote-migration-in-floats.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 448 B |
@ -295,3 +295,23 @@ A #footnote(numbering: "*")[B]<fn>, C @fn, D @fn, E @fn.
|
|||||||
--- issue-5256-multiple-footnotes-in-footnote ---
|
--- issue-5256-multiple-footnotes-in-footnote ---
|
||||||
// Test whether all footnotes inside another footnote are listed.
|
// Test whether all footnotes inside another footnote are listed.
|
||||||
#footnote[#footnote[A]#footnote[B]#footnote[C]]
|
#footnote[#footnote[A]#footnote[B]#footnote[C]]
|
||||||
|
|
||||||
|
--- issue-5435-footnote-migration-in-floats ---
|
||||||
|
// Test that a footnote should not prompt migration when in a float that was
|
||||||
|
// queued to the next page (due to the float being too large), even if the
|
||||||
|
// footnote does not fit, breaking the footnote invariant.
|
||||||
|
#set page(height: 50pt)
|
||||||
|
|
||||||
|
#place(
|
||||||
|
top,
|
||||||
|
float: true,
|
||||||
|
{
|
||||||
|
v(100pt)
|
||||||
|
footnote[a]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
#place(
|
||||||
|
top,
|
||||||
|
float: true,
|
||||||
|
footnote[b]
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user