diff --git a/crates/typst/src/introspection/mod.rs b/crates/typst/src/introspection/mod.rs index e9ea02919..25f67400e 100644 --- a/crates/typst/src/introspection/mod.rs +++ b/crates/typst/src/introspection/mod.rs @@ -58,6 +58,15 @@ pub fn define(global: &mut Scope) { pub struct MetaElem { /// Metadata that should be attached to all elements affected by this style /// property. + /// + /// This must be accessed and applied to all frames produced by elements + /// that manually handle styles (because their children can have varying + /// styles). This currently includes flow, par, and equation. + /// + /// Other elements don't manually need to handle it because their parents + /// that result from realization will take care of it and the metadata can + /// only apply to them as a whole, not part of it (because they don't manage + /// styles). #[fold] pub data: SmallVec<[Meta; 1]>, } diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs index 3905a37ae..a8429e066 100644 --- a/crates/typst/src/layout/container.rs +++ b/crates/typst/src/layout/container.rs @@ -172,7 +172,6 @@ impl Layout for Packed { } // Apply metadata. - frame.meta(styles, false); frame.set_kind(FrameKind::Hard); Ok(Fragment::frame(frame)) @@ -454,7 +453,6 @@ impl Layout for Packed { // Apply metadata. for frame in &mut frames { frame.set_kind(FrameKind::Hard); - frame.meta(styles, false); } Ok(Fragment::frames(frames)) diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs index d7b9880fe..b4798cd89 100644 --- a/crates/typst/src/layout/flow.rs +++ b/crates/typst/src/layout/flow.rs @@ -71,14 +71,7 @@ impl Layout for Packed { let layoutable = child.with::().unwrap(); layouter.layout_single(engine, layoutable, styles)?; } else if child.is::() { - let mut frame = Frame::soft(Size::zero()); - frame.meta(styles, true); - layouter.items.push(FlowItem::Frame { - frame, - align: Axes::splat(FixedAlignment::Start), - sticky: true, - movable: false, - }); + layouter.layout_meta(styles); } else if let Some(placed) = child.to_packed::() { layouter.layout_placed(engine, placed, styles)?; } else if child.can::() { @@ -297,7 +290,8 @@ impl<'a> FlowLayouter<'a> { let align = AlignElem::alignment_in(styles).resolve(styles); let sticky = BlockElem::sticky_in(styles); let pod = Regions::one(self.regions.base(), Axes::splat(false)); - let frame = content.layout(engine, styles, pod)?.into_frame(); + let mut frame = content.layout(engine, styles, pod)?.into_frame(); + frame.meta(styles, false); self.layout_item( engine, FlowItem::Frame { frame, align, sticky, movable: true }, @@ -306,6 +300,18 @@ impl<'a> FlowLayouter<'a> { Ok(()) } + /// Place explicit metadata into the flow. + fn layout_meta(&mut self, styles: StyleChain) { + let mut frame = Frame::soft(Size::zero()); + frame.meta(styles, true); + self.items.push(FlowItem::Frame { + frame, + align: Axes::splat(FixedAlignment::Start), + sticky: true, + movable: false, + }); + } + /// Layout a placed element. fn layout_placed( &mut self, @@ -321,7 +327,8 @@ impl<'a> FlowLayouter<'a> { align.x().unwrap_or_default().resolve(styles) }); let y_align = alignment.map(|align| align.y().map(VAlignment::fix)); - let frame = placed.layout(engine, styles, self.regions)?.into_frame(); + let mut frame = placed.layout(engine, styles, self.regions)?.into_frame(); + frame.meta(styles, false); let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance }; self.layout_item(engine, item) } @@ -361,7 +368,7 @@ impl<'a> FlowLayouter<'a> { let sticky = BlockElem::sticky_in(styles); let fragment = block.layout(engine, styles, self.regions)?; - for (i, frame) in fragment.into_iter().enumerate() { + for (i, mut frame) in fragment.into_iter().enumerate() { // Find footnotes in the frame. if self.root { find_footnotes(&mut notes, &frame); @@ -371,8 +378,11 @@ impl<'a> FlowLayouter<'a> { self.finish_region(engine, false)?; } - let item = FlowItem::Frame { frame, align, sticky, movable: false }; - self.layout_item(engine, item)?; + frame.meta(styles, false); + self.layout_item( + engine, + FlowItem::Frame { frame, align, sticky, movable: false }, + )?; } self.try_handle_footnotes(engine, notes)?; diff --git a/crates/typst/src/layout/frame.rs b/crates/typst/src/layout/frame.rs index 43cab1f4c..3c6d88e7b 100644 --- a/crates/typst/src/layout/frame.rs +++ b/crates/typst/src/layout/frame.rs @@ -289,20 +289,32 @@ impl Frame { /// Attach metadata from an iterator. pub fn meta_iter(&mut self, iter: impl IntoIterator) { let mut hide = false; - for meta in iter { + let size = self.size; + self.prepend_multiple(iter.into_iter().filter_map(|meta| { if matches!(meta, Meta::Hide) { hide = true; + None } else { - self.prepend(Point::zero(), FrameItem::Meta(meta, self.size)); + Some((Point::zero(), FrameItem::Meta(meta, size))) } - } + })); if hide { - Arc::make_mut(&mut self.items).retain(|(_, item)| { - matches!(item, FrameItem::Group(_) | FrameItem::Meta(Meta::Elem(_), _)) - }); + self.hide(); } } + /// Hide all content in the frame, but keep metadata. + pub fn hide(&mut self) { + Arc::make_mut(&mut self.items).retain_mut(|(_, item)| match item { + FrameItem::Group(group) => { + group.frame.hide(); + !group.frame.is_empty() + } + FrameItem::Meta(Meta::Elem(_), _) => true, + _ => false, + }); + } + /// Add a background fill. pub fn fill(&mut self, fill: Paint) { self.prepend( diff --git a/crates/typst/src/layout/grid/layout.rs b/crates/typst/src/layout/grid/layout.rs index b2490d1ea..577baaeb3 100644 --- a/crates/typst/src/layout/grid/layout.rs +++ b/crates/typst/src/layout/grid/layout.rs @@ -570,13 +570,8 @@ impl<'a> GridLayouter<'a> { } self.finish_region(engine)?; - self.render_fills_strokes()?; - for frame in &mut self.finished { - frame.meta(self.styles, false); - } - Ok(Fragment::frames(self.finished)) } diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs index 1c04d0ce8..627265f78 100644 --- a/crates/typst/src/layout/inline/mod.rs +++ b/crates/typst/src/layout/inline/mod.rs @@ -501,7 +501,11 @@ fn collect<'a>( Segment::Text(full.len() - prev) } else if let Some(elem) = child.to_packed::() { let pod = Regions::one(region, Axes::splat(false)); - let items = elem.layout_inline(engine, styles, pod)?; + let mut items = elem.layout_inline(engine, styles, pod)?; + for item in &mut items { + let MathParItem::Frame(frame) = item else { continue }; + frame.meta(styles, false); + } full.extend(items.iter().map(MathParItem::text)); Segment::Equation(elem, items) } else if let Some(elem) = child.to_packed::() { @@ -591,6 +595,7 @@ fn prepare<'a>( } else { let pod = Regions::one(region, Axes::splat(false)); let mut frame = elem.layout(engine, styles, pod)?.into_frame(); + frame.meta(styles, false); frame.translate(Point::with_y(TextElem::baseline_in(styles))); items.push(Item::Frame(frame)); } @@ -1315,6 +1320,7 @@ fn commit( let region = Size::new(amount, full); let pod = Regions::one(region, Axes::new(true, false)); let mut frame = elem.layout(engine, *styles, pod)?.into_frame(); + frame.meta(*styles, false); frame.translate(Point::with_y(TextElem::baseline_in(*styles))); push(&mut offset, frame); } else { @@ -1322,8 +1328,9 @@ fn commit( } } Item::Text(shaped) => { - let frame = + let mut frame = shaped.build(engine, justification_ratio, extra_justification); + frame.meta(shaped.styles, false); push(&mut offset, frame); } Item::Frame(frame) | Item::Meta(frame) => { diff --git a/crates/typst/src/layout/inline/shaping.rs b/crates/typst/src/layout/inline/shaping.rs index 28aee7bd1..6b1e2e93d 100644 --- a/crates/typst/src/layout/inline/shaping.rs +++ b/crates/typst/src/layout/inline/shaping.rs @@ -322,9 +322,6 @@ impl<'a> ShapedText<'a> { offset += width; } - // Apply metadata. - frame.meta(self.styles, false); - frame } diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs index 9cd771e68..2f02dfe4a 100644 --- a/crates/typst/src/math/equation.rs +++ b/crates/typst/src/math/equation.rs @@ -188,9 +188,6 @@ impl Packed { let descent = bottom_edge.max(frame.descent() - slack); frame.translate(Point::with_y(ascent - frame.baseline())); frame.size_mut().y = ascent + descent; - - // Apply metadata. - frame.meta(styles, false); } Ok(items) @@ -251,9 +248,6 @@ impl Layout for Packed { frame.push_frame(Point::new(x, y), counter) } - // Apply metadata. - frame.meta(styles, false); - Ok(Fragment::frame(frame)) } } diff --git a/crates/typst/src/visualize/image/mod.rs b/crates/typst/src/visualize/image/mod.rs index 9d26a8ed8..613bb7d41 100644 --- a/crates/typst/src/visualize/image/mod.rs +++ b/crates/typst/src/visualize/image/mod.rs @@ -236,13 +236,9 @@ impl Layout for Packed { // Create a clipping group if only part of the image should be visible. if fit == ImageFit::Cover && !target.fits(fitted) { - frame.meta(styles, false); frame.clip(Path::rect(frame.size())); } - // Apply metadata. - frame.meta(styles, false); - Ok(Fragment::frame(frame)) } } diff --git a/crates/typst/src/visualize/line.rs b/crates/typst/src/visualize/line.rs index 0c8835000..b17074840 100644 --- a/crates/typst/src/visualize/line.rs +++ b/crates/typst/src/visualize/line.rs @@ -89,7 +89,6 @@ impl Layout for Packed { let mut frame = Frame::soft(target); let shape = Geometry::Line(delta.to_point()).stroked(stroke); frame.push(start.to_point(), FrameItem::Shape(shape, self.span())); - frame.meta(styles, false); Ok(Fragment::frame(frame)) } } diff --git a/crates/typst/src/visualize/polygon.rs b/crates/typst/src/visualize/polygon.rs index 1f6dce44e..f375e8ca2 100644 --- a/crates/typst/src/visualize/polygon.rs +++ b/crates/typst/src/visualize/polygon.rs @@ -171,7 +171,6 @@ impl Layout for Packed { let shape = Shape { geometry: Geometry::Path(path), stroke, fill }; frame.push(Point::zero(), FrameItem::Shape(shape, self.span())); - frame.meta(styles, false); Ok(Fragment::frame(frame)) } diff --git a/crates/typst/src/visualize/shape.rs b/crates/typst/src/visualize/shape.rs index b8938d3ba..dd559b475 100644 --- a/crates/typst/src/visualize/shape.rs +++ b/crates/typst/src/visualize/shape.rs @@ -523,9 +523,6 @@ fn layout( } } - // Apply metadata. - frame.meta(styles, false); - Ok(Fragment::frame(frame)) }