mirror of
https://github.com/typst/typst
synced 2025-05-17 02:25:27 +08:00
Improve footnote handling for multi-page blocks
This commit is contained in:
parent
2b812259c2
commit
56f7ede964
@ -295,11 +295,15 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
// Layout the block itself.
|
// Layout the block itself.
|
||||||
let sticky = BlockElem::sticky_in(styles);
|
let sticky = BlockElem::sticky_in(styles);
|
||||||
let fragment = block.layout(vt, styles, self.regions)?;
|
let fragment = block.layout(vt, styles, self.regions)?;
|
||||||
|
let mut notes = Vec::new();
|
||||||
|
|
||||||
for (i, frame) in fragment.into_iter().enumerate() {
|
for (i, frame) in fragment.into_iter().enumerate() {
|
||||||
if i > 0
|
// Find footnotes in the frame.
|
||||||
&& !self.items.iter().all(|item| matches!(item, FlowItem::Footnote(_)))
|
if self.root {
|
||||||
{
|
find_footnotes(&mut notes, &frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
self.finish_region()?;
|
self.finish_region()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,6 +313,11 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.root && !self.handle_footnotes(vt, &mut notes, false)? {
|
||||||
|
self.finish_region()?;
|
||||||
|
self.handle_footnotes(vt, &mut notes, true)?;
|
||||||
|
}
|
||||||
|
|
||||||
self.root = is_root;
|
self.root = is_root;
|
||||||
self.regions.root = false;
|
self.regions.root = false;
|
||||||
self.last_was_par = false;
|
self.last_was_par = false;
|
||||||
@ -332,15 +341,25 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
self.regions.size.y -= v
|
self.regions.size.y -= v
|
||||||
}
|
}
|
||||||
FlowItem::Fractional(_) => {}
|
FlowItem::Fractional(_) => {}
|
||||||
FlowItem::Frame { ref frame, .. } => {
|
FlowItem::Frame { ref frame, movable, .. } => {
|
||||||
let size = frame.size();
|
let size = frame.size();
|
||||||
if !self.regions.size.y.fits(size.y) && !self.regions.in_last() {
|
if !self.regions.size.y.fits(size.y) && !self.regions.in_last() {
|
||||||
self.finish_region()?;
|
self.finish_region()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.regions.size.y -= size.y;
|
self.regions.size.y -= size.y;
|
||||||
if self.root {
|
if self.root && movable {
|
||||||
return self.handle_footnotes(vt, item);
|
let mut notes = Vec::new();
|
||||||
|
find_footnotes(&mut notes, frame);
|
||||||
|
self.items.push(item);
|
||||||
|
if !self.handle_footnotes(vt, &mut notes, false)? {
|
||||||
|
let item = self.items.pop();
|
||||||
|
self.finish_region()?;
|
||||||
|
self.items.extend(item);
|
||||||
|
self.regions.size.y -= size.y;
|
||||||
|
self.handle_footnotes(vt, &mut notes, true)?;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FlowItem::Placed(_) => {}
|
FlowItem::Placed(_) => {}
|
||||||
@ -456,34 +475,18 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
impl FlowLayouter<'_> {
|
impl FlowLayouter<'_> {
|
||||||
/// Processes all footnotes in the frame.
|
/// Processes all footnotes in the frame.
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn handle_footnotes(&mut self, vt: &mut Vt, item: FlowItem) -> SourceResult<()> {
|
fn handle_footnotes(
|
||||||
// Find footnotes in the frame.
|
&mut self,
|
||||||
let mut notes = Vec::new();
|
vt: &mut Vt,
|
||||||
|
notes: &mut Vec<FootnoteElem>,
|
||||||
|
force: bool,
|
||||||
|
) -> SourceResult<bool> {
|
||||||
|
let items_len = self.items.len();
|
||||||
|
let notes_len = notes.len();
|
||||||
|
|
||||||
let mut is_movable = true;
|
// Process footnotes one at a time.
|
||||||
if let FlowItem::Frame { frame, movable, .. } = &item {
|
|
||||||
find_footnotes(&mut notes, frame);
|
|
||||||
is_movable = *movable;
|
|
||||||
}
|
|
||||||
|
|
||||||
let prev_len = self.items.len();
|
|
||||||
self.items.push(item);
|
|
||||||
|
|
||||||
// No new footnotes.
|
|
||||||
if notes.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The currently handled footnote.
|
|
||||||
let mut k = 0;
|
let mut k = 0;
|
||||||
|
while k < notes.len() {
|
||||||
// Whether we can still skip one region to ensure that the footnote
|
|
||||||
// and its entry are on the same page.
|
|
||||||
let mut can_skip = true;
|
|
||||||
|
|
||||||
// Process footnotes.
|
|
||||||
'outer: while k < notes.len() {
|
|
||||||
let had_footnotes = self.has_footnotes;
|
|
||||||
if !self.has_footnotes {
|
if !self.has_footnotes {
|
||||||
self.layout_footnote_separator(vt)?;
|
self.layout_footnote_separator(vt)?;
|
||||||
}
|
}
|
||||||
@ -494,33 +497,24 @@ impl FlowLayouter<'_> {
|
|||||||
.layout(vt, self.styles, self.regions.with_root(false))?
|
.layout(vt, self.styles, self.regions.with_root(false))?
|
||||||
.into_frames();
|
.into_frames();
|
||||||
|
|
||||||
// If the entries didn't fit, undo the separator layout, move the
|
// If the entries didn't fit, abort (to keep footnote and entry
|
||||||
// item into the next region (to keep footnote and entry together)
|
// together).
|
||||||
// and try again.
|
if !force && k == 0 && frames.first().map_or(false, Frame::is_empty) {
|
||||||
if can_skip && frames.first().map_or(false, Frame::is_empty) {
|
// Remove existing footnotes attempts because we need to
|
||||||
// Remove separator
|
// move the item to the next page.
|
||||||
if !had_footnotes {
|
notes.truncate(notes_len);
|
||||||
self.items.pop();
|
|
||||||
|
// Undo region modifications.
|
||||||
|
for item in self.items.drain(items_len..) {
|
||||||
|
self.regions.size.y -= item.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_movable {
|
return Ok(false);
|
||||||
let moved: Vec<_> = self.items.drain(prev_len..).collect();
|
|
||||||
self.finish_region()?;
|
|
||||||
self.has_footnotes =
|
|
||||||
moved.iter().any(|item| matches!(item, FlowItem::Footnote(_)));
|
|
||||||
self.regions.size.y -= moved.iter().map(FlowItem::height).sum();
|
|
||||||
self.items.extend(moved);
|
|
||||||
} else {
|
|
||||||
self.finish_region()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
can_skip = false;
|
|
||||||
continue 'outer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev = notes.len();
|
let prev = notes.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
find_footnotes(&mut notes, &frame);
|
find_footnotes(notes, &frame);
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
self.finish_region()?;
|
self.finish_region()?;
|
||||||
self.layout_footnote_separator(vt)?;
|
self.layout_footnote_separator(vt)?;
|
||||||
@ -532,14 +526,15 @@ impl FlowLayouter<'_> {
|
|||||||
|
|
||||||
k += 1;
|
k += 1;
|
||||||
|
|
||||||
// Process the nested notes before dealing with further notes.
|
// Process the nested notes before dealing with further top-level
|
||||||
|
// notes.
|
||||||
let nested = notes.len() - prev;
|
let nested = notes.len() - prev;
|
||||||
if nested > 0 {
|
if nested > 0 {
|
||||||
notes[k..].rotate_right(nested);
|
notes[k..].rotate_right(nested);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout and save the footnote separator, typically a line.
|
/// Layout and save the footnote separator, typically a line.
|
||||||
|
BIN
tests/ref/meta/footnote-table.png
Normal file
BIN
tests/ref/meta/footnote-table.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
@ -5,6 +5,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
#set page(height: 100pt)
|
#set page(height: 100pt)
|
||||||
#v(30pt)
|
#v(40pt)
|
||||||
A #footnote[a] \
|
A #footnote[a] \
|
||||||
B #footnote[b]
|
B #footnote[b]
|
||||||
|
23
tests/typ/meta/footnote-table.typ
Normal file
23
tests/typ/meta/footnote-table.typ
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Test footnotes in tables. When the table spans multiple pages, the footnotes
|
||||||
|
// will all be after the table, but it shouldn't create any empty pages.
|
||||||
|
---
|
||||||
|
|
||||||
|
#set page(height: 100pt)
|
||||||
|
|
||||||
|
= Tables
|
||||||
|
#table(
|
||||||
|
columns: 2,
|
||||||
|
[Hello footnote #footnote[This is a footnote.]],
|
||||||
|
[This is more text],
|
||||||
|
[This cell
|
||||||
|
#footnote[This footnote is not on the same page]
|
||||||
|
breaks over multiple pages.],
|
||||||
|
image("/tiger.jpg"),
|
||||||
|
)
|
||||||
|
|
||||||
|
#table(
|
||||||
|
columns: 3,
|
||||||
|
..range(1, 10)
|
||||||
|
.map(numbering.with("a"))
|
||||||
|
.map(v => upper(v) + footnote(v))
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user