Fix tag order with place and fr block bugs (#5203)

This commit is contained in:
Laurenz 2024-10-14 16:18:25 +02:00 committed by GitHub
parent 1dc8b99ec9
commit 6a8e29b2e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 98 additions and 58 deletions

View File

@ -221,7 +221,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, &regions, true)?; self.float(placed, &regions, false)?;
} }
distribute(self, regions) distribute(self, regions)
@ -280,7 +280,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
}; };
// We only require clearance if there is other content. // We only require clearance if there is other content.
let clearance = if clearance { Abs::zero() } else { placed.clearance }; let clearance = if clearance { placed.clearance } else { Abs::zero() };
let need = frame.height() + clearance; let need = frame.height() + clearance;
// If the float doesn't fit, queue it for the next region. // If the float doesn't fit, queue it for the next region.
@ -338,7 +338,13 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
} }
// Search for footnotes. // Search for footnotes.
let notes = find_in_frame::<FootnoteElem>(frame); let mut notes = vec![];
for tag in &self.work.tags {
let Tag::Start(elem) = tag else { continue };
let Some(note) = elem.to_packed::<FootnoteElem>() else { continue };
notes.push((Abs::zero(), note.clone()));
}
find_in_frame_impl::<FootnoteElem>(&mut notes, frame, Abs::zero());
if notes.is_empty() { if notes.is_empty() {
return Ok(()); return Ok(());
} }

View File

