Skip to content

Commit 92d6c2f

Browse files
committed
Fix tag order with place and fr block bugs (#5203)
1 parent bc31ac2 commit 92d6c2f

File tree

5 files changed

+98
-58
lines changed

5 files changed

+98
-58
lines changed

crates/typst/src/layout/flow/compose.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
221221

222222
// Process pending floats.
223223
for placed in std::mem::take(&mut self.work.floats) {
224-
self.float(placed, &regions, true)?;
224+
self.float(placed, &regions, false)?;
225225
}
226226

227227
distribute(self, regions)
@@ -280,7 +280,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
280280
};
281281

282282
// We only require clearance if there is other content.
283-
let clearance = if clearance { Abs::zero() } else { placed.clearance };
283+
let clearance = if clearance { placed.clearance } else { Abs::zero() };
284284
let need = frame.height() + clearance;
285285

286286
// If the float doesn't fit, queue it for the next region.
@@ -338,7 +338,13 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
338338
}
339339

340340
// Search for footnotes.
341-
let notes = find_in_frame::<FootnoteElem>(frame);
341+
let mut notes = vec![];
342+
for tag in &self.work.tags {
343+
let Tag::Start(elem) = tag else { continue };
344+
let Some(note) = elem.to_packed::<FootnoteElem>() else { continue };
345+
notes.push((Abs::zero(), note.clone()));
346+
}
347+
find_in_frame_impl::<FootnoteElem>(&mut notes, frame, Abs::zero());
342348
if notes.is_empty() {
343349
return Ok(());
344350
}

crates/typst/src/layout/flow/distribute.rs

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ struct DistributionSnapshot<'a, 'b> {
5454

5555
/// A laid out item in a distribution.
5656
enum Item<'a, 'b> {
57+
/// An introspection tag.
58+
Tag(&'a Tag),
5759
/// Absolute spacing and its weakness level.
5860
Abs(Abs, u8),
5961
/// Fractional spacing or a fractional block.
@@ -69,6 +71,7 @@ impl Item<'_, '_> {
6971
/// consists solely of such items.
7072
fn migratable(&self) -> bool {
7173
match self {
74+
Self::Tag(_) => true,
7275
Self::Frame(frame, _) => {
7376
frame.size().is_zero()
7477
&& frame.items().all(|(_, item)| {
@@ -126,6 +129,15 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
126129
self.composer.work.tags.push(tag);
127130
}
128131

132+
/// Generate items for pending tags.
133+
fn flush_tags(&mut self) {
134+
if !self.composer.work.tags.is_empty() {
135+
let tags = &mut self.composer.work.tags;
136+
self.items.extend(tags.iter().copied().map(Item::Tag));
137+
tags.clear();
138+
}
139+
}
140+
129141
/// Processes relative spacing.
130142
fn rel(&mut self, amount: Rel<Abs>, weakness: u8) {
131143
let amount = amount.relative_to(self.regions.base().y);
@@ -157,7 +169,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
157169
}
158170
return false;
159171
}
160-
Item::Abs(..) | Item::Placed(..) => {}
172+
Item::Tag(_) | Item::Abs(..) | Item::Placed(..) => {}
161173
Item::Fr(.., None) => return false,
162174
Item::Frame(..) | Item::Fr(.., Some(_)) => return true,
163175
}
@@ -174,7 +186,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
174186
self.items.remove(i);
175187
break;
176188
}
177-
Item::Abs(..) | Item::Placed(..) => {}
189+
Item::Tag(_) | Item::Abs(..) | Item::Placed(..) => {}
178190
Item::Frame(..) | Item::Fr(..) => break,
179191
}
180192
}
@@ -185,7 +197,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
185197
for item in self.items.iter().rev() {
186198
match *item {
187199
Item::Abs(amount, 1..) => return amount,
188-
Item::Abs(..) | Item::Placed(..) => {}
200+
Item::Tag(_) | Item::Abs(..) | Item::Placed(..) => {}
189201
Item::Frame(..) | Item::Fr(..) => break,
190202
}
191203
}
@@ -219,18 +231,20 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
219231

220232
/// Processes an unbreakable block.
221233
fn single(&mut self, single: &'b SingleChild<'a>) -> FlowResult<()> {
222-
// Handle fractionally sized blocks.
223-
if let Some(fr) = single.fr {
224-
self.items.push(Item::Fr(fr, Some(single)));
225-
return Ok(());
226-
}
227-
228234
// Lay out the block.
229235
let frame = single.layout(
230236
self.composer.engine,
231237
Region::new(self.regions.base(), self.regions.expand),
232238
)?;
233239

240+
// Handle fractionally sized blocks.
241+
if let Some(fr) = single.fr {
242+
self.composer.footnotes(&self.regions, &frame, Abs::zero(), false)?;
243+
self.flush_tags();
244+
self.items.push(Item::Fr(fr, Some(single)));
245+
return Ok(());
246+
}
247+
234248
// If the block doesn't fit and a followup region may improve things,
235249
// finish the region.
236250
if !self.regions.size.y.fits(frame.height()) && self.regions.may_progress() {
@@ -289,7 +303,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
289303
/// Processes an in-flow frame, generated from a line or block.
290304
fn frame(
291305
&mut self,
292-
mut frame: Frame,
306+
frame: Frame,
293307
align: Axes<FixedAlignment>,
294308
sticky: bool,
295309
breakable: bool,
@@ -307,26 +321,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
307321
self.sticky = None;
308322
}
309323

310-
if !frame.is_empty() {
311-
// Drain tags.
312-
let tags = &mut self.composer.work.tags;
313-
if !tags.is_empty() {
314-
frame.prepend_multiple(
315-
tags.iter().map(|&tag| (Point::zero(), FrameItem::Tag(tag.clone()))),
316-
);
317-
}
318-
319-
// Handle footnotes.
320-
self.composer
321-
.footnotes(&self.regions, &frame, frame.height(), breakable)?;
322-
323-
// Clear the drained tags _after_ the footnotes are handled because
324-
// a [`Stop::Finish`] could otherwise lose them.
325-
self.composer.work.tags.clear();
326-
}
324+
// Handle footnotes.
325+
self.composer
326+
.footnotes(&self.regions, &frame, frame.height(), breakable)?;
327327

328328
// Push an item for the frame.
329329
self.regions.size.y -= frame.height();
330+
self.flush_tags();
330331
self.items.push(Item::Frame(frame, align));
331332
Ok(())
332333
}
@@ -341,11 +342,16 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
341342
// ends up at a break due to the float.
342343
let weak_spacing = self.weak_spacing();
343344
self.regions.size.y += weak_spacing;
344-
self.composer.float(placed, &self.regions, self.items.is_empty())?;
345+
self.composer.float(
346+
placed,
347+
&self.regions,
348+
self.items.iter().any(|item| matches!(item, Item::Frame(..))),
349+
)?;
345350
self.regions.size.y -= weak_spacing;
346351
} else {
347352
let frame = placed.layout(self.composer.engine, self.regions.base())?;
348353
self.composer.footnotes(&self.regions, &frame, Abs::zero(), true)?;
354+
self.flush_tags();
349355
self.items.push(Item::Placed(frame, placed));
350356
}
351357
Ok(())
@@ -382,46 +388,53 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
382388
init: DistributionSnapshot<'a, 'b>,
383389
forced: bool,
384390
) -> FlowResult<Frame> {
385-
if !forced {
386-
if !self.items.is_empty() && self.items.iter().all(Item::migratable) {
387-
// Restore the initial state of all items are migratable.
388-
self.restore(init);
389-
} else {
390-
// If we ended on a sticky block, but are not yet at the end of
391-
// the flow, restore the saved checkpoint to move the sticky
392-
// suffix to the next region.
393-
if let Some(snapshot) = self.sticky.take() {
394-
self.restore(snapshot)
395-
}
391+
if forced {
392+
// If this is the very end of the flow, flush pending tags.
393+
self.flush_tags();
394+
} else if !self.items.is_empty() && self.items.iter().all(Item::migratable) {
395+
// Restore the initial state of all items are migratable.
396+
self.restore(init);
397+
} else {
398+
// If we ended on a sticky block, but are not yet at the end of
399+
// the flow, restore the saved checkpoint to move the sticky
400+
// suffix to the next region.
401+
if let Some(snapshot) = self.sticky.take() {
402+
self.restore(snapshot)
396403
}
397404
}
398405

399406
self.trim_spacing();
400407

401408
let mut frs = Fr::zero();
402409
let mut used = Size::zero();
410+
let mut has_fr_child = false;
403411

404412
// Determine the amount of used space and the sum of fractionals.
405413
for item in &self.items {
406414
match item {
407415
Item::Abs(v, _) => used.y += *v,
408-
Item::Fr(v, _) => frs += *v,
416+
Item::Fr(v, child) => {
417+
frs += *v;
418+
has_fr_child |= child.is_some();
419+
}
409420
Item::Frame(frame, _) => {
410421
used.y += frame.height();
411422
used.x.set_max(frame.width());
412423
}
413-
Item::Placed(..) => {}
424+
Item::Tag(_) | Item::Placed(..) => {}
414425
}
415426
}
416427

417428
// When we have fractional spacing, occupy the remaining space with it.
418429
let mut fr_space = Abs::zero();
419-
let mut fr_frames = vec![];
420430
if frs.get() > 0.0 && region.size.y.is_finite() {
421431
fr_space = region.size.y - used.y;
422432
used.y = region.size.y;
433+
}
423434

424-
// Lay out fractionally sized blocks.
435+
// Lay out fractionally sized blocks.
436+
let mut fr_frames = vec![];
437+
if has_fr_child {
425438
for item in &self.items {
426439
let Item::Fr(v, Some(single)) = item else { continue };
427440
let length = v.share(frs, fr_space);
@@ -439,6 +452,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
439452

440453
// Determine the region's size.
441454
let size = region.expand.select(region.size, used.min(region.size));
455+
let free = size.y - used.y;
442456

443457
let mut output = Frame::soft(size);
444458
let mut ruler = FixedAlignment::Start;
@@ -448,6 +462,11 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
448462
// Position all items.
449463
for item in self.items {
450464
match item {
465+
Item::Tag(tag) => {
466+
let y = offset + ruler.position(free);
467+
let pos = Point::with_y(y);
468+
output.push(pos, FrameItem::Tag(tag.clone()));
469+
}
451470
Item::Abs(v, _) => {
452471
offset += v;
453472
}
@@ -465,7 +484,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
465484
ruler = ruler.max(align.y);
466485

467486
let x = align.x.position(size.x - frame.width());
468-
let y = offset + ruler.position(size.y - used.y);
487+
let y = offset + ruler.position(free);
469488
let pos = Point::new(x, y);
470489
offset += frame.height();
471490

@@ -475,7 +494,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
475494
let x = placed.align_x.position(size.x - frame.width());
476495
let y = match placed.align_y.unwrap_or_default() {
477496
Some(align) => align.position(size.y - frame.height()),
478-
_ => offset + ruler.position(size.y - used.y),
497+
_ => offset + ruler.position(free),
479498
};
480499

481500
let pos = Point::new(x, y)
@@ -486,16 +505,6 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
486505
}
487506
}
488507

489-
// If this is the very end of the flow, drain trailing tags.
490-
if forced && !self.composer.work.tags.is_empty() {
491-
let tags = &mut self.composer.work.tags;
492-
let pos = Point::with_y(offset);
493-
output.push_multiple(
494-
tags.iter().map(|&tag| (pos, FrameItem::Tag(tag.clone()))),
495-
);
496-
tags.clear();
497-
}
498-
499508
Ok(output)
500509
}
501510

tests/ref/footnote-block-fr.png

833 Bytes
Loading

tests/suite/introspection/query.typ

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,20 @@
233233
#quote[NOP] <nop>
234234

235235
#context query(<nop>).first()
236+
237+
--- issue-5117-query-order-place ---
238+
#let t(expected) = context {
239+
let elems = query(selector(metadata).after(here()))
240+
let val = elems.first().value
241+
test(val, expected)
242+
}
243+
244+
#{
245+
t("a")
246+
place(metadata("a"))
247+
}
248+
249+
#{
250+
t("b")
251+
block(height: 1fr, metadata("b"))
252+
}

tests/suite/layout/flow/footnote.typ

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ Beautiful footnotes. #footnote[Wonderful, aren't they?]
114114
A
115115
#block(footnote[hello])
116116

117+
--- footnote-block-fr ---
118+
#set page(height: 110pt)
119+
A
120+
#block(width: 100%, height: 1fr, fill: aqua)[
121+
B #footnote[I] #footnote[II]
122+
]
123+
C
124+
117125
--- footnote-float-priority ---
118126
#set page(height: 100pt)
119127

0 commit comments

Comments
 (0)