diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs index c550ac28a..2f985f285 100644 --- a/crates/typst/src/layout/flow.rs +++ b/crates/typst/src/layout/flow.rs @@ -46,7 +46,23 @@ impl LayoutMultiple for Packed { bail!(self.span(), "cannot expand into infinite height"); } - let mut layouter = FlowLayouter::new(regions, styles); + // Check whether we have just a single multiple-layoutable element. In + // that case, we do not set `expand.y` to `false`, but rather keep it at + // its original value (since that element can take the full space). + // + // Consider the following code: `block(height: 5cm, pad(10pt, align(bottom, ..)))` + // Thanks to the code below, the expansion will be passed all the way + // through the block & pad and reach the innermost flow, so that things + // are properly bottom-aligned. + let mut alone = false; + if let [child] = self.children().as_slice() { + alone = child + .to_packed::() + .map_or(child, |styled| &styled.child) + .can::(); + } + + let mut layouter = FlowLayouter::new(regions, styles, alone); for mut child in self.children().iter() { let outer = styles; let mut styles = styles; @@ -70,8 +86,8 @@ impl LayoutMultiple for Packed { layouter.layout_par(engine, elem, styles)?; } else if let Some(layoutable) = child.with::() { layouter.layout_single(engine, layoutable, styles)?; - } else if child.can::() { - layouter.layout_multiple(engine, child, styles)?; + } else if let Some(layoutable) = child.with::() { + layouter.layout_multiple(engine, child, layoutable, styles)?; } else { bail!(child.span(), "unexpected flow child"); } @@ -179,11 +195,16 @@ impl FlowItem { impl<'a> FlowLayouter<'a> { /// Create a new flow layouter. - fn new(mut regions: Regions<'a>, styles: StyleChain<'a>) -> Self { + fn new(mut regions: Regions<'a>, styles: StyleChain<'a>, alone: bool) -> Self { let expand = regions.expand; - // Disable vertical expansion & root for children. - regions.expand.y = false; + // Disable vertical expansion when there are multiple or not directly + // layoutable children. + if !alone { + regions.expand.y = false; + } + + // Disable root. let root = std::mem::replace(&mut regions.root, false); Self { @@ -340,6 +361,7 @@ impl<'a> FlowLayouter<'a> { &mut self, engine: &mut Engine, child: &Content, + layoutable: &dyn LayoutMultiple, styles: StyleChain, ) -> SourceResult<()> { // Temporarily delegerate rootness to the columns. @@ -368,7 +390,7 @@ impl<'a> FlowLayouter<'a> { // Layout the block itself. let sticky = BlockElem::sticky_in(styles); - let fragment = child.layout(engine, styles, self.regions)?; + let fragment = layoutable.layout(engine, styles, self.regions)?; for (i, mut frame) in fragment.into_iter().enumerate() { // Find footnotes in the frame. diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs index 23a08c5f1..0fd8e6b10 100644 --- a/crates/typst/src/layout/mod.rs +++ b/crates/typst/src/layout/mod.rs @@ -77,7 +77,7 @@ use crate::eval::Tracer; use crate::foundations::{category, Category, Content, Scope, StyleChain}; use crate::introspection::{Introspector, Locator}; use crate::model::Document; -use crate::realize::{realize_block, realize_root, Arenas}; +use crate::realize::{realize_doc, realize_flow, Arenas}; use crate::World; /// Arranging elements on the page in different ways. @@ -207,7 +207,7 @@ impl LayoutRoot for Content { tracer, }; let arenas = Arenas::default(); - let (document, styles) = realize_root(&mut engine, &arenas, content, styles)?; + let (document, styles) = realize_doc(&mut engine, &arenas, content, styles)?; document.layout_root(&mut engine, styles) } @@ -258,14 +258,16 @@ impl LayoutMultiple for Content { ); } + // If we are in a `PageElem`, this might already be a realized flow. + if let Some(flow) = content.to_packed::() { + return flow.layout(&mut engine, styles, regions); + } + + // Layout the content by first turning it into a `FlowElem` and then + // layouting that. let arenas = Arenas::default(); - let (realized, styles) = - realize_block(&mut engine, &arenas, content, styles)?; - realized.with::().unwrap().layout( - &mut engine, - styles, - regions, - ) + let (flow, styles) = realize_flow(&mut engine, &arenas, content, styles)?; + flow.layout(&mut engine, styles, regions) } let fragment = cached( diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs index 98b5c1271..4679a61cb 100644 --- a/crates/typst/src/realize/mod.rs +++ b/crates/typst/src/realize/mod.rs @@ -12,9 +12,7 @@ mod process; pub use self::arenas::Arenas; pub use self::behaviour::{Behave, BehavedBuilder, Behaviour}; -pub use self::process::{process, processable}; - -use std::borrow::Cow; +pub use self::process::process; use std::mem; @@ -36,9 +34,10 @@ use crate::model::{ use crate::syntax::Span; use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem}; -/// Realize into an element that is capable of root-level layout. -#[typst_macros::time(name = "realize root")] -pub fn realize_root<'a>( +/// Realize into a `DocumentElem`, an element that is capable of root-level +/// layout. +#[typst_macros::time(name = "realize doc")] +pub fn realize_doc<'a>( engine: &mut Engine, arenas: &'a Arenas<'a>, content: &'a Content, @@ -47,30 +46,21 @@ pub fn realize_root<'a>( let mut builder = Builder::new(engine, arenas, true); builder.accept(content, styles)?; builder.interrupt_page(Some(styles), true)?; - let (doc, trunk) = builder.doc.unwrap().finish(); - Ok((doc, trunk)) + Ok(builder.doc.unwrap().finish()) } -/// Realize into an element that is capable of block-level layout. -#[typst_macros::time(name = "realize block")] -pub fn realize_block<'a>( +/// Realize into a `FlowElem`, an element that is capable of block-level layout. +#[typst_macros::time(name = "realize flow")] +pub fn realize_flow<'a>( engine: &mut Engine, arenas: &'a Arenas<'a>, content: &'a Content, styles: StyleChain<'a>, -) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> { - // These elements implement `Layout` but still require a flow for - // proper layout. - if content.can::() && !processable(engine, content, styles) { - return Ok((Cow::Borrowed(content), styles)); - } - +) -> SourceResult<(Packed, StyleChain<'a>)> { let mut builder = Builder::new(engine, arenas, false); builder.accept(content, styles)?; builder.interrupt_par()?; - - let (flow, trunk) = builder.flow.finish(); - Ok((Cow::Owned(flow.pack()), trunk)) + Ok(builder.flow.finish()) } /// Builds a document or a flow element from content. diff --git a/crates/typst/src/realize/process.rs b/crates/typst/src/realize/process.rs index 6ddb493d6..4212ecb4b 100644 --- a/crates/typst/src/realize/process.rs +++ b/crates/typst/src/realize/process.rs @@ -31,15 +31,6 @@ enum ShowStep<'a> { Builtin, } -/// Returns whether the `target` element needs processing. -pub fn processable<'a>( - engine: &mut Engine, - target: &'a Content, - styles: StyleChain<'a>, -) -> bool { - verdict(engine, target, styles).is_some() -} - /// Processes the given `target` element when encountering it during realization. pub fn process( engine: &mut Engine, diff --git a/tests/ref/grid-cell-override.png b/tests/ref/grid-cell-override.png index d6f37d632..a38efca57 100644 Binary files a/tests/ref/grid-cell-override.png and b/tests/ref/grid-cell-override.png differ diff --git a/tests/ref/grid-rowspan-split-2.png b/tests/ref/grid-rowspan-split-2.png index 43a5eed7c..fad27f499 100644 Binary files a/tests/ref/grid-rowspan-split-2.png and b/tests/ref/grid-rowspan-split-2.png differ diff --git a/tests/ref/grid-rtl-rowspan.png b/tests/ref/grid-rtl-rowspan.png index 2465164b1..7b51f97b1 100644 Binary files a/tests/ref/grid-rtl-rowspan.png and b/tests/ref/grid-rtl-rowspan.png differ diff --git a/tests/ref/place-float-columns.png b/tests/ref/place-float-columns.png index 97065b68f..5bc50c46e 100644 Binary files a/tests/ref/place-float-columns.png and b/tests/ref/place-float-columns.png differ diff --git a/tests/ref/table-cell-override.png b/tests/ref/table-cell-override.png index d6f37d632..a38efca57 100644 Binary files a/tests/ref/table-cell-override.png and b/tests/ref/table-cell-override.png differ