@ -54,6 +54,8 @@ struct DistributionSnapshot<'a, 'b> {
/// A laid out item in a distribution. /// A laid out item in a distribution.
enum Item<'a, 'b> { enum Item<'a, 'b> {
/// An introspection tag.
Tag(&'a Tag),
/// Absolute spacing and its weakness level. /// Absolute spacing and its weakness level.
Abs(Abs, u8), Abs(Abs, u8),
/// Fractional spacing or a fractional block. /// Fractional spacing or a fractional block.
@ -69,6 +71,7 @@ impl Item<'_, '_> {
/// consists solely of such items. /// consists solely of such items.
fn migratable(&self) -> bool { fn migratable(&self) -> bool {
match self { match self {
Self::Tag(_) => true,
Self::Frame(frame, _) => { Self::Frame(frame, _) => {
frame.size().is_zero() frame.size().is_zero()
&& frame.items().all(|(_, item)| { && frame.items().all(|(_, item)| {
@ -126,6 +129,15 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
self.composer.work.tags.push(tag); self.composer.work.tags.push(tag);
} }
/// Generate items for pending tags.
fn flush_tags(&mut self) {
if !self.composer.work.tags.is_empty() {
let tags = &mut self.composer.work.tags;
self.items.extend(tags.iter().copied().map(Item::Tag));
tags.clear();
}
}
/// Processes relative spacing. /// Processes relative spacing.
fn rel(&mut self, amount: Rel<Abs>, weakness: u8) { fn rel(&mut self, amount: Rel<Abs>, weakness: u8) {
let amount = amount.relative_to(self.regions.base().y); let amount = amount.relative_to(self.regions.base().y);
@ -157,7 +169,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
} }
return false; return false;
} }
Item::Abs(..) | Item::Placed(..) => {} Item::Tag(_) | Item::Abs(..) | Item::Placed(..) => {}
Item::Fr(.., None) => return false, Item::Fr(.., None) => return false,
Item::Frame(..) | Item::Fr(.., Some(_)) => return true, Item::Frame(..) | Item::Fr(.., Some(_)) => return true,
} }
@ -174,7 +186,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
self.items.remove(i); self.items.remove(i);
break; break;
} }
Item::Abs(..) | Item::Placed(..) => {} Item::Tag(_) | Item::Abs(..) | Item::Placed(..) => {}
Item::Frame(..) | Item::Fr(..) => break, Item::Frame(..) | Item::Fr(..) => break,
} }
} }
@ -185,7 +197,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
for item in self.items.iter().rev() { for item in self.items.iter().rev() {
match *item { match *item {
Item::Abs(amount, 1..) => return amount, Item::Abs(amount, 1..) => return amount,
Item::Abs(..) | Item::Placed(..) => {} Item::Tag(_) | Item::Abs(..) | Item::Placed(..) => {}
Item::Frame(..) | Item::Fr(..) => break, Item::Frame(..) | Item::Fr(..) => break,
} }
} }
@ -219,18 +231,20 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
/// Processes an unbreakable block. /// Processes an unbreakable block.
fn single(&mut self, single: &'b SingleChild<'a>) -> FlowResult<()> { fn single(&mut self, single: &'b SingleChild<'a>) -> FlowResult<()> {
// Handle fractionally sized blocks.
if let Some(fr) = single.fr {
self.items.push(Item::Fr(fr, Some(single)));
return Ok(());
}
// Lay out the block. // Lay out the block.
let frame = single.layout( let frame = single.layout(
self.composer.engine, self.composer.engine,
Region::new(self.regions.base(), self.regions.expand), Region::new(self.regions.base(), self.regions.expand),
)?; )?;
// Handle fractionally sized blocks.
if let Some(fr) = single.fr {
self.composer.footnotes(&self.regions, &frame, Abs::zero(), false)?;
self.flush_tags();
self.items.push(Item::Fr(fr, Some(single)));
return Ok(());
}
// If the block doesn't fit and a followup region may improve things, // If the block doesn't fit and a followup region may improve things,
// finish the region. // finish the region.
if !self.regions.size.y.fits(frame.height()) && self.regions.may_progress() { if !self.regions.size.y.fits(frame.height()) && self.regions.may_progress() {
@ -289,7 +303,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
/// Processes an in-flow frame, generated from a line or block. /// Processes an in-flow frame, generated from a line or block.
fn frame( fn frame(
&mut self, &mut self,
mut frame: Frame, frame: Frame,
align: Axes<FixedAlignment>, align: Axes<FixedAlignment>,
sticky: bool, sticky: bool,
breakable: bool, breakable: bool,
@ -307,26 +321,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
self.sticky = None; self.sticky = None;
} }
if !frame.is_empty() {
// Drain tags.
let tags = &mut self.composer.work.tags;
if !tags.is_empty() {
frame.prepend_multiple(
tags.iter().map(|&tag| (Point::zero(), FrameItem::Tag(tag.clone()))),
);
}
// Handle footnotes. // Handle footnotes.
self.composer self.composer
.footnotes(&self.regions, &frame, frame.height(), breakable)?; .footnotes(&self.regions, &frame, frame.height(), breakable)?;
// Clear the drained tags _after_ the footnotes are handled because
// a [`Stop::Finish`] could otherwise lose them.
self.composer.work.tags.clear();
}
// Push an item for the frame. // Push an item for the frame.
self.regions.size.y -= frame.height(); self.regions.size.y -= frame.height();
self.flush_tags();
self.items.push(Item::Frame(frame, align)); self.items.push(Item::Frame(frame, align));
Ok(()) Ok(())
} }
@ -341,11 +342,16 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
// ends up at a break due to the float. // ends up at a break due to the float.
let weak_spacing = self.weak_spacing(); let weak_spacing = self.weak_spacing();
self.regions.size.y += weak_spacing; self.regions.size.y += weak_spacing;
self.composer.float(placed, &self.regions, self.items.is_empty())?; self.composer.float(
placed,
&self.regions,
self.items.iter().any(|item| matches!(item, Item::Frame(..))),
)?;
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)?;
self.flush_tags();
self.items.push(Item::Placed(frame, placed)); self.items.push(Item::Placed(frame, placed));
} }
Ok(()) Ok(())
@ -382,8 +388,10 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
init: DistributionSnapshot<'a, 'b>, init: DistributionSnapshot<'a, 'b>,
forced: bool, forced: bool,
) -> FlowResult<Frame> { ) -> FlowResult<Frame> {
if !forced { if forced {
if !self.items.is_empty() && self.items.iter().all(Item::migratable) { // If this is the very end of the flow, flush pending tags.
self.flush_tags();
} else if !self.items.is_empty() && self.items.iter().all(Item::migratable) {
// Restore the initial state of all items are migratable. // Restore the initial state of all items are migratable.
self.restore(init); self.restore(init);
} else { } else {
@ -394,34 +402,39 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
self.restore(snapshot) self.restore(snapshot)
} }
} }
}
self.trim_spacing(); self.trim_spacing();
let mut frs = Fr::zero(); let mut frs = Fr::zero();
let mut used = Size::zero(); let mut used = Size::zero();
let mut has_fr_child = false;
// Determine the amount of used space and the sum of fractionals. // Determine the amount of used space and the sum of fractionals.
for item in &self.items { for item in &self.items {
match item { match item {
Item::Abs(v, _) => used.y += *v, Item::Abs(v, _) => used.y += *v,
Item::Fr(v, _) => frs += *v, Item::Fr(v, child) => {
frs += *v;
has_fr_child |= child.is_some();
}
Item::Frame(frame, _) => { Item::Frame(frame, _) => {
used.y += frame.height(); used.y += frame.height();
used.x.set_max(frame.width()); used.x.set_max(frame.width());
} }
Item::Placed(..) => {} Item::Tag(_) | Item::Placed(..) => {}
} }
} }
// When we have fractional spacing, occupy the remaining space with it. // When we have fractional spacing, occupy the remaining space with it.
let mut fr_space = Abs::zero(); let mut fr_space = Abs::zero();
let mut fr_frames = vec![];
if frs.get() > 0.0 && region.size.y.is_finite() { if frs.get() > 0.0 && region.size.y.is_finite() {
fr_space = region.size.y - used.y; fr_space = region.size.y - used.y;
used.y = region.size.y; used.y = region.size.y;
}
// Lay out fractionally sized blocks. // Lay out fractionally sized blocks.
let mut fr_frames = vec![];
if has_fr_child {
for item in &self.items { for item in &self.items {
let Item::Fr(v, Some(single)) = item else { continue }; let Item::Fr(v, Some(single)) = item else { continue };
let length = v.share(frs, fr_space); let length = v.share(frs, fr_space);
@ -439,6 +452,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
// Determine the region's size. // Determine the region's size.
let size = region.expand.select(region.size, used.min(region.size)); let size = region.expand.select(region.size, used.min(region.size));
let free = size.y - used.y;
let mut output = Frame::soft(size); let mut output = Frame::soft(size);
let mut ruler = FixedAlignment::Start; let mut ruler = FixedAlignment::Start;
@ -448,6 +462,11 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
// Position all items. // Position all items.
for item in self.items { for item in self.items {
match item { match item {
Item::Tag(tag) => {
let y = offset + ruler.position(free);
let pos = Point::with_y(y);
output.push(pos, FrameItem::Tag(tag.clone()));
}
Item::Abs(v, _) => { Item::Abs(v, _) => {
offset += v; offset += v;
} }
@ -465,7 +484,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
ruler = ruler.max(align.y); ruler = ruler.max(align.y);
let x = align.x.position(size.x - frame.width()); let x = align.x.position(size.x - frame.width());
let y = offset + ruler.position(size.y - used.y); let y = offset + ruler.position(free);
let pos = Point::new(x, y); let pos = Point::new(x, y);
offset += frame.height(); offset += frame.height();
@ -475,7 +494,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
let x = placed.align_x.position(size.x - frame.width()); let x = placed.align_x.position(size.x - frame.width());
let y = match placed.align_y.unwrap_or_default() { let y = match placed.align_y.unwrap_or_default() {
Some(align) => align.position(size.y - frame.height()), Some(align) => align.position(size.y - frame.height()),
_ => offset + ruler.position(size.y - used.y), _ => offset + ruler.position(free),
}; };
let pos = Point::new(x, y) let pos = Point::new(x, y)
@ -486,16 +505,6 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
} }
} }
// If this is the very end of the flow, drain trailing tags.
if forced && !self.composer.work.tags.is_empty() {
let tags = &mut self.composer.work.tags;
let pos = Point::with_y(offset);
output.push_multiple(
tags.iter().map(|&tag| (pos, FrameItem::Tag(tag.clone()))),
);
tags.clear();
}
Ok(output) Ok(output)
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

View File

@ -233,3 +233,20 @@
#quote[NOP] <nop> #quote[NOP] <nop>
#context query(<nop>).first() #context query(<nop>).first()
--- issue-5117-query-order-place ---
#let t(expected) = context {
let elems = query(selector(metadata).after(here()))
let val = elems.first().value
test(val, expected)
}
#{
t("a")
place(metadata("a"))
}
#{
t("b")
block(height: 1fr, metadata("b"))
}

View File

@ -114,6 +114,14 @@ Beautiful footnotes. #footnote[Wonderful, aren't they?]
A A
#block(footnote[hello]) #block(footnote[hello])
--- footnote-block-fr ---
#set page(height: 110pt)
A
#block(width: 100%, height: 1fr, fill: aqua)[
B #footnote[I] #footnote[II]
]
C
--- footnote-float-priority --- --- footnote-float-priority ---
#set page(height: 100pt) #set page(height: 100pt)