diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs index 2f985f285..84a395cc3 100644 --- a/crates/typst/src/layout/flow.rs +++ b/crates/typst/src/layout/flow.rs @@ -13,9 +13,9 @@ use crate::foundations::{ }; use crate::introspection::TagElem; use crate::layout::{ - Abs, AlignElem, Axes, BlockElem, ColbreakElem, ColumnsElem, FixedAlignment, Fr, - Fragment, Frame, FrameItem, LayoutMultiple, LayoutSingle, PlaceElem, Point, Regions, - Rel, Size, Spacing, VElem, + Abs, AlignElem, Axes, BlockElem, ColbreakElem, ColumnsElem, FixedAlignment, + FlushElem, Fr, Fragment, Frame, FrameItem, LayoutMultiple, LayoutSingle, PlaceElem, + Point, Regions, Rel, Size, Spacing, VElem, }; use crate::model::{FootnoteElem, FootnoteEntry, ParElem}; use crate::utils::Numeric; @@ -73,6 +73,8 @@ impl LayoutMultiple for Packed { if let Some(elem) = child.to_packed::() { layouter.layout_tag(elem); + } else if child.is::() { + layouter.flush(engine)?; } else if let Some(elem) = child.to_packed::() { layouter.layout_spacing(engine, elem, styles)?; } else if let Some(placed) = child.to_packed::() { @@ -683,6 +685,18 @@ impl<'a> FlowLayouter<'a> { Ok(()) } + /// Lays out all floating elements before continuing with other content. + fn flush(&mut self, engine: &mut Engine) -> SourceResult<()> { + for item in std::mem::take(&mut self.pending_floats) { + self.layout_item(engine, item)?; + } + while !self.pending_floats.is_empty() { + self.finish_region(engine, false)?; + } + + Ok(()) + } + /// Finish layouting and return the resulting fragment. fn finish(mut self, engine: &mut Engine) -> SourceResult { if self.expand.y { diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs index 0fd8e6b10..444d51621 100644 --- a/crates/typst/src/layout/mod.rs +++ b/crates/typst/src/layout/mod.rs @@ -107,6 +107,7 @@ pub fn define(global: &mut Scope) { global.define_elem::(); global.define_elem::(); global.define_elem::(); + global.define_elem::(); global.define_elem::(); global.define_elem::(); global.define_elem::(); diff --git a/crates/typst/src/layout/place.rs b/crates/typst/src/layout/place.rs index 7d41b0376..f81af2b80 100644 --- a/crates/typst/src/layout/place.rs +++ b/crates/typst/src/layout/place.rs @@ -1,6 +1,6 @@ use crate::diag::{bail, At, Hint, SourceResult}; use crate::engine::Engine; -use crate::foundations::{elem, Content, Packed, Smart, StyleChain}; +use crate::foundations::{elem, scope, Content, Packed, Smart, StyleChain, Unlabellable}; use crate::layout::{ Alignment, Axes, Em, Fragment, LayoutMultiple, Length, Regions, Rel, Size, VAlignment, }; @@ -26,7 +26,7 @@ use crate::realize::{Behave, Behaviour}; /// ), /// ) /// ``` -#[elem(Behave)] +#[elem(scope, Behave)] pub struct PlaceElem { /// Relative to which position in the parent container to place the content. /// @@ -43,7 +43,9 @@ pub struct PlaceElem { /// Whether the placed element has floating layout. /// /// Floating elements are positioned at the top or bottom of the page, - /// displacing in-flow content. + /// displacing in-flow content. They are always placed in the in-flow + /// order relative to each other, as well as before any content following + /// a later [`flush`] element. /// /// ```example /// #set page(height: 150pt) @@ -95,6 +97,12 @@ pub struct PlaceElem { pub body: Content, } +#[scope] +impl PlaceElem { + #[elem] + type FlushElem; +} + impl Packed { #[typst_macros::time(name = "place", span = self.span())] pub fn layout( @@ -136,3 +144,40 @@ impl Behave for Packed { Behaviour::Ignorant } } + +/// Asks the layout algorithm to place pending floating elements before +/// continuing with the content. +/// +/// This is useful for preventing floating figures from spilling +/// into the next section. +/// +/// ```example +/// #set page(height: 165pt, width: 150pt) +/// +/// Some introductory text: #lorem(15) +/// +/// #figure( +/// rect( +/// width: 100%, +/// height: 64pt, +/// [I float with a caption!], +/// ), +/// placement: auto, +/// caption: [A self-describing figure], +/// ) +/// +/// #place.flush() +/// +/// Some conclusive text that must occur +/// after the figure. +/// ``` +#[elem(Behave, Unlabellable)] +pub struct FlushElem {} + +impl Behave for Packed { + fn behaviour(&self) -> Behaviour { + Behaviour::Invisible + } +} + +impl Unlabellable for Packed {} diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs index 4679a61cb..600df1bb7 100644 --- a/crates/typst/src/realize/mod.rs +++ b/crates/typst/src/realize/mod.rs @@ -23,8 +23,8 @@ use crate::foundations::{ }; use crate::introspection::TagElem; use crate::layout::{ - AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, HElem, LayoutMultiple, - LayoutSingle, PageElem, PagebreakElem, Parity, PlaceElem, VElem, + AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, FlushElem, HElem, + LayoutMultiple, LayoutSingle, PageElem, PagebreakElem, Parity, PlaceElem, VElem, }; use crate::math::{EquationElem, LayoutMath}; use crate::model::{ @@ -381,6 +381,7 @@ impl<'a> FlowBuilder<'a> { || content.is::() || content.is::() || content.is::() + || content.is::() { self.0.push(content, styles); return true; diff --git a/tests/ref/place-figure-flush.png b/tests/ref/place-figure-flush.png new file mode 100644 index 000000000..2e6e03105 Binary files /dev/null and b/tests/ref/place-figure-flush.png differ diff --git a/tests/ref/place-float-flush.png b/tests/ref/place-float-flush.png new file mode 100644 index 000000000..67c01c2cd Binary files /dev/null and b/tests/ref/place-float-flush.png differ diff --git a/tests/suite/layout/place.typ b/tests/suite/layout/place.typ index b8765e937..0922800db 100644 --- a/tests/suite/layout/place.typ +++ b/tests/suite/layout/place.typ @@ -121,6 +121,33 @@ Second image("/assets/images/diagram.svg", width: 80%), ) +--- place-float-flush --- +#set page(height: 150pt, width: 150pt) + +#let floater = place(auto, float: true, rect(width: 100%, height: 90pt, text(size: 24pt)[I float!])) + +Some introductory text. + +#floater #floater #floater #floater + +Some additional text. + +#place.flush() + +Some conclusive text. // Should appear after all the floating figures + +--- place-figure-flush --- + +#set page(height: 165pt, width: 150pt) + +Some introductory text: #lorem(15) + +#figure(placement: auto, caption: [A self-describing figure], rect(width: 100%, height: 64pt, [I float with a caption!])) + +#place.flush() + +Some conclusive text that must occur after the figure. + --- place-bottom-in-box --- #box( fill: aqua,