summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-06-03 10:12:52 +0200
committerGitHub <noreply@github.com>2024-06-03 08:12:52 +0000
commit755dd4112d58d54d6bce7d9a44a6d183ddadc772 (patch)
treeceb4a8a7ac2d941ae5b6dc7ae56a05cd4afb7473 /crates
parent23746ee18901e08852306f35639298ad234d3481 (diff)
Show block-level elements as blocks (#4310)
Diffstat (limited to 'crates')
-rw-r--r--crates/typst/src/foundations/int.rs6
-rw-r--r--crates/typst/src/introspection/mod.rs4
-rw-r--r--crates/typst/src/layout/abs.rs14
-rw-r--r--crates/typst/src/layout/align.rs5
-rw-r--r--crates/typst/src/layout/axes.rs12
-rw-r--r--crates/typst/src/layout/columns.rs151
-rw-r--r--crates/typst/src/layout/container.rs708
-rw-r--r--crates/typst/src/layout/flow.rs131
-rw-r--r--crates/typst/src/layout/fragment.rs5
-rw-r--r--crates/typst/src/layout/frame.rs21
-rw-r--r--crates/typst/src/layout/grid/cells.rs16
-rw-r--r--crates/typst/src/layout/grid/layout.rs12
-rw-r--r--crates/typst/src/layout/grid/mod.rs124
-rw-r--r--crates/typst/src/layout/grid/rowspans.rs6
-rw-r--r--crates/typst/src/layout/inline/mod.rs25
-rw-r--r--crates/typst/src/layout/layout.rs39
-rw-r--r--crates/typst/src/layout/measure.rs2
-rw-r--r--crates/typst/src/layout/mod.rs105
-rw-r--r--crates/typst/src/layout/pad.rs118
-rw-r--r--crates/typst/src/layout/page.rs4
-rw-r--r--crates/typst/src/layout/place.rs2
-rw-r--r--crates/typst/src/layout/regions.rs24
-rw-r--r--crates/typst/src/layout/repeat.rs83
-rw-r--r--crates/typst/src/layout/sides.rs10
-rw-r--r--crates/typst/src/layout/spacing.rs7
-rw-r--r--crates/typst/src/layout/stack.rs112
-rw-r--r--crates/typst/src/layout/transform.rs224
-rw-r--r--crates/typst/src/lib.rs6
-rw-r--r--crates/typst/src/math/ctx.rs8
-rw-r--r--crates/typst/src/math/equation.rs362
-rw-r--r--crates/typst/src/math/row.rs14
-rw-r--r--crates/typst/src/model/bibliography.rs10
-rw-r--r--crates/typst/src/model/document.rs9
-rw-r--r--crates/typst/src/model/enum.rs153
-rw-r--r--crates/typst/src/model/figure.rs9
-rw-r--r--crates/typst/src/model/heading.rs7
-rw-r--r--crates/typst/src/model/list.rs111
-rw-r--r--crates/typst/src/model/quote.rs10
-rw-r--r--crates/typst/src/model/table.rs117
-rw-r--r--crates/typst/src/model/terms.rs30
-rw-r--r--crates/typst/src/realize/mod.rs54
-rw-r--r--crates/typst/src/text/deco.rs2
-rw-r--r--crates/typst/src/text/raw.rs8
-rw-r--r--crates/typst/src/visualize/image/mod.rs231
-rw-r--r--crates/typst/src/visualize/line.rs66
-rw-r--r--crates/typst/src/visualize/path.rs160
-rw-r--r--crates/typst/src/visualize/pattern.rs2
-rw-r--r--crates/typst/src/visualize/polygon.rs97
-rw-r--r--crates/typst/src/visualize/shape.rs285
49 files changed, 2105 insertions, 1616 deletions
diff --git a/crates/typst/src/foundations/int.rs b/crates/typst/src/foundations/int.rs
index 7b6c0263..40f89618 100644
--- a/crates/typst/src/foundations/int.rs
+++ b/crates/typst/src/foundations/int.rs
@@ -2,10 +2,8 @@ use std::num::{NonZeroI64, NonZeroIsize, NonZeroU64, NonZeroUsize, ParseIntError
use ecow::{eco_format, EcoString};
-use crate::{
- diag::StrResult,
- foundations::{cast, func, repr, scope, ty, Repr, Str, Value},
-};
+use crate::diag::StrResult;
+use crate::foundations::{cast, func, repr, scope, ty, Repr, Str, Value};
/// A whole number.
///
diff --git a/crates/typst/src/introspection/mod.rs b/crates/typst/src/introspection/mod.rs
index 9bf61d35..1a7c0239 100644
--- a/crates/typst/src/introspection/mod.rs
+++ b/crates/typst/src/introspection/mod.rs
@@ -25,9 +25,9 @@ pub use self::state::*;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
-use crate::foundations::NativeElement;
use crate::foundations::{
- category, elem, Args, Category, Construct, Content, Packed, Scope, Unlabellable,
+ category, elem, Args, Category, Construct, Content, NativeElement, Packed, Scope,
+ Unlabellable,
};
use crate::realize::{Behave, Behaviour};
diff --git a/crates/typst/src/layout/abs.rs b/crates/typst/src/layout/abs.rs
index 451a1b5c..c3ff8cbc 100644
--- a/crates/typst/src/layout/abs.rs
+++ b/crates/typst/src/layout/abs.rs
@@ -7,6 +7,9 @@ use ecow::EcoString;
use crate::foundations::{cast, repr, Fold, Repr, Value};
use crate::utils::{Numeric, Scalar};
+/// The epsilon for approximate comparisons.
+const EPS: f64 = 1e-6;
+
/// An absolute length.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Abs(Scalar);
@@ -54,7 +57,7 @@ impl Abs {
/// Get the value of this absolute length in raw units.
pub const fn to_raw(self) -> f64 {
- (self.0).get()
+ self.0.get()
}
/// Get the value of this absolute length in a unit.
@@ -110,12 +113,17 @@ impl Abs {
/// Whether the other absolute length fits into this one (i.e. is smaller).
/// Allows for a bit of slack.
pub fn fits(self, other: Self) -> bool {
- self.0 + 1e-6 >= other.0
+ self.0 + EPS >= other.0
}
/// Compares two absolute lengths for whether they are approximately equal.
pub fn approx_eq(self, other: Self) -> bool {
- self == other || (self - other).to_raw().abs() < 1e-6
+ self == other || (self - other).to_raw().abs() < EPS
+ }
+
+ /// Whether the size is close to zero or negative.
+ pub fn approx_empty(self) -> bool {
+ self.to_raw() <= EPS
}
/// Returns a number that represent the sign of this length
diff --git a/crates/typst/src/layout/align.rs b/crates/typst/src/layout/align.rs
index 41986a10..95744412 100644
--- a/crates/typst/src/layout/align.rs
+++ b/crates/typst/src/layout/align.rs
@@ -49,10 +49,7 @@ pub struct AlignElem {
impl Show for Packed<AlignElem> {
#[typst_macros::time(name = "align", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(self
- .body()
- .clone()
- .styled(AlignElem::set_alignment(self.alignment(styles))))
+ Ok(self.body().clone().aligned(self.alignment(styles)))
}
}
diff --git a/crates/typst/src/layout/axes.rs b/crates/typst/src/layout/axes.rs
index 6f2ab70d..a96fa850 100644
--- a/crates/typst/src/layout/axes.rs
+++ b/crates/typst/src/layout/axes.rs
@@ -4,7 +4,7 @@ use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, Not};
use crate::diag::bail;
use crate::foundations::{array, cast, Array, Resolve, Smart, StyleChain};
-use crate::layout::{Abs, Dir, Length, Ratio, Rel};
+use crate::layout::{Abs, Dir, Length, Ratio, Rel, Size};
use crate::utils::Get;
/// A container with a horizontal and vertical component.
@@ -120,6 +120,16 @@ impl<T: Ord> Axes<T> {
}
}
+impl Axes<Rel<Abs>> {
+ /// Evaluate the axes relative to the given `size`.
+ pub fn relative_to(&self, size: Size) -> Size {
+ Size {
+ x: self.x.relative_to(size.x),
+ y: self.y.relative_to(size.y),
+ }
+ }
+}
+
impl<T> Get<Axis> for Axes<T> {
type Component = T;
diff --git a/crates/typst/src/layout/columns.rs b/crates/typst/src/layout/columns.rs
index f3812311..c249a227 100644
--- a/crates/typst/src/layout/columns.rs
+++ b/crates/typst/src/layout/columns.rs
@@ -2,10 +2,9 @@ use std::num::NonZeroUsize;
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, StyleChain};
+use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
use crate::layout::{
- Abs, Axes, Dir, Fragment, Frame, LayoutMultiple, Length, Point, Ratio, Regions, Rel,
- Size,
+ Abs, Axes, BlockElem, Dir, Fragment, Frame, Length, Point, Ratio, Regions, Rel, Size,
};
use crate::realize::{Behave, Behaviour};
use crate::text::TextElem;
@@ -42,7 +41,7 @@ use crate::utils::Numeric;
/// increasingly been used to solve a
/// variety of problems.
/// ```
-#[elem(LayoutMultiple)]
+#[elem(Show)]
pub struct ColumnsElem {
/// The number of columns.
#[positional]
@@ -59,82 +58,86 @@ pub struct ColumnsElem {
pub body: Content,
}
-impl LayoutMultiple for Packed<ColumnsElem> {
- #[typst_macros::time(name = "columns", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let body = self.body();
-
- // Separating the infinite space into infinite columns does not make
- // much sense.
- if !regions.size.x.is_finite() {
- return body.layout(engine, styles, regions);
- }
+impl Show for Packed<ColumnsElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), layout_columns)
+ .with_rootable(true)
+ .pack())
+ }
+}
- // Determine the width of the gutter and each column.
- let columns = self.count(styles).get();
- let gutter = self.gutter(styles).relative_to(regions.base().x);
- let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64;
-
- let backlog: Vec<_> = std::iter::once(&regions.size.y)
- .chain(regions.backlog)
- .flat_map(|&height| std::iter::repeat(height).take(columns))
- .skip(1)
- .collect();
-
- // Create the pod regions.
- let pod = Regions {
- size: Size::new(width, regions.size.y),
- full: regions.full,
- backlog: &backlog,
- last: regions.last,
- expand: Axes::new(true, regions.expand.y),
- root: regions.root,
- };
-
- // Layout the children.
- let mut frames = body.layout(engine, styles, pod)?.into_iter();
- let mut finished = vec![];
-
- let dir = TextElem::dir_in(styles);
- let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
-
- // Stitch together the columns for each region.
- for region in regions.iter().take(total_regions) {
- // The height should be the parent height if we should expand.
- // Otherwise its the maximum column height for the frame. In that
- // case, the frame is first created with zero height and then
- // resized.
- let height = if regions.expand.y { region.y } else { Abs::zero() };
- let mut output = Frame::hard(Size::new(regions.size.x, height));
- let mut cursor = Abs::zero();
-
- for _ in 0..columns {
- let Some(frame) = frames.next() else { break };
- if !regions.expand.y {
- output.size_mut().y.set_max(frame.height());
- }
-
- let width = frame.width();
- let x = if dir == Dir::LTR {
- cursor
- } else {
- regions.size.x - cursor - width
- };
-
- output.push_frame(Point::with_x(x), frame);
- cursor += width + gutter;
+/// Layout the columns.
+#[typst_macros::time(span = elem.span())]
+fn layout_columns(
+ elem: &Packed<ColumnsElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let body = elem.body();
+
+ // Separating the infinite space into infinite columns does not make
+ // much sense.
+ if !regions.size.x.is_finite() {
+ return body.layout(engine, styles, regions);
+ }
+
+ // Determine the width of the gutter and each column.
+ let columns = elem.count(styles).get();
+ let gutter = elem.gutter(styles).relative_to(regions.base().x);
+ let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64;
+
+ let backlog: Vec<_> = std::iter::once(&regions.size.y)
+ .chain(regions.backlog)
+ .flat_map(|&height| std::iter::repeat(height).take(columns))
+ .skip(1)
+ .collect();
+
+ // Create the pod regions.
+ let pod = Regions {
+ size: Size::new(width, regions.size.y),
+ full: regions.full,
+ backlog: &backlog,
+ last: regions.last,
+ expand: Axes::new(true, regions.expand.y),
+ root: regions.root,
+ };
+
+ // Layout the children.
+ let mut frames = body.layout(engine, styles, pod)?.into_iter();
+ let mut finished = vec![];
+
+ let dir = TextElem::dir_in(styles);
+ let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
+
+ // Stitch together the columns for each region.
+ for region in regions.iter().take(total_regions) {
+ // The height should be the parent height if we should expand.
+ // Otherwise its the maximum column height for the frame. In that
+ // case, the frame is first created with zero height and then
+ // resized.
+ let height = if regions.expand.y { region.y } else { Abs::zero() };
+ let mut output = Frame::hard(Size::new(regions.size.x, height));
+ let mut cursor = Abs::zero();
+
+ for _ in 0..columns {
+ let Some(frame) = frames.next() else { break };
+ if !regions.expand.y {
+ output.size_mut().y.set_max(frame.height());
}
- finished.push(output);
+ let width = frame.width();
+ let x =
+ if dir == Dir::LTR { cursor } else { regions.size.x - cursor - width };
+
+ output.push_frame(Point::with_x(x), frame);
+ cursor += width + gutter;
}
- Ok(Fragment::frames(finished))
+ finished.push(output);
}
+
+ Ok(Fragment::frames(finished))
}
/// Forces a column break.
diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs
index 31699197..26db64b6 100644
--- a/crates/typst/src/layout/container.rs
+++ b/crates/typst/src/layout/container.rs
@@ -1,11 +1,15 @@
-use crate::diag::SourceResult;
+use once_cell::unsync::Lazy;
+use smallvec::SmallVec;
+
+use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, AutoValue, Content, Packed, Resolve, Smart, StyleChain, Value,
+ cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Resolve,
+ Smart, StyleChain, Value,
};
use crate::layout::{
- Abs, Axes, Corners, Em, Fr, Fragment, Frame, FrameKind, LayoutMultiple, Length,
- Ratio, Regions, Rel, Sides, Size, Spacing, VElem,
+ Abs, Axes, Corners, Em, Fr, Fragment, Frame, FrameKind, Length, Region, Regions, Rel,
+ Sides, Size, Spacing, VElem,
};
use crate::utils::Numeric;
use crate::visualize::{clip_rect, Paint, Stroke};
@@ -106,47 +110,53 @@ pub struct BoxElem {
/// The contents of the box.
#[positional]
+ #[borrowed]
pub body: Option<Content>,
}
impl Packed<BoxElem> {
+ /// Layout this box as part of a paragraph.
#[typst_macros::time(name = "box", span = self.span())]
pub fn layout(
&self,
engine: &mut Engine,
styles: StyleChain,
- regions: Regions,
+ region: Size,
) -> SourceResult<Frame> {
- let width = match self.width(styles) {
- Sizing::Auto => Smart::Auto,
- Sizing::Rel(rel) => Smart::Custom(rel),
- Sizing::Fr(_) => Smart::Custom(Ratio::one().into()),
+ // Fetch sizing properties.
+ let width = self.width(styles);
+ let height = self.height(styles);
+ let inset = self.inset(styles).unwrap_or_default();
+
+ // Build the pod region.
+ let pod = Self::pod(&width, &height, &inset, styles, region);
+
+ // Layout the body.
+ let mut frame = match self.body(styles) {
+ // If we have no body, just create an empty frame. If necessary,
+ // its size will be adjusted below.
+ None => Frame::hard(Size::zero()),
+
+ // If we have a child, layout it into the body. Boxes are boundaries
+ // for gradient relativeness, so we set the `FrameKind` to `Hard`.
+ Some(body) => body
+ .layout(engine, styles, pod.into_regions())?
+ .into_frame()
+ .with_kind(FrameKind::Hard),
};
- // Resolve the sizing to a concrete size.
- let sizing = Axes::new(width, self.height(styles));
- let expand = sizing.as_ref().map(Smart::is_custom);
- let size = sizing
- .resolve(styles)
- .zip_map(regions.base(), |s, b| s.map(|v| v.relative_to(b)))
- .unwrap_or(regions.base());
+ // Enforce a correct frame size on the expanded axes. Do this before
+ // applying the inset, since the pod shrunk.
+ frame.set_size(pod.expand.select(pod.size, frame.size()));
- // Apply inset.
- let mut body = self.body(styles).unwrap_or_default();
- let inset = self.inset(styles).unwrap_or_default();
- if inset.iter().any(|v| !v.is_zero()) {
- body = body.padded(inset.map(|side| side.map(Length::from)));
+ // Apply the inset.
+ if !inset.is_zero() {
+ crate::layout::grow(&mut frame, &inset);
}
- // Select the appropriate base and expansion for the child depending
- // on whether it is automatically or relatively sized.
- let pod = Regions::one(size, expand);
- let mut frame = body.layout(engine, styles, pod)?.into_frame();
-
- // Enforce correct size.
- *frame.size_mut() = expand.select(size, frame.size());
-
- // Apply baseline shift.
+ // Apply baseline shift. Do this after setting the size and applying the
+ // inset, so that a relative shift is resolved relative to the final
+ // height.
let shift = self.baseline(styles).relative_to(frame.height());
if !shift.is_zero() {
frame.set_baseline(frame.baseline() - shift);
@@ -159,27 +169,115 @@ impl Packed<BoxElem> {
.unwrap_or_default()
.map(|s| s.map(Stroke::unwrap_or_default));
- // Clip the contents
+ // Only fetch these if necessary (for clipping or filling/stroking).
+ let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
+ let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
+
+ // Clip the contents, if requested.
if self.clip(styles) {
- let outset =
- self.outset(styles).unwrap_or_default().relative_to(frame.size());
- let size = frame.size() + outset.sum_by_axis();
- let radius = self.radius(styles).unwrap_or_default();
- frame.clip(clip_rect(size, radius, &stroke));
+ let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
+ frame.clip(clip_rect(size, &radius, &stroke));
}
// Add fill and/or stroke.
if fill.is_some() || stroke.iter().any(Option::is_some) {
- let outset = self.outset(styles).unwrap_or_default();
- let radius = self.radius(styles).unwrap_or_default();
- frame.fill_and_stroke(fill, stroke, outset, radius, self.span());
+ frame.fill_and_stroke(fill, &stroke, &outset, &radius, self.span());
}
- // Apply metadata.
- frame.set_kind(FrameKind::Hard);
-
Ok(frame)
}
+
+ /// Builds the pod region for box layout.
+ fn pod(
+ width: &Sizing,
+ height: &Smart<Rel>,
+ inset: &Sides<Rel<Abs>>,
+ styles: StyleChain,
+ region: Size,
+ ) -> Region {
+ // Resolve the size.
+ let mut size = Size::new(
+ match width {
+ // For auto, the whole region is available.
+ Sizing::Auto => region.x,
+ // Resolve the relative sizing.
+ Sizing::Rel(rel) => rel.resolve(styles).relative_to(region.x),
+ // Fr is handled outside and already factored into the `region`,
+ // so we can treat it equivalently to 100%.
+ Sizing::Fr(_) => region.x,
+ },
+ match height {
+ // See above. Note that fr is not supported on this axis.
+ Smart::Auto => region.y,
+ Smart::Custom(rel) => rel.resolve(styles).relative_to(region.y),
+ },
+ );
+
+ // Take the inset, if any, into account.
+ if !inset.is_zero() {
+ size = crate::layout::shrink(size, inset);
+ }
+
+ // If the child is not auto-sized, the size is forced and we should
+ // enable expansion.
+ let expand = Axes::new(*width != Sizing::Auto, *height != Smart::Auto);
+
+ Region::new(size, expand)
+ }
+}
+
+/// An inline-level container that can produce arbitrary items that can break
+/// across lines.
+#[elem(Construct)]
+pub struct InlineElem {
+ /// A callback that is invoked with the regions to produce arbitrary
+ /// inline items.
+ #[required]
+ #[internal]
+ body: callbacks::InlineCallback,
+}
+
+impl Construct for InlineElem {
+ fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
+ bail!(args.span, "cannot be constructed manually");
+ }
+}
+
+impl InlineElem {
+ /// Create an inline-level item with a custom layouter.
+ #[allow(clippy::type_complexity)]
+ pub fn layouter<T: NativeElement>(
+ captured: Packed<T>,
+ callback: fn(
+ content: &Packed<T>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Vec<InlineItem>>,
+ ) -> Self {
+ Self::new(callbacks::InlineCallback::new(captured, callback))
+ }
+}
+
+impl Packed<InlineElem> {
+ /// Layout the element.
+ pub fn layout(
+ &self,
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Vec<InlineItem>> {
+ self.body().call(engine, styles, region)
+ }
+}
+
+/// Layouted items suitable for placing in a paragraph.
+#[derive(Debug, Clone)]
+pub enum InlineItem {
+ /// Absolute spacing between other items, and whether it is weak.
+ Space(Abs, bool),
+ /// Layouted inline-level content.
+ Frame(Frame),
}
/// A block-level container.
@@ -211,7 +309,7 @@ impl Packed<BoxElem> {
/// = Blocky
/// More text.
/// ```
-#[elem(LayoutMultiple)]
+#[elem]
pub struct BlockElem {
/// The block's width.
///
@@ -332,93 +430,155 @@ pub struct BlockElem {
#[default(false)]
pub clip: bool,
- /// The contents of the block.
- #[positional]
- pub body: Option<Content>,
-
/// Whether this block must stick to the following one.
///
/// Use this to prevent page breaks between e.g. a heading and its body.
#[internal]
#[default(false)]
- #[ghost]
+ #[parse(None)]
pub sticky: bool,
+
+ /// Whether this block can host footnotes.
+ #[internal]
+ #[default(false)]
+ #[parse(None)]
+ pub rootable: bool,
+
+ /// The contents of the block.
+ #[positional]
+ #[borrowed]
+ pub body: Option<BlockChild>,
+}
+
+impl BlockElem {
+ /// Create a block with a custom single-region layouter.
+ ///
+ /// Such a block must have `breakable: false` (which is set by this
+ /// constructor).
+ pub fn single_layouter<T: NativeElement>(
+ captured: Packed<T>,
+ f: fn(
+ content: &Packed<T>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>,
+ ) -> Self {
+ Self::new()
+ .with_breakable(false)
+ .with_body(Some(BlockChild::SingleLayouter(
+ callbacks::BlockSingleCallback::new(captured, f),
+ )))
+ }
+
+ /// Create a block with a custom multi-region layouter.
+ pub fn multi_layouter<T: NativeElement>(
+ captured: Packed<T>,
+ f: fn(
+ content: &Packed<T>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>,
+ ) -> Self {
+ Self::new().with_body(Some(BlockChild::MultiLayouter(
+ callbacks::BlockMultiCallback::new(captured, f),
+ )))
+ }
}
-impl LayoutMultiple for Packed<BlockElem> {
+impl Packed<BlockElem> {
+ /// Layout this block as part of a flow.
#[typst_macros::time(name = "block", span = self.span())]
- fn layout(
+ pub fn layout(
&self,
engine: &mut Engine,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- // Apply inset.
- let mut body = self.body(styles).unwrap_or_default();
+ // Fetch sizing properties.
+ let width = self.width(styles);
+ let height = self.height(styles);
let inset = self.inset(styles).unwrap_or_default();
- if inset.iter().any(|v| !v.is_zero()) {
- body = body.clone().padded(inset.map(|side| side.map(Length::from)));
- }
-
- // Resolve the sizing to a concrete size.
- let sizing = Axes::new(self.width(styles), self.height(styles));
- let mut expand = sizing.as_ref().map(Smart::is_custom);
- let mut size = sizing
- .resolve(styles)
- .zip_map(regions.base(), |s, b| s.map(|v| v.relative_to(b)))
- .unwrap_or(regions.base());
-
- // Layout the child.
- let mut frames = if self.breakable(styles) {
- // Measure to ensure frames for all regions have the same width.
- if sizing.x == Smart::Auto {
- let pod = Regions::one(size, Axes::splat(false));
- let frame = body.measure(engine, styles, pod)?.into_frame();
- size.x = frame.width();
- expand.x = true;
- }
-
- let mut pod = regions;
- pod.size.x = size.x;
- pod.expand = expand;
-
- if expand.y {
- pod.full = size.y;
- }
-
- // Generate backlog for fixed height.
- let mut heights = vec![];
- if sizing.y.is_custom() {
- let mut remaining = size.y;
- for region in regions.iter() {
- let limited = region.y.min(remaining);
- heights.push(limited);
- remaining -= limited;
- if Abs::zero().fits(remaining) {
- break;
+ let breakable = self.breakable(styles);
+
+ // Allocate a small vector for backlogs.
+ let mut buf = SmallVec::<[Abs; 2]>::new();
+
+ // Build the pod regions.
+ let pod =
+ Self::pod(&width, &height, &inset, breakable, styles, regions, &mut buf);
+
+ // Layout the body.
+ let body = self.body(styles);
+ let mut fragment = match body {
+ // If we have no body, just create one frame plus one per backlog
+ // region. We create them zero-sized; if necessary, their size will
+ // be adjusted below.
+ None => {
+ let mut frames = vec![];
+ frames.push(Frame::hard(Size::zero()));
+ if pod.expand.y {
+ let mut iter = pod;
+ while !iter.backlog.is_empty() {
+ frames.push(Frame::hard(Size::zero()));
+ iter.next();
}
}
+ Fragment::frames(frames)
+ }
- if let Some(last) = heights.last_mut() {
- *last += remaining;
+ // If we have content as our body, just layout it.
+ Some(BlockChild::Content(body)) => {
+ let mut fragment = body.measure(engine, styles, pod)?;
+
+ // If the body is automatically sized and produced more than one
+ // fragment, ensure that the width was consistent across all
+ // regions. If it wasn't, we need to relayout with expansion.
+ if !pod.expand.x
+ && fragment
+ .as_slice()
+ .windows(2)
+ .any(|w| !w[0].width().approx_eq(w[1].width()))
+ {
+ let max_width = fragment
+ .iter()
+ .map(|frame| frame.width())
+ .max()
+ .unwrap_or_default();
+ let pod = Regions {
+ size: Size::new(max_width, pod.size.y),
+ expand: Axes::new(true, pod.expand.y),
+ ..pod
+ };
+ fragment = body.layout(engine, styles, pod)?;
+ } else {
+ // Apply the side effect to turn the `measure` into a
+ // `layout`.
+ engine.locator.visit_frames(&fragment);
}
- pod.size.y = heights[0];
- pod.backlog = &heights[1..];
- pod.last = None;
+ fragment
+ }
+
+ // If we have a child that wants to layout with just access to the
+ // base region, give it that.
+ Some(BlockChild::SingleLayouter(callback)) => {
+ let pod = Region::new(pod.base(), pod.expand);
+ callback.call(engine, styles, pod).map(Fragment::frame)?
}
- let mut frames = body.layout(engine, styles, pod)?.into_frames();
- for (frame, &height) in frames.iter_mut().zip(&heights) {
- *frame.size_mut() =
- expand.select(Size::new(size.x, height), frame.size());
+ // If we have a child that wants to layout with full region access,
+ // we layout it.
+ //
+ // For auto-sized multi-layouters, we propagate the outer expansion
+ // so that they can decide for themselves. We also ensure again to
+ // only expand if the size is finite.
+ Some(BlockChild::MultiLayouter(callback)) => {
+ let expand = (pod.expand | regions.expand) & pod.size.map(Abs::is_finite);
+ let pod = Regions { expand, ..pod };
+ callback.call(engine, styles, pod)?
}
- frames
- } else {
- let pod = Regions::one(size, expand);
- let mut frames = body.layout(engine, styles, pod)?.into_frames();
- *frames[0].size_mut() = expand.select(size, frames[0].size());
- frames
};
// Prepare fill and stroke.
@@ -428,60 +588,219 @@ impl LayoutMultiple for Packed<BlockElem> {
.unwrap_or_default()
.map(|s| s.map(Stroke::unwrap_or_default));
- // Clip the contents
- if self.clip(styles) {
- for frame in frames.iter_mut() {
- let outset =
- self.outset(styles).unwrap_or_default().relative_to(frame.size());
- let size = frame.size() + outset.sum_by_axis();
- let radius = self.radius(styles).unwrap_or_default();
- frame.clip(clip_rect(size, radius, &stroke));
- }
+ // Only fetch these if necessary (for clipping or filling/stroking).
+ let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
+ let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
+
+ // Fetch/compute these outside of the loop.
+ let clip = self.clip(styles);
+ let has_fill_or_stroke = fill.is_some() || stroke.iter().any(Option::is_some);
+ let has_inset = !inset.is_zero();
+ let is_explicit = matches!(body, None | Some(BlockChild::Content(_)));
+
+ // Skip filling/stroking the first frame if it is empty and a non-empty
+ // one follows.
+ let mut skip_first = false;
+ if let [first, rest @ ..] = fragment.as_slice() {
+ skip_first = has_fill_or_stroke
+ && first.is_empty()
+ && rest.iter().any(|frame| !frame.is_empty());
}
- // Add fill and/or stroke.
- if fill.is_some() || stroke.iter().any(Option::is_some) {
- let mut skip = false;
- if let [first, rest @ ..] = frames.as_slice() {
- skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty());
+ // Post-process to apply insets, clipping, fills, and strokes.
+ for (i, (frame, region)) in fragment.iter_mut().zip(pod.iter()).enumerate() {
+ // Explicit blocks are boundaries for gradient relativeness.
+ if is_explicit {
+ frame.set_kind(FrameKind::Hard);
}
- let outset = self.outset(styles).unwrap_or_default();
- let radius = self.radius(styles).unwrap_or_default();
- for frame in frames.iter_mut().skip(skip as usize) {
+ // Enforce a correct frame size on the expanded axes. Do this before
+ // applying the inset, since the pod shrunk.
+ frame.set_size(pod.expand.select(region, frame.size()));
+
+ // Apply the inset.
+ if has_inset {
+ crate::layout::grow(frame, &inset);
+ }
+
+ // Clip the contents, if requested.
+ if clip {
+ let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
+ frame.clip(clip_rect(size, &radius, &stroke));
+ }
+
+ // Add fill and/or stroke.
+ if has_fill_or_stroke && (i > 0 || !skip_first) {
frame.fill_and_stroke(
fill.clone(),
- stroke.clone(),
- outset,
- radius,
+ &stroke,
+ &outset,
+ &radius,
self.span(),
);
}
}
- // Apply metadata.
- for frame in &mut frames {
- frame.set_kind(FrameKind::Hard);
+ Ok(fragment)
+ }
+
+ /// Builds the pod regions for block layout.
+ ///
+ /// If `breakable` is `false`, this will only ever return a single region.
+ fn pod<'a>(
+ width: &Smart<Rel>,
+ height: &Smart<Rel>,
+ inset: &Sides<Rel<Abs>>,
+ breakable: bool,
+ styles: StyleChain,
+ regions: Regions,
+ buf: &'a mut SmallVec<[Abs; 2]>,
+ ) -> Regions<'a> {
+ let base = regions.base();
+
+ // The vertical region sizes we're about to build.
+ let first;
+ let full;
+ let backlog: &mut [Abs];
+ let last;
+
+ // If the block has a fixed height, things are very different, so we
+ // handle that case completely separately.
+ match height {
+ Smart::Auto => {
+ if breakable {
+ // If the block automatically sized and breakable, we can
+ // just inherit the regions.
+ first = regions.size.y;
+ buf.extend_from_slice(regions.backlog);
+ backlog = buf;
+ last = regions.last;
+ } else {
+ // If the block is automatically sized, but not breakable,
+ // we provide the full base height. It doesn't really make
+ // sense to provide just the remaining height to an
+ // unbreakable block.
+ first = regions.full;
+ backlog = &mut [];
+ last = None;
+ }
+
+ // Since we're automatically sized, we inherit the base size.
+ full = regions.full;
+ }
+
+ Smart::Custom(rel) => {
+ // Resolve the sizing to a concrete size.
+ let resolved = rel.resolve(styles).relative_to(base.y);
+
+ if breakable {
+ // If the block is fixed-height and breakable, distribute
+ // the fixed height across a start region and a backlog.
+ (first, backlog) = distribute(resolved, regions, buf);
+ } else {
+ // If the block is fixed-height, but not breakable, the
+ // fixed height is all in the first region, and we have no
+ // backlog.
+ first = resolved;
+ backlog = &mut [];
+ }
+
+ // Since we're manually sized, the resolved size is also the
+ // base height.
+ full = resolved;
+
+ // If the height is manually sized, we don't want a final
+ // repeatable region.
+ last = None;
+ }
+ };
+
+ // Resolve the horizontal sizing to a concrete width and combine
+ // `width` and `first` into `size`.
+ let mut size = Size::new(
+ match width {
+ Smart::Auto => regions.size.x,
+ Smart::Custom(rel) => rel.resolve(styles).relative_to(base.x),
+ },
+ first,
+ );
+
+ // Take the inset, if any, into account, applying it to the
+ // individual region components.
+ let (mut full, mut last) = (full, last);
+ if !inset.is_zero() {
+ crate::layout::shrink_multiple(
+ &mut size, &mut full, backlog, &mut last, inset,
+ );
+ }
+
+ // If the child is manually sized along an axis (i.e. not `auto`), then
+ // it should expand along that axis. We also ensure that we only expand
+ // if the size is finite because it just doesn't make sense to expand
+ // into infinite regions.
+ let expand = Axes::new(*width != Smart::Auto, *height != Smart::Auto)
+ & size.map(Abs::is_finite);
+
+ Regions {
+ size,
+ full,
+ backlog,
+ last,
+ expand,
+ // This will only ever be set by the flow if the block is
+ // `rootable`. It is important that we propagate this, so that
+ // columns can hold footnotes.
+ root: regions.root,
}
+ }
+}
- Ok(Fragment::frames(frames))
+/// The contents of a block.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum BlockChild {
+ /// The block contains normal content.
+ Content(Content),
+ /// The block contains a layout callback that needs access to just one
+ /// base region.
+ SingleLayouter(callbacks::BlockSingleCallback),
+ /// The block contains a layout callback that needs access to the exact
+ /// regions.
+ MultiLayouter(callbacks::BlockMultiCallback),
+}
+
+impl Default for BlockChild {
+ fn default() -> Self {
+ Self::Content(Content::default())
}
}
-/// Defines how to size a grid cell along an axis.
+cast! {
+ BlockChild,
+ self => match self {
+ Self::Content(content) => content.into_value(),
+ _ => Value::Auto,
+ },
+ v: Content => Self::Content(v),
+}
+
+/// Defines how to size something along an axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Sizing {
- /// A track that fits its cell's contents.
+ /// A track that fits its item's contents.
Auto,
- /// A track size specified in absolute terms and relative to the parent's
- /// size.
- Rel(Rel<Length>),
- /// A track size specified as a fraction of the remaining free space in the
+ /// A size specified in absolute terms and relative to the parent's size.
+ Rel(Rel),
+ /// A size specified as a fraction of the remaining free space in the
/// parent.
Fr(Fr),
}
impl Sizing {
+ /// Whether this is an automatic sizing.
+ pub fn is_auto(self) -> bool {
+ matches!(self, Self::Auto)
+ }
+
/// Whether this is fractional sizing.
pub fn is_fractional(self) -> bool {
matches!(self, Self::Fr(_))
@@ -494,6 +813,15 @@ impl Default for Sizing {
}
}
+impl From<Smart<Rel>> for Sizing {
+ fn from(smart: Smart<Rel>) -> Self {
+ match smart {
+ Smart::Auto => Self::Auto,
+ Smart::Custom(rel) => Self::Rel(rel),
+ }
+ }
+}
+
impl<T: Into<Spacing>> From<T> for Sizing {
fn from(spacing: T) -> Self {
match spacing.into() {
@@ -514,3 +842,109 @@ cast! {
v: Rel<Length> => Self::Rel(v),
v: Fr => Self::Fr(v),
}
+
+/// Distribute a fixed height spread over existing regions into a new first
+/// height and a new backlog.
+fn distribute<'a>(
+ height: Abs,
+ regions: Regions,
+ buf: &'a mut SmallVec<[Abs; 2]>,
+) -> (Abs, &'a mut [Abs]) {
+ // Build new region heights from old regions.
+ let mut remaining = height;
+ for region in regions.iter() {
+ let limited = region.y.min(remaining);
+ buf.push(limited);
+ remaining -= limited;
+ if remaining.approx_empty() {
+ break;
+ }
+ }
+
+ // If there is still something remaining, apply it to the
+ // last region (it will overflow, but there's nothing else
+ // we can do).
+ if !remaining.approx_empty() {
+ if let Some(last) = buf.last_mut() {
+ *last += remaining;
+ }
+ }
+
+ // Distribute the heights to the first region and the
+ // backlog. There is no last region, since the height is
+ // fixed.
+ (buf[0], &mut buf[1..])
+}
+
+/// Manual closure implementations for layout callbacks.
+///
+/// Normal closures are not `Hash`, so we can't use them.
+mod callbacks {
+ use super::*;
+
+ macro_rules! callback {
+ ($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => {
+ #[derive(Debug, Clone, PartialEq, Hash)]
+ pub struct $name {
+ captured: Content,
+ f: fn(&Content, $($param_ty),*) -> $ret,
+ }
+
+ impl $name {
+ pub fn new<T: NativeElement>(
+ captured: Packed<T>,
+ f: fn(&Packed<T>, $($param_ty),*) -> $ret,
+ ) -> Self {
+ Self {
+ // Type-erased the content.
+ captured: captured.pack(),
+ // Safety: The only difference between the two function
+ // pointer types is the type of the first parameter,
+ // which changes from `&Packed<T>` to `&Content`. This
+ // is safe because:
+ // - `Packed<T>` is a transparent wrapper around
+ // `Content`, so for any `T` it has the same memory
+ // representation as `Content`.
+ // - While `Packed<T>` imposes the additional constraint
+ // that the content is of type `T`, this constraint is
+ // upheld: It is initially the case because we store a
+ // `Packed<T>` above. It keeps being the case over the
+ // lifetime of the closure because `capture` is a
+ // private field and `Content`'s `Clone` impl is
+ // guaranteed to retain the type (if it didn't,
+ // literally everything would break).
+ f: unsafe { std::mem::transmute(f) },
+ }
+ }
+
+ pub fn call(&self, $($param: $param_ty),*) -> $ret {
+ (self.f)(&self.captured, $($param),*)
+ }
+ }
+ };
+ }
+
+ callback! {
+ InlineCallback = (
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Vec<InlineItem>>
+ }
+
+ callback! {
+ BlockSingleCallback = (
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+ }
+
+ callback! {
+ BlockMultiCallback = (
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+ }
+}
diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs
index 84a395cc..5ae28cde 100644
--- a/crates/typst/src/layout/flow.rs
+++ b/crates/typst/src/layout/flow.rs
@@ -13,9 +13,8 @@ use crate::foundations::{
};
use crate::introspection::TagElem;
use crate::layout::{
- Abs, AlignElem, Axes, BlockElem, ColbreakElem, ColumnsElem, FixedAlignment,
- FlushElem, Fr, Fragment, Frame, FrameItem, LayoutMultiple, LayoutSingle, PlaceElem,
- Point, Regions, Rel, Size, Spacing, VElem,
+ Abs, AlignElem, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem, Fr,
+ Fragment, Frame, FrameItem, PlaceElem, Point, Regions, Rel, Size, Spacing, VElem,
};
use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
use crate::utils::Numeric;
@@ -24,16 +23,16 @@ use crate::utils::Numeric;
///
/// This element is responsible for layouting both the top-level content flow
/// and the contents of boxes.
-#[elem(Debug, LayoutMultiple)]
+#[elem(Debug)]
pub struct FlowElem {
/// The children that will be arranged into a flow.
#[variadic]
pub children: Vec<Content>,
}
-impl LayoutMultiple for Packed<FlowElem> {
+impl Packed<FlowElem> {
#[typst_macros::time(name = "flow", span = self.span())]
- fn layout(
+ pub fn layout(
&self,
engine: &mut Engine,
styles: StyleChain,
@@ -59,12 +58,13 @@ impl LayoutMultiple for Packed<FlowElem> {
alone = child
.to_packed::<StyledElem>()
.map_or(child, |styled| &styled.child)
- .can::<dyn LayoutMultiple>();
+ .is::<BlockElem>();
}
+ let outer = styles;
+
let mut layouter = FlowLayouter::new(regions, styles, alone);
for mut child in self.children().iter() {
- let outer = styles;
let mut styles = styles;
if let Some(styled) = child.to_packed::<StyledElem>() {
child = &styled.child;
@@ -77,6 +77,10 @@ impl LayoutMultiple for Packed<FlowElem> {
layouter.flush(engine)?;
} else if let Some(elem) = child.to_packed::<VElem>() {
layouter.layout_spacing(engine, elem, styles)?;
+ } else if let Some(elem) = child.to_packed::<ParElem>() {
+ layouter.layout_par(engine, elem, styles)?;
+ } else if let Some(elem) = child.to_packed::<BlockElem>() {
+ layouter.layout_block(engine, elem, styles)?;
} else if let Some(placed) = child.to_packed::<PlaceElem>() {
layouter.layout_placed(engine, placed, styles)?;
} else if child.is::<ColbreakElem>() {
@@ -84,12 +88,6 @@ impl LayoutMultiple for Packed<FlowElem> {
{
layouter.finish_region(engine, true)?;
}
- } else if let Some(elem) = child.to_packed::<ParElem>() {
- layouter.layout_par(engine, elem, styles)?;
- } else if let Some(layoutable) = child.with::<dyn LayoutSingle>() {
- layouter.layout_single(engine, layoutable, styles)?;
- } else if let Some(layoutable) = child.with::<dyn LayoutMultiple>() {
- layouter.layout_multiple(engine, child, layoutable, styles)?;
} else {
bail!(child.span(), "unexpected flow child");
}
@@ -199,6 +197,7 @@ impl<'a> FlowLayouter<'a> {
/// Create a new flow layouter.
fn new(mut regions: Regions<'a>, styles: StyleChain<'a>, alone: bool) -> Self {
let expand = regions.expand;
+ let root = std::mem::replace(&mut regions.root, false);
// Disable vertical expansion when there are multiple or not directly
// layoutable children.
@@ -206,9 +205,6 @@ impl<'a> FlowLayouter<'a> {
regions.expand.y = false;
}
- // Disable root.
- let root = std::mem::replace(&mut regions.root, false);
-
Self {
root,
regions,
@@ -253,27 +249,6 @@ impl<'a> FlowLayouter<'a> {
)
}
- /// Layout a placed element.
- fn layout_placed(
- &mut self,
- engine: &mut Engine,
- placed: &Packed<PlaceElem>,
- styles: StyleChain,
- ) -> SourceResult<()> {
- let float = placed.float(styles);
- let clearance = placed.clearance(styles);
- let alignment = placed.alignment(styles);
- let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles);
- let x_align = alignment.map_or(FixedAlignment::Center, |align| {
- align.x().unwrap_or_default().resolve(styles)
- });
- let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
- let mut frame = placed.layout(engine, styles, self.regions.base())?.into_frame();
- frame.post_process(styles);
- let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
- self.layout_item(engine, item)
- }
-
/// Layout a paragraph.
fn layout_par(
&mut self,
@@ -337,63 +312,33 @@ impl<'a> FlowLayouter<'a> {
Ok(())
}
- /// Layout into a single region.
- fn layout_single(
- &mut self,
- engine: &mut Engine,
- layoutable: &dyn LayoutSingle,
- styles: StyleChain,
- ) -> SourceResult<()> {
- 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 mut frame = layoutable.layout(engine, styles, pod)?;
- self.drain_tag(&mut frame);
- frame.post_process(styles);
- self.layout_item(
- engine,
- FlowItem::Frame { frame, align, sticky, movable: true },
- )?;
- self.last_was_par = false;
- Ok(())
- }
-
/// Layout into multiple regions.
- fn layout_multiple(
+ fn layout_block(
&mut self,
engine: &mut Engine,
- child: &Content,
- layoutable: &dyn LayoutMultiple,
- styles: StyleChain,
+ block: &'a Packed<BlockElem>,
+ styles: StyleChain<'a>,
) -> SourceResult<()> {
- // Temporarily delegerate rootness to the columns.
+ // Temporarily delegate rootness to the columns.
let is_root = self.root;
- if is_root && child.is::<ColumnsElem>() {
+ if is_root && block.rootable(styles) {
self.root = false;
self.regions.root = true;
}
- let mut notes = Vec::new();
-
if self.regions.is_full() {
// Skip directly if region is already full.
self.finish_region(engine, false)?;
}
- // How to align the block.
- let align = if let Some(align) = child.to_packed::<AlignElem>() {
- align.alignment(styles)
- } else if let Some(styled) = child.to_packed::<StyledElem>() {
- AlignElem::alignment_in(styles.chain(&styled.styles))
- } else {
- AlignElem::alignment_in(styles)
- }
- .resolve(styles);
-
// Layout the block itself.
- let sticky = BlockElem::sticky_in(styles);
- let fragment = layoutable.layout(engine, styles, self.regions)?;
+ let sticky = block.sticky(styles);
+ let fragment = block.layout(engine, styles, self.regions)?;
+
+ // How to align the block.
+ let align = AlignElem::alignment_in(styles).resolve(styles);
+ let mut notes = Vec::new();
for (i, mut frame) in fragment.into_iter().enumerate() {
// Find footnotes in the frame.
if self.root {
@@ -421,6 +366,27 @@ impl<'a> FlowLayouter<'a> {
Ok(())
}
+ /// Layout a placed element.
+ fn layout_placed(
+ &mut self,
+ engine: &mut Engine,
+ placed: &Packed<PlaceElem>,
+ styles: StyleChain,
+ ) -> SourceResult<()> {
+ let float = placed.float(styles);
+ let clearance = placed.clearance(styles);
+ let alignment = placed.alignment(styles);
+ let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles);
+ let x_align = alignment.map_or(FixedAlignment::Center, |align| {
+ align.x().unwrap_or_default().resolve(styles)
+ });
+ let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
+ let mut frame = placed.layout(engine, styles, self.regions.base())?.into_frame();
+ frame.post_process(styles);
+ let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
+ self.layout_item(engine, item)
+ }
+
/// Attach currently pending metadata to the frame.
fn drain_tag(&mut self, frame: &mut Frame) {
if !self.pending_tags.is_empty() && !frame.is_empty() {
@@ -444,13 +410,13 @@ impl<'a> FlowLayouter<'a> {
&& !self
.items
.iter()
- .any(|item| matches!(item, FlowItem::Frame { .. }))
+ .any(|item| matches!(item, FlowItem::Frame { .. },))
{
return Ok(());
}
self.regions.size.y -= v
}
- FlowItem::Fractional(_) => {}
+ FlowItem::Fractional(..) => {}
FlowItem::Frame { ref frame, movable, .. } => {
let height = frame.height();
while !self.regions.size.y.fits(height) && !self.regions.in_last() {
@@ -615,7 +581,8 @@ impl<'a> FlowLayouter<'a> {
}
FlowItem::Fractional(v) => {
let remaining = self.initial.y - used.y;
- offset += v.share(fr, remaining);
+ let length = v.share(fr, remaining);
+ offset += length;
}
FlowItem::Frame { frame, align, .. } => {
ruler = ruler.max(align.y);
diff --git a/crates/typst/src/layout/fragment.rs b/crates/typst/src/layout/fragment.rs
index ce8f17d1..c2666115 100644
--- a/crates/typst/src/layout/fragment.rs
+++ b/crates/typst/src/layout/fragment.rs
@@ -41,6 +41,11 @@ impl Fragment {
self.0
}
+ /// Extract a slice with the contained frames.
+ pub fn as_slice(&self) -> &[Frame] {
+ &self.0
+ }
+
/// Iterate over the contained frames.
pub fn iter(&self) -> std::slice::Iter<Frame> {
self.0.iter()
diff --git a/crates/typst/src/layout/frame.rs b/crates/typst/src/layout/frame.rs
index be207dc3..42fc1d72 100644
--- a/crates/typst/src/layout/frame.rs
+++ b/crates/typst/src/layout/frame.rs
@@ -30,6 +30,8 @@ pub struct Frame {
/// The items composing this layout.
items: Arc<LazyHash<Vec<(Point, FrameItem)>>>,
/// The hardness of this frame.
+ ///
+ /// Determines whether it is a boundary for gradient drawing.
kind: FrameKind,
}
@@ -70,6 +72,12 @@ impl Frame {
self.kind = kind;
}
+ /// Sets the frame's hardness builder-style.
+ pub fn with_kind(mut self, kind: FrameKind) -> Self {
+ self.kind = kind;
+ self
+ }
+
/// Whether the frame is hard or soft.
pub fn kind(&self) -> FrameKind {
self.kind
@@ -217,6 +225,11 @@ impl Frame {
/// Inline a frame at the given layer.
fn inline(&mut self, layer: usize, pos: Point, frame: Frame) {
+ // Skip work if there's nothing to do.
+ if frame.items.is_empty() {
+ return;
+ }
+
// Try to just reuse the items.
if pos.is_zero() && self.items.is_empty() {
self.items = frame.items;
@@ -354,9 +367,9 @@ impl Frame {
pub fn fill_and_stroke(
&mut self,
fill: Option<Paint>,
- stroke: Sides<Option<FixedStroke>>,
- outset: Sides<Rel<Abs>>,
- radius: Corners<Rel<Abs>>,
+ stroke: &Sides<Option<FixedStroke>>,
+ outset: &Sides<Rel<Abs>>,
+ radius: &Corners<Rel<Abs>>,
span: Span,
) {
let outset = outset.relative_to(self.size());
@@ -479,7 +492,7 @@ pub enum FrameKind {
Soft,
/// A container which uses its own size.
///
- /// This is used for page, block, box, column, grid, and stack elements.
+ /// This is used for pages, blocks, and boxes.
Hard,
}
diff --git a/crates/typst/src/layout/grid/cells.rs b/crates/typst/src/layout/grid/cells.rs
index 2d3cc556..8ec84bc3 100644
--- a/crates/typst/src/layout/grid/cells.rs
+++ b/crates/typst/src/layout/grid/cells.rs
@@ -14,10 +14,7 @@ use crate::foundations::{
Array, CastInfo, Content, Context, Fold, FromValue, Func, IntoValue, Reflect,
Resolve, Smart, StyleChain, Value,
};
-use crate::layout::{
- Abs, Alignment, Axes, Fragment, LayoutMultiple, Length, LinePosition, Regions, Rel,
- Sides, Sizing,
-};
+use crate::layout::{Abs, Alignment, Axes, Length, LinePosition, Rel, Sides, Sizing};
use crate::syntax::Span;
use crate::utils::NonZeroExt;
use crate::visualize::{Paint, Stroke};
@@ -204,17 +201,6 @@ impl From<Content> for Cell {
}
}
-impl LayoutMultiple for Cell {
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- self.body.layout(engine, styles, regions)
- }
-}
-
/// A grid entry.
#[derive(Clone)]
pub(super) enum Entry {
diff --git a/crates/typst/src/layout/grid/layout.rs b/crates/typst/src/layout/grid/layout.rs
index 2f4adbe4..ec9d1e15 100644
--- a/crates/typst/src/layout/grid/layout.rs
+++ b/crates/typst/src/layout/grid/layout.rs
@@ -10,8 +10,8 @@ use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{Resolve, StyleChain};
use crate::layout::{
- Abs, Axes, Cell, CellGrid, Dir, Fr, Fragment, Frame, FrameItem, LayoutMultiple,
- Length, Point, Regions, Rel, Size, Sizing,
+ Abs, Axes, Cell, CellGrid, Dir, Fr, Fragment, Frame, FrameItem, Length, Point,
+ Regions, Rel, Size, Sizing,
};
use crate::syntax::Span;
use crate::text::TextElem;
@@ -841,7 +841,7 @@ impl<'a> GridLayouter<'a> {
let size = Size::new(available, height);
let pod = Regions::one(size, Axes::splat(false));
- let frame = cell.measure(engine, self.styles, pod)?.into_frame();
+ let frame = cell.body.measure(engine, self.styles, pod)?.into_frame();
resolved.set_max(frame.width() - already_covered_width);
}
@@ -1069,7 +1069,7 @@ impl<'a> GridLayouter<'a> {
pod
};
- let frames = cell.measure(engine, self.styles, pod)?.into_frames();
+ let frames = cell.body.measure(engine, self.styles, pod)?.into_frames();
// Skip the first region if one cell in it is empty. Then,
// remeasure.
@@ -1232,7 +1232,7 @@ impl<'a> GridLayouter<'a> {
// rows.
pod.full = self.regions.full;
}
- let frame = cell.layout(engine, self.styles, pod)?.into_frame();
+ let frame = cell.body.layout(engine, self.styles, pod)?.into_frame();
let mut pos = pos;
if self.is_rtl {
// In the grid, cell colspans expand to the right,
@@ -1286,7 +1286,7 @@ impl<'a> GridLayouter<'a> {
pod.size.x = width;
// Push the layouted frames into the individual output frames.
- let fragment = cell.layout(engine, self.styles, pod)?;
+ let fragment = cell.body.layout(engine, self.styles, pod)?;
for (output, frame) in outputs.iter_mut().zip(fragment) {
let mut pos = pos;
if self.is_rtl {
diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst/src/layout/grid/mod.rs
index 4b6829c0..e1fec58e 100644
--- a/crates/typst/src/layout/grid/mod.rs
+++ b/crates/typst/src/layout/grid/mod.rs
@@ -19,11 +19,12 @@ use smallvec::{smallvec, SmallVec};
use crate::diag::{bail, SourceResult, StrResult, Trace, Tracepoint};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, Fold, Packed, Show, Smart, StyleChain, Value,
+ cast, elem, scope, Array, Content, Fold, NativeElement, Packed, Show, Smart,
+ StyleChain, Value,
};
use crate::layout::{
- Abs, AlignElem, Alignment, Axes, Dir, Fragment, LayoutMultiple, Length,
- OuterHAlignment, OuterVAlignment, Regions, Rel, Sides, Sizing,
+ Abs, Alignment, Axes, BlockElem, Dir, Fragment, Length, OuterHAlignment,
+ OuterVAlignment, Regions, Rel, Sides, Sizing,
};
use crate::model::{TableCell, TableFooter, TableHLine, TableHeader, TableVLine};
use crate::syntax::Span;
@@ -148,7 +149,7 @@ use crate::visualize::{Paint, Stroke};
///
/// Furthermore, strokes of a repeated grid header or footer will take
/// precedence over regular cell strokes.
-#[elem(scope, LayoutMultiple)]
+#[elem(scope, Show)]
pub struct GridElem {
/// The column sizes.
///
@@ -335,64 +336,67 @@ impl GridElem {
type GridFooter;
}
-impl LayoutMultiple for Packed<GridElem> {
- #[typst_macros::time(name = "grid", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let inset = self.inset(styles);
- let align = self.align(styles);
- let columns = self.columns(styles);
- let rows = self.rows(styles);
- let column_gutter = self.column_gutter(styles);
- let row_gutter = self.row_gutter(styles);
- let fill = self.fill(styles);
- let stroke = self.stroke(styles);
-
- let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
- let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
- // Use trace to link back to the grid when a specific cell errors
- let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
- let resolve_item = |item: &GridItem| item.to_resolvable(styles);
- let children = self.children().iter().map(|child| match child {
- GridChild::Header(header) => ResolvableGridChild::Header {
- repeat: header.repeat(styles),
- span: header.span(),
- items: header.children().iter().map(resolve_item),
- },
- GridChild::Footer(footer) => ResolvableGridChild::Footer {
- repeat: footer.repeat(styles),
- span: footer.span(),
- items: footer.children().iter().map(resolve_item),
- },
- GridChild::Item(item) => {
- ResolvableGridChild::Item(item.to_resolvable(styles))
- }
- });
- let grid = CellGrid::resolve(
- tracks,
- gutter,
- children,
- fill,
- align,
- &inset,
- &stroke,
- engine,
- styles,
- self.span(),
- )
- .trace(engine.world, tracepoint, self.span())?;
-
- let layouter = GridLayouter::new(&grid, regions, styles, self.span());
-
- // Measure the columns and layout the grid row-by-row.
- layouter.layout(engine)
+impl Show for Packed<GridElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), layout_grid).pack())
}
}
+/// Layout the grid.
+#[typst_macros::time(span = elem.span())]
+fn layout_grid(
+ elem: &Packed<GridElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let inset = elem.inset(styles);
+ let align = elem.align(styles);
+ let columns = elem.columns(styles);
+ let rows = elem.rows(styles);
+ let column_gutter = elem.column_gutter(styles);
+ let row_gutter = elem.row_gutter(styles);
+ let fill = elem.fill(styles);
+ let stroke = elem.stroke(styles);
+
+ let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
+ let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
+ // Use trace to link back to the grid when a specific cell errors
+ let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
+ let resolve_item = |item: &GridItem| item.to_resolvable(styles);
+ let children = elem.children().iter().map(|child| match child {
+ GridChild::Header(header) => ResolvableGridChild::Header {
+ repeat: header.repeat(styles),
+ span: header.span(),
+ items: header.children().iter().map(resolve_item),
+ },
+ GridChild::Footer(footer) => ResolvableGridChild::Footer {
+ repeat: footer.repeat(styles),
+ span: footer.span(),
+ items: footer.children().iter().map(resolve_item),
+ },
+ GridChild::Item(item) => ResolvableGridChild::Item(item.to_resolvable(styles)),
+ });
+ let grid = CellGrid::resolve(
+ tracks,
+ gutter,
+ children,
+ fill,
+ align,
+ &inset,
+ &stroke,
+ engine,
+ styles,
+ elem.span(),
+ )
+ .trace(engine.world, tracepoint, elem.span())?;
+
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+
+ // Measure the columns and layout the grid row-by-row.
+ layouter.layout(engine)
+}
+
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
@@ -956,7 +960,7 @@ pub fn show_grid_cell(
}
if let Smart::Custom(alignment) = align {
- body = body.styled(AlignElem::set_alignment(alignment));
+ body = body.aligned(alignment);
}
Ok(body)
diff --git a/crates/typst/src/layout/grid/rowspans.rs b/crates/typst/src/layout/grid/rowspans.rs
index 6ee3fe8d..282616ad 100644
--- a/crates/typst/src/layout/grid/rowspans.rs
+++ b/crates/typst/src/layout/grid/rowspans.rs
@@ -3,9 +3,7 @@ use super::repeated::Repeatable;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::Resolve;
-use crate::layout::{
- Abs, Axes, Cell, Frame, GridLayouter, LayoutMultiple, Point, Regions, Size, Sizing,
-};
+use crate::layout::{Abs, Axes, Cell, Frame, GridLayouter, Point, Regions, Size, Sizing};
use crate::utils::MaybeReverseIter;
/// All information needed to layout a single rowspan.
@@ -138,7 +136,7 @@ impl<'a> GridLayouter<'a> {
}
// Push the layouted frames directly into the finished frames.
- let fragment = cell.layout(engine, self.styles, pod)?;
+ let fragment = cell.body.layout(engine, self.styles, pod)?;
let (current_region, current_rrows) = current_region_data.unzip();
for ((i, finished), frame) in self
.finished
diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs
index 862077e7..5d4dd011 100644
--- a/crates/typst/src/layout/inline/mod.rs
+++ b/crates/typst/src/layout/inline/mod.rs
@@ -16,10 +16,9 @@ use crate::eval::Tracer;
use crate::foundations::{Content, Packed, Resolve, Smart, StyleChain, StyledElem};
use crate::introspection::{Introspector, Locator, TagElem};
use crate::layout::{
- Abs, AlignElem, Axes, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame,
- FrameItem, HElem, Point, Regions, Size, Sizing, Spacing,
+ Abs, AlignElem, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame, FrameItem,
+ HElem, InlineElem, InlineItem, Point, Size, Sizing, Spacing,
};
-use crate::math::{EquationElem, MathParItem};
use crate::model::{Linebreaks, ParElem};
use crate::syntax::Span;
use crate::text::{
@@ -220,7 +219,7 @@ impl Segment<'_> {
enum Item<'a> {
/// A shaped text run with consistent style and direction.
Text(ShapedText<'a>),
- /// Absolute spacing between other items.
+ /// Absolute spacing between other items, and whether it is weak.
Absolute(Abs, bool),
/// Fractional spacing between other items.
Fractional(Fr, Option<(&'a Packed<BoxElem>, StyleChain<'a>)>),
@@ -544,17 +543,15 @@ fn collect<'a>(
} else {
collector.push_text(if double { "\"" } else { "'" }, styles);
}
- } else if let Some(elem) = child.to_packed::<EquationElem>() {
+ } else if let Some(elem) = child.to_packed::<InlineElem>() {
collector.push_item(Item::Skip(LTR_ISOLATE));
- let pod = Regions::one(region, Axes::splat(false));
- for item in elem.layout_inline(engine, styles, pod)? {
+ for item in elem.layout(engine, styles, region)? {
match item {
- MathParItem::Space(space) => {
- // Spaces generated by math layout are weak.
- collector.push_item(Item::Absolute(space, true));
+ InlineItem::Space(space, weak) => {
+ collector.push_item(Item::Absolute(space, weak));
}
- MathParItem::Frame(frame) => {
+ InlineItem::Frame(frame) => {
collector.push_item(Item::Frame(frame, styles));
}
}
@@ -565,8 +562,7 @@ fn collect<'a>(
if let Sizing::Fr(v) = elem.width(styles) {
collector.push_item(Item::Fractional(v, Some((elem, styles))));
} else {
- let pod = Regions::one(region, Axes::splat(false));
- let frame = elem.layout(engine, styles, pod)?;
+ let frame = elem.layout(engine, styles, region)?;
collector.push_item(Item::Frame(frame, styles));
}
} else if let Some(elem) = child.to_packed::<TagElem>() {
@@ -1440,8 +1436,7 @@ fn commit(
let amount = v.share(fr, remaining);
if let Some((elem, styles)) = elem {
let region = Size::new(amount, full);
- let pod = Regions::one(region, Axes::new(true, false));
- let mut frame = elem.layout(engine, *styles, pod)?;
+ let mut frame = elem.layout(engine, *styles, region)?;
frame.post_process(*styles);
frame.translate(Point::with_y(TextElem::baseline_in(*styles)));
push(&mut offset, frame);
diff --git a/crates/typst/src/layout/layout.rs b/crates/typst/src/layout/layout.rs
index 7ad2a0c1..b7293640 100644
--- a/crates/typst/src/layout/layout.rs
+++ b/crates/typst/src/layout/layout.rs
@@ -3,10 +3,10 @@ use comemo::Track;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
- dict, elem, func, Content, Context, Func, NativeElement, Packed, StyleChain,
+ dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain,
};
use crate::introspection::Locatable;
-use crate::layout::{Fragment, LayoutMultiple, Regions, Size};
+use crate::layout::{BlockElem, Size};
use crate::syntax::Span;
/// Provides access to the current outer container's (or page's, if none)
@@ -67,30 +67,27 @@ pub fn layout(
}
/// Executes a `layout` call.
-#[elem(Locatable, LayoutMultiple)]
+#[elem(Locatable, Show)]
struct LayoutElem {
/// The function to call with the outer container's (or page's) size.
#[required]
func: Func,
}
-impl LayoutMultiple for Packed<LayoutElem> {
- #[typst_macros::time(name = "layout", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- // Gets the current region's base size, which will be the size of the
- // outer container, or of the page if there is no such container.
- let Size { x, y } = regions.base();
- let loc = self.location().unwrap();
- let context = Context::new(Some(loc), Some(styles));
- let result = self
- .func()
- .call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
- .display();
- result.layout(engine, styles, regions)
+impl Show for Packed<LayoutElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), |elem, engine, styles, regions| {
+ // Gets the current region's base size, which will be the size of the
+ // outer container, or of the page if there is no such container.
+ let Size { x, y } = regions.base();
+ let loc = elem.location().unwrap();
+ let context = Context::new(Some(loc), Some(styles));
+ let result = elem
+ .func()
+ .call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
+ .display();
+ result.layout(engine, styles, regions)
+ })
+ .pack())
}
}
diff --git a/crates/typst/src/layout/measure.rs b/crates/typst/src/layout/measure.rs
index 7e8ebf58..cae9d9e0 100644
--- a/crates/typst/src/layout/measure.rs
+++ b/crates/typst/src/layout/measure.rs
@@ -5,7 +5,7 @@ use crate::engine::Engine;
use crate::foundations::{
dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles,
};
-use crate::layout::{Abs, Axes, LayoutMultiple, Length, Regions, Size};
+use crate::layout::{Abs, Axes, Length, Regions, Size};
use crate::syntax::Span;
/// Measures the layouted size of content.
diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs
index 444d5162..ac1452ca 100644
--- a/crates/typst/src/layout/mod.rs
+++ b/crates/typst/src/layout/mod.rs
@@ -58,7 +58,7 @@ pub use self::page::*;
pub use self::place::*;
pub use self::point::*;
pub use self::ratio::*;
-pub use self::regions::Regions;
+pub use self::regions::*;
pub use self::rel::*;
pub use self::repeat::*;
pub use self::sides::*;
@@ -119,72 +119,15 @@ pub fn define(global: &mut Scope) {
global.define_func::<layout>();
}
-/// Root-level layout.
-///
-/// This produces a complete document and is implemented for
-/// [`DocumentElem`][crate::model::DocumentElem]. Any [`Content`]
-/// can also be laid out at root level, in which case it is
-/// wrapped inside a document element.
-pub trait LayoutRoot {
- /// Layout into a document with one frame per page.
- fn layout_root(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- ) -> SourceResult<Document>;
-}
-
-/// Layout into multiple [regions][Regions].
-///
-/// This is more appropriate for elements that, for example, can be
-/// laid out across multiple pages or columns.
-pub trait LayoutMultiple {
- /// Layout into one frame per region.
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment>;
-
- /// Layout without side effects.
+impl Content {
+ /// Layout the content into a document.
///
- /// This element must be layouted again in the same order for the results to
- /// be valid.
- fn measure(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let mut locator = Locator::chained(engine.locator.track());
- let mut engine = Engine {
- world: engine.world,
- route: engine.route.clone(),
- introspector: engine.introspector,
- locator: &mut locator,
- tracer: TrackedMut::reborrow_mut(&mut engine.tracer),
- };
- self.layout(&mut engine, styles, regions)
- }
-}
-
-/// Layout into a single [region][Regions].
-///
-/// This is more appropriate for elements that don't make sense to
-/// layout across multiple pages or columns, such as shapes.
-pub trait LayoutSingle {
- /// Layout into one frame per region.
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame>;
-}
-
-impl LayoutRoot for Content {
- fn layout_root(
+ /// This first realizes the content into a
+ /// [`DocumentElem`][crate::model::DocumentElem], which is then laid out. In
+ /// contrast to [`layout`](Self::layout()), this does not take regions since
+ /// the regions are defined by the page configuration in the content and
+ /// style chain.
+ pub fn layout_document(
&self,
engine: &mut Engine,
styles: StyleChain,
@@ -209,7 +152,7 @@ impl LayoutRoot for Content {
};
let arenas = Arenas::default();
let (document, styles) = realize_doc(&mut engine, &arenas, content, styles)?;
- document.layout_root(&mut engine, styles)
+ document.layout(&mut engine, styles)
}
cached(
@@ -222,10 +165,25 @@ impl LayoutRoot for Content {
styles,
)
}
-}
-impl LayoutMultiple for Content {
- fn layout(
+ /// Layout the content into the given regions.
+ pub fn layout(
+ &self,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment> {
+ let fragment = self.measure(engine, styles, regions)?;
+ engine.locator.visit_frames(&fragment);
+ Ok(fragment)
+ }
+
+ /// Layout without side effects.
+ ///
+ /// For the results to be valid, the element must either be layouted again
+ /// or the measurement must be confirmed through a call to
+ /// `engine.locator.visit_frames(&fragment)`.
+ pub fn measure(
&self,
engine: &mut Engine,
styles: StyleChain,
@@ -271,7 +229,7 @@ impl LayoutMultiple for Content {
flow.layout(&mut engine, styles, regions)
}
- let fragment = cached(
+ cached(
self,
engine.world,
engine.introspector,
@@ -280,9 +238,6 @@ impl LayoutMultiple for Content {
TrackedMut::reborrow_mut(&mut engine.tracer),
styles,
regions,
- )?;
-
- engine.locator.visit_frames(&fragment);
- Ok(fragment)
+ )
}
}
diff --git a/crates/typst/src/layout/pad.rs b/crates/typst/src/layout/pad.rs
index fdc4d0b6..6e1e6258 100644
--- a/crates/typst/src/layout/pad.rs
+++ b/crates/typst/src/layout/pad.rs
@@ -1,8 +1,10 @@
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Resolve, StyleChain};
+use crate::foundations::{
+ elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
+};
use crate::layout::{
- Abs, Fragment, LayoutMultiple, Length, Point, Regions, Rel, Sides, Size,
+ Abs, BlockElem, Fragment, Frame, Length, Point, Regions, Rel, Sides, Size,
};
/// Adds spacing around content.
@@ -18,7 +20,7 @@ use crate::layout::{
/// _Typing speeds can be
/// measured in words per minute._
/// ```
-#[elem(title = "Padding", LayoutMultiple)]
+#[elem(title = "Padding", Show)]
pub struct PadElem {
/// The padding at the left side.
#[parse(
@@ -60,49 +62,64 @@ pub struct PadElem {
pub body: Content,
}
-impl LayoutMultiple for Packed<PadElem> {
- #[typst_macros::time(name = "pad", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let sides = Sides::new(
- self.left(styles),
- self.top(styles),
- self.right(styles),
- self.bottom(styles),
- );
-
- // Layout child into padded regions.
- let mut backlog = vec![];
- let padding = sides.resolve(styles);
- let pod = regions.map(&mut backlog, |size| shrink(size, padding));
- let mut fragment = self.body().layout(engine, styles, pod)?;
-
- for frame in &mut fragment {
- // Apply the padding inversely such that the grown size padded
- // yields the frame's size.
- let padded = grow(frame.size(), padding);
- let padding = padding.relative_to(padded);
- let offset = Point::new(padding.left, padding.top);
-
- // Grow the frame and translate everything in the frame inwards.
- frame.set_size(padded);
- frame.translate(offset);
- }
-
- Ok(fragment)
+impl Show for Packed<PadElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), layout_pad).pack())
}
}
-/// Shrink a size by padding relative to the size itself.
-fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size {
- size - padding.relative_to(size).sum_by_axis()
+/// Layout the padded content.
+#[typst_macros::time(span = elem.span())]
+fn layout_pad(
+ elem: &Packed<PadElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let padding = Sides::new(
+ elem.left(styles).resolve(styles),
+ elem.top(styles).resolve(styles),
+ elem.right(styles).resolve(styles),
+ elem.bottom(styles).resolve(styles),
+ );
+
+ let mut backlog = vec![];
+ let pod = regions.map(&mut backlog, |size| shrink(size, &padding));
+
+ // Layout child into padded regions.
+ let mut fragment = elem.body().layout(engine, styles, pod)?;
+
+ for frame in &mut fragment {
+ grow(frame, &padding);
+ }
+
+ Ok(fragment)
}
-/// Grow a size by padding relative to the grown size.
+/// Shrink a region size by an inset relative to the size itself.
+pub(crate) fn shrink(size: Size, inset: &Sides<Rel<Abs>>) -> Size {
+ size - inset.sum_by_axis().relative_to(size)
+}
+
+/// Shrink the components of possibly multiple `Regions` by an inset relative to
+/// the regions themselves.
+pub(crate) fn shrink_multiple(
+ size: &mut Size,
+ full: &mut Abs,
+ backlog: &mut [Abs],
+ last: &mut Option<Abs>,
+ inset: &Sides<Rel<Abs>>,
+) {
+ let summed = inset.sum_by_axis();
+ *size -= summed.relative_to(*size);
+ *full -= summed.y.relative_to(*full);
+ for item in backlog {
+ *item -= summed.y.relative_to(*item);
+ }
+ *last = last.map(|v| v - summed.y.relative_to(v));
+}
+
+/// Grow a frame's size by an inset relative to the grown size.
/// This is the inverse operation to `shrink()`.
///
/// For the horizontal axis the derivation looks as follows.
@@ -110,8 +127,8 @@ fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size {
///
/// Let w be the grown target width,
/// s be the given width,
-/// l be the left padding,
-/// r be the right padding,
+/// l be the left inset,
+/// r be the right inset,
/// p = l + r.
///
/// We want that: w - l.resolve(w) - r.resolve(w) = s
@@ -121,6 +138,17 @@ fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size {
/// <=> w - p.rel * w - p.abs = s
/// <=> (1 - p.rel) * w = s + p.abs
/// <=> w = (s + p.abs) / (1 - p.rel)
-fn grow(size: Size, padding: Sides<Rel<Abs>>) -> Size {
- size.zip_map(padding.sum_by_axis(), |s, p| (s + p.abs) / (1.0 - p.rel.get()))
+pub(crate) fn grow(frame: &mut Frame, inset: &Sides<Rel<Abs>>) {
+ // Apply the padding inversely such that the grown size padded
+ // yields the frame's size.
+ let padded = frame
+ .size()
+ .zip_map(inset.sum_by_axis(), |s, p| (s + p.abs) / (1.0 - p.rel.get()));
+
+ let inset = inset.relative_to(padded);
+ let offset = Point::new(inset.left, inset.top);
+
+ // Grow the frame and translate everything in the frame inwards.
+ frame.set_size(padded);
+ frame.translate(offset);
}
diff --git a/crates/typst/src/layout/page.rs b/crates/typst/src/layout/page.rs
index dae9293b..42c267ed 100644
--- a/crates/typst/src/layout/page.rs
+++ b/crates/typst/src/layout/page.rs
@@ -14,8 +14,8 @@ use crate::foundations::{
};
use crate::introspection::{Counter, CounterDisplayElem, CounterKey, ManualPageCounter};
use crate::layout::{
- Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, LayoutMultiple,
- Length, OuterVAlignment, Point, Ratio, Regions, Rel, Sides, Size, SpecificAlignment,
+ Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length,
+ OuterVAlignment, Point, Ratio, Regions, Rel, Sides, Size, SpecificAlignment,
VAlignment,
};
diff --git a/crates/typst/src/layout/place.rs b/crates/typst/src/layout/place.rs
index f81af2b8..d8791327 100644
--- a/crates/typst/src/layout/place.rs
+++ b/crates/typst/src/layout/place.rs
@@ -2,7 +2,7 @@ use crate::diag::{bail, At, Hint, SourceResult};
use crate::engine::Engine;
use crate::foundations::{elem, scope, Content, Packed, Smart, StyleChain, Unlabellable};
use crate::layout::{
- Alignment, Axes, Em, Fragment, LayoutMultiple, Length, Regions, Rel, Size, VAlignment,
+ Alignment, Axes, Em, Fragment, Length, Regions, Rel, Size, VAlignment,
};
use crate::realize::{Behave, Behaviour};
diff --git a/crates/typst/src/layout/regions.rs b/crates/typst/src/layout/regions.rs
index d8807f37..6280632d 100644
--- a/crates/typst/src/layout/regions.rs
+++ b/crates/typst/src/layout/regions.rs
@@ -2,6 +2,28 @@ use std::fmt::{self, Debug, Formatter};
use crate::layout::{Abs, Axes, Size};
+/// A single region to layout into.
+#[derive(Debug, Copy, Clone, Hash)]
+pub struct Region {
+ /// The size of the region.
+ pub size: Size,
+ /// Whether elements should expand to fill the regions instead of shrinking
+ /// to fit the content.
+ pub expand: Axes<bool>,
+}
+
+impl Region {
+ /// Create a new region.
+ pub fn new(size: Size, expand: Axes<bool>) -> Self {
+ Self { size, expand }
+ }
+
+ /// Turns this into a region sequence.
+ pub fn into_regions(self) -> Regions<'static> {
+ Regions::one(self.size, self.expand)
+ }
+}
+
/// A sequence of regions to layout into.
///
/// A *region* is a contiguous rectangular space in which elements
@@ -80,7 +102,7 @@ impl Regions<'_> {
backlog,
last: self.last.map(|y| f(Size::new(x, y)).y),
expand: self.expand,
- root: false,
+ root: self.root,
}
}
diff --git a/crates/typst/src/layout/repeat.rs b/crates/typst/src/layout/repeat.rs
index e3a1cdab..08905466 100644
--- a/crates/typst/src/layout/repeat.rs
+++ b/crates/typst/src/layout/repeat.rs
@@ -1,8 +1,10 @@
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Resolve, StyleChain};
+use crate::foundations::{
+ elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
+};
use crate::layout::{
- Abs, AlignElem, Axes, Fragment, Frame, LayoutMultiple, Point, Regions, Size,
+ Abs, AlignElem, Axes, BlockElem, Fragment, Frame, Point, Regions, Size,
};
use crate::utils::Numeric;
@@ -27,54 +29,59 @@ use crate::utils::Numeric;
/// Berlin, the 22nd of December, 2022
/// ]
/// ```
-#[elem(LayoutMultiple)]
+#[elem(Show)]
pub struct RepeatElem {
/// The content to repeat.
#[required]
pub body: Content,
}
-impl LayoutMultiple for Packed<RepeatElem> {
- #[typst_macros::time(name = "repeat", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let pod = Regions::one(regions.size, Axes::new(false, false));
- let piece = self.body().layout(engine, styles, pod)?.into_frame();
- let align = AlignElem::alignment_in(styles).resolve(styles);
+impl Show for Packed<RepeatElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), layout_repeat).pack())
+ }
+}
- let fill = regions.size.x;
- let width = piece.width();
- let count = (fill / width).floor();
- let remaining = fill % width;
- let apart = remaining / (count - 1.0);
+/// Layout the repeated content.
+#[typst_macros::time(span = elem.span())]
+fn layout_repeat(
+ elem: &Packed<RepeatElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let pod = Regions::one(regions.size, Axes::new(false, false));
+ let piece = elem.body().layout(engine, styles, pod)?.into_frame();
+ let align = AlignElem::alignment_in(styles).resolve(styles);
- let size = Size::new(regions.size.x, piece.height());
+ let fill = regions.size.x;
+ let width = piece.width();
+ let count = (fill / width).floor();
+ let remaining = fill % width;
+ let apart = remaining / (count - 1.0);
- if !size.is_finite() {
- bail!(self.span(), "repeat with no size restrictions");
- }
+ let size = Size::new(regions.size.x, piece.height());
- let mut frame = Frame::soft(size);
- if piece.has_baseline() {
- frame.set_baseline(piece.baseline());
- }
+ if !size.is_finite() {
+ bail!(elem.span(), "repeat with no size restrictions");
+ }
- let mut offset = Abs::zero();
- if count == 1.0 {
- offset += align.x.position(remaining);
- }
+ let mut frame = Frame::soft(size);
+ if piece.has_baseline() {
+ frame.set_baseline(piece.baseline());
+ }
- if width > Abs::zero() {
- for _ in 0..(count as usize).min(1000) {
- frame.push_frame(Point::with_x(offset), piece.clone());
- offset += piece.width() + apart;
- }
- }
+ let mut offset = Abs::zero();
+ if count == 1.0 {
+ offset += align.x.position(remaining);
+ }
- Ok(Fragment::frame(frame))
+ if width > Abs::zero() {
+ for _ in 0..(count as usize).min(1000) {
+ frame.push_frame(Point::with_x(offset), piece.clone());
+ offset += piece.width() + apart;
+ }
}
+
+ Ok(Fragment::frame(frame))
}
diff --git a/crates/typst/src/layout/sides.rs b/crates/typst/src/layout/sides.rs
index c75fab63..ddf58ea9 100644
--- a/crates/typst/src/layout/sides.rs
+++ b/crates/typst/src/layout/sides.rs
@@ -107,7 +107,7 @@ impl<T> Sides<Option<T>> {
impl Sides<Rel<Abs>> {
/// Evaluate the sides relative to the given `size`.
- pub fn relative_to(self, size: Size) -> Sides<Abs> {
+ pub fn relative_to(&self, size: Size) -> Sides<Abs> {
Sides {
left: self.left.relative_to(size.x),
top: self.top.relative_to(size.y),
@@ -115,6 +115,14 @@ impl Sides<Rel<Abs>> {
bottom: self.bottom.relative_to(size.y),
}
}
+
+ /// Whether all sides are zero.
+ pub fn is_zero(&self) -> bool {
+ self.left.is_zero()
+ && self.top.is_zero()
+ && self.right.is_zero()
+ && self.bottom.is_zero()
+ }
}
impl<T> Get<Side> for Sides<T> {
diff --git a/crates/typst/src/layout/spacing.rs b/crates/typst/src/layout/spacing.rs
index 776dfdb2..ddf17e5d 100644
--- a/crates/typst/src/layout/spacing.rs
+++ b/crates/typst/src/layout/spacing.rs
@@ -130,6 +130,11 @@ pub struct VElem {
#[internal]
#[parse(args.named("weak")?.map(|v: bool| v as usize))]
pub weakness: usize,
+
+ /// Whether the element collapses if there is a parbreak in front.
+ #[internal]
+ #[parse(Some(false))]
+ pub attach: bool,
}
impl VElem {
@@ -145,7 +150,7 @@ impl VElem {
/// Weak spacing with list attach weakness.
pub fn list_attach(amount: Spacing) -> Self {
- Self::new(amount).with_weakness(2)
+ Self::new(amount).with_weakness(2).with_attach(true)
}
/// Weak spacing with BlockElem::ABOVE/BELOW weakness.
diff --git a/crates/typst/src/layout/stack.rs b/crates/typst/src/layout/stack.rs
index 33268bae..8271ff00 100644
--- a/crates/typst/src/layout/stack.rs
+++ b/crates/typst/src/layout/stack.rs
@@ -3,10 +3,12 @@ use typst_syntax::Span;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
-use crate::foundations::{cast, elem, Content, Packed, Resolve, StyleChain, StyledElem};
+use crate::foundations::{
+ cast, elem, Content, NativeElement, Packed, Resolve, Show, StyleChain, StyledElem,
+};
use crate::layout::{
- Abs, AlignElem, Axes, Axis, Dir, FixedAlignment, Fr, Fragment, Frame, HElem,
- LayoutMultiple, Point, Regions, Size, Spacing, VElem,
+ Abs, AlignElem, Axes, Axis, BlockElem, Dir, FixedAlignment, Fr, Fragment, Frame,
+ HElem, Point, Regions, Size, Spacing, VElem,
};
use crate::utils::{Get, Numeric};
@@ -24,7 +26,7 @@ use crate::utils::{Get, Numeric};
/// rect(width: 90pt),
/// )
/// ```
-#[elem(LayoutMultiple)]
+#[elem(Show)]
pub struct StackElem {
/// The direction along which the items are stacked. Possible values are:
///
@@ -52,54 +54,9 @@ pub struct StackElem {
pub children: Vec<StackChild>,
}
-impl LayoutMultiple for Packed<StackElem> {
- #[typst_macros::time(name = "stack", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let mut layouter =
- StackLayouter::new(self.span(), self.dir(styles), regions, styles);
- let axis = layouter.dir.axis();
-
- // Spacing to insert before the next block.
- let spacing = self.spacing(styles);
- let mut deferred = None;
-
- for child in self.children() {
- match child {
- StackChild::Spacing(kind) => {
- layouter.layout_spacing(*kind);
- deferred = None;
- }
- StackChild::Block(block) => {
- // Transparently handle `h`.
- if let (Axis::X, Some(h)) = (axis, block.to_packed::<HElem>()) {
- layouter.layout_spacing(*h.amount());
- deferred = None;
- continue;
- }
-
- // Transparently handle `v`.
- if let (Axis::Y, Some(v)) = (axis, block.to_packed::<VElem>()) {
- layouter.layout_spacing(*v.amount());
- deferred = None;
- continue;
- }
-
- if let Some(kind) = deferred {
- layouter.layout_spacing(kind);
- }
-
- layouter.layout_block(engine, block, styles)?;
- deferred = spacing;
- }
- }
- }
-
- layouter.finish()
+impl Show for Packed<StackElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), layout_stack).pack())
}
}
@@ -131,6 +88,55 @@ cast! {
v: Content => Self::Block(v),
}
+/// Layout the stack.
+#[typst_macros::time(span = elem.span())]
+fn layout_stack(
+ elem: &Packed<StackElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let mut layouter = StackLayouter::new(elem.span(), elem.dir(styles), regions, styles);
+ let axis = layouter.dir.axis();
+
+ // Spacing to insert before the next block.
+ let spacing = elem.spacing(styles);
+ let mut deferred = None;
+
+ for child in elem.children() {
+ match child {
+ StackChild::Spacing(kind) => {
+ layouter.layout_spacing(*kind);
+ deferred = None;
+ }
+ StackChild::Block(block) => {
+ // Transparently handle `h`.
+ if let (Axis::X, Some(h)) = (axis, block.to_packed::<HElem>()) {
+ layouter.layout_spacing(*h.amount());
+ deferred = None;
+ continue;
+ }
+
+ // Transparently handle `v`.
+ if let (Axis::Y, Some(v)) = (axis, block.to_packed::<VElem>()) {
+ layouter.layout_spacing(*v.amount());
+ deferred = None;
+ continue;
+ }
+
+ if let Some(kind) = deferred {
+ layouter.layout_spacing(kind);
+ }
+
+ layouter.layout_block(engine, block, styles)?;
+ deferred = spacing;
+ }
+ }
+ }
+
+ layouter.finish()
+}
+
/// Performs stack layout.
struct StackLayouter<'a> {
/// The span to raise errors at during layout.
@@ -231,7 +237,7 @@ impl<'a> StackLayouter<'a> {
self.finish_region()?;
}
- // Block-axis alignment of the `AlignElement` is respected by stacks.
+ // Block-axis alignment of the `AlignElem` is respected by stacks.
let align = if let Some(align) = block.to_packed::<AlignElem>() {
align.alignment(styles)
} else if let Some(styled) = block.to_packed::<StyledElem>() {
diff --git a/crates/typst/src/layout/transform.rs b/crates/typst/src/layout/transform.rs
index 85bbb362..0e9b0ca6 100644
--- a/crates/typst/src/layout/transform.rs
+++ b/crates/typst/src/layout/transform.rs
@@ -1,9 +1,11 @@
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Resolve, StyleChain};
+use crate::foundations::{
+ elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
+};
use crate::layout::{
- Abs, Alignment, Angle, Axes, FixedAlignment, Frame, HAlignment, LayoutMultiple,
- LayoutSingle, Length, Point, Ratio, Regions, Rel, Size, VAlignment,
+ Abs, Alignment, Angle, Axes, BlockElem, FixedAlignment, Frame, HAlignment, Length,
+ Point, Ratio, Region, Regions, Rel, Size, VAlignment,
};
/// Moves content without affecting layout.
@@ -24,7 +26,7 @@ use crate::layout::{
/// )
/// ))
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct MoveElem {
/// The horizontal displacement of the content.
pub dx: Rel<Length>,
@@ -37,23 +39,30 @@ pub struct MoveElem {
pub body: Content,
}
-impl LayoutSingle for Packed<MoveElem> {
- #[typst_macros::time(name = "move", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- let pod = Regions::one(regions.base(), Axes::splat(false));
- let mut frame = self.body().layout(engine, styles, pod)?.into_frame();
- let delta = Axes::new(self.dx(styles), self.dy(styles)).resolve(styles);
- let delta = delta.zip_map(regions.base(), Rel::relative_to);
- frame.translate(delta.to_point());
- Ok(frame)
+impl Show for Packed<MoveElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), layout_move).pack())
}
}
+/// Layout the moved content.
+#[typst_macros::time(span = elem.span())]
+fn layout_move(
+ elem: &Packed<MoveElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let mut frame = elem
+ .body()
+ .layout(engine, styles, region.into_regions())?
+ .into_frame();
+ let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
+ let delta = delta.zip_map(region.size, Rel::relative_to);
+ frame.translate(delta.to_point());
+ Ok(frame)
+}
+
/// Rotates content without affecting layout.
///
/// Rotates an element by a given angle. The layout will act as if the element
@@ -68,7 +77,7 @@ impl LayoutSingle for Packed<MoveElem> {
/// .map(i => rotate(24deg * i)[X]),
/// )
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct RotateElem {
/// The amount of rotation.
///
@@ -115,38 +124,43 @@ pub struct RotateElem {
pub body: Content,
}
-impl LayoutSingle for Packed<RotateElem> {
- #[typst_macros::time(name = "rotate", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- let angle = self.angle(styles);
- let align = self.origin(styles).resolve(styles);
-
- // Compute the new region's approximate size.
- let size = regions
- .base()
- .to_point()
- .transform_inf(Transform::rotate(angle))
- .map(Abs::abs)
- .to_size();
-
- measure_and_layout(
- engine,
- regions.base(),
- size,
- styles,
- self.body(),
- Transform::rotate(angle),
- align,
- self.reflow(styles),
- )
+impl Show for Packed<RotateElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), layout_rotate).pack())
}
}
+/// Layout the rotated content.
+#[typst_macros::time(span = elem.span())]
+fn layout_rotate(
+ elem: &Packed<RotateElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let angle = elem.angle(styles);
+ let align = elem.origin(styles).resolve(styles);
+
+ // Compute the new region's approximate size.
+ let size = region
+ .size
+ .to_point()
+ .transform_inf(Transform::rotate(angle))
+ .map(Abs::abs)
+ .to_size();
+
+ measure_and_layout(
+ engine,
+ region,
+ size,
+ styles,
+ elem.body(),
+ Transform::rotate(angle),
+ align,
+ elem.reflow(styles),
+ )
+}
+
/// Scales content without affecting layout.
///
/// Lets you mirror content by specifying a negative scale on a single axis.
@@ -157,7 +171,7 @@ impl LayoutSingle for Packed<RotateElem> {
/// #scale(x: -100%)[This is mirrored.]
/// #scale(x: -100%, reflow: true)[This is mirrored.]
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct ScaleElem {
/// The horizontal scaling factor.
///
@@ -203,37 +217,39 @@ pub struct ScaleElem {
pub body: Content,
}
-impl LayoutSingle for Packed<ScaleElem> {
- #[typst_macros::time(name = "scale", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- let sx = self.x(styles);
- let sy = self.y(styles);
- let align = self.origin(styles).resolve(styles);
-
- // Compute the new region's approximate size.
- let size = regions
- .base()
- .zip_map(Axes::new(sx, sy), |r, s| s.of(r))
- .map(Abs::abs);
-
- measure_and_layout(
- engine,
- regions.base(),
- size,
- styles,
- self.body(),
- Transform::scale(sx, sy),
- align,
- self.reflow(styles),
- )
+impl Show for Packed<ScaleElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), layout_scale).pack())
}
}
+/// Layout the scaled content.
+#[typst_macros::time(span = elem.span())]
+fn layout_scale(
+ elem: &Packed<ScaleElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let sx = elem.x(styles);
+ let sy = elem.y(styles);
+ let align = elem.origin(styles).resolve(styles);
+
+ // Compute the new region's approximate size.
+ let size = region.size.zip_map(Axes::new(sx, sy), |r, s| s.of(r)).map(Abs::abs);
+
+ measure_and_layout(
+ engine,
+ region,
+ size,
+ styles,
+ elem.body(),
+ Transform::scale(sx, sy),
+ align,
+ elem.reflow(styles),
+ )
+}
+
/// A scale-skew-translate transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Transform {
@@ -363,7 +379,7 @@ impl Default for Transform {
#[allow(clippy::too_many_arguments)]
fn measure_and_layout(
engine: &mut Engine,
- base_size: Size,
+ region: Region,
size: Size,
styles: StyleChain,
body: &Content,
@@ -371,41 +387,41 @@ fn measure_and_layout(
align: Axes<FixedAlignment>,
reflow: bool,
) -> SourceResult<Frame> {
- if !reflow {
- // Layout the body.
- let pod = Regions::one(base_size, Axes::splat(false));
+ if reflow {
+ // Measure the size of the body.
+ let pod = Regions::one(size, Axes::splat(false));
+ let frame = body.measure(engine, styles, pod)?.into_frame();
+
+ // Actually perform the layout.
+ let pod = Regions::one(frame.size(), Axes::splat(true));
let mut frame = body.layout(engine, styles, pod)?.into_frame();
let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
- // Apply the transform.
+ // Compute the transform.
let ts = Transform::translate(x, y)
.pre_concat(transform)
.pre_concat(Transform::translate(-x, -y));
+
+ // Compute the bounding box and offset and wrap in a new frame.
+ let (offset, size) = compute_bounding_box(&frame, ts);
frame.transform(ts);
+ frame.translate(offset);
+ frame.set_size(size);
+ Ok(frame)
+ } else {
+ // Layout the body.
+ let mut frame = body.layout(engine, styles, region.into_regions())?.into_frame();
+ let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
- return Ok(frame);
- }
+ // Compute the transform.
+ let ts = Transform::translate(x, y)
+ .pre_concat(transform)
+ .pre_concat(Transform::translate(-x, -y));
- // Measure the size of the body.
- let pod = Regions::one(size, Axes::splat(false));
- let frame = body.measure(engine, styles, pod)?.into_frame();
-
- // Actually perform the layout.
- let pod = Regions::one(frame.size(), Axes::splat(true));
- let mut frame = body.layout(engine, styles, pod)?.into_frame();
- let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
-
- // Apply the transform.
- let ts = Transform::translate(x, y)
- .pre_concat(transform)
- .pre_concat(Transform::translate(-x, -y));
-
- // Compute the bounding box and offset and wrap in a new frame.
- let (offset, size) = compute_bounding_box(&frame, ts);
- frame.transform(ts);
- frame.translate(offset);
- frame.set_size(size);
- Ok(frame)
+ // Apply the transform.
+ frame.transform(ts);
+ Ok(frame)
+ }
}
/// Computes the bounding box and offset of a transformed frame.
diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs
index e17d9346..49f2c32b 100644
--- a/crates/typst/src/lib.rs
+++ b/crates/typst/src/lib.rs
@@ -26,7 +26,7 @@
//! [evaluate]: eval::eval
//! [module]: foundations::Module
//! [content]: foundations::Content
-//! [layouted]: layout::LayoutRoot
+//! [layouted]: foundations::Content::layout_document
//! [document]: model::Document
//! [frame]: layout::Frame
@@ -70,7 +70,7 @@ use crate::foundations::{
Array, Bytes, Content, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
};
use crate::introspection::{Introspector, Locator};
-use crate::layout::{Alignment, Dir, LayoutRoot};
+use crate::layout::{Alignment, Dir};
use crate::model::Document;
use crate::syntax::package::PackageSpec;
use crate::syntax::{FileId, Source, Span};
@@ -139,7 +139,7 @@ fn typeset(
};
// Layout!
- document = content.layout_root(&mut engine, styles)?;
+ document = content.layout_document(&mut engine, styles)?;
document.introspector.rebuild(&document.pages);
iter += 1;
diff --git a/crates/typst/src/math/ctx.rs b/crates/typst/src/math/ctx.rs
index 6af17bb4..88a0664c 100644
--- a/crates/typst/src/math/ctx.rs
+++ b/crates/typst/src/math/ctx.rs
@@ -12,7 +12,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{Content, Packed, StyleChain};
-use crate::layout::{Abs, Axes, BoxElem, Em, Frame, LayoutMultiple, Regions, Size};
+use crate::layout::{Abs, Axes, BoxElem, Em, Frame, Regions, Size};
use crate::math::{
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
LayoutMath, MathFragment, MathRun, MathSize, THICK,
@@ -65,7 +65,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
pub fn new(
engine: &'v mut Engine<'b>,
styles: StyleChain<'a>,
- regions: Regions,
+ base: Size,
font: &'a Font,
) -> Self {
let math_table = font.ttf().tables().math.unwrap();
@@ -102,7 +102,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
Self {
engine,
- regions: Regions::one(regions.base(), Axes::splat(false)),
+ regions: Regions::one(base, Axes::splat(false)),
font,
ttf: font.ttf(),
table: math_table,
@@ -173,7 +173,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
) -> SourceResult<Frame> {
let local =
TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap();
- boxed.layout(self.engine, styles.chain(&local), self.regions)
+ boxed.layout(self.engine, styles.chain(&local), self.regions.base())
}
/// Layout the given [`Content`] into a [`Frame`].
diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs
index 43c82c45..d6a43d75 100644
--- a/crates/typst/src/math/equation.rs
+++ b/crates/typst/src/math/equation.rs
@@ -5,13 +5,14 @@ use unicode_math_class::MathClass;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, Content, NativeElement, Packed, Resolve, ShowSet, Smart, StyleChain, Styles,
- Synthesize,
+ elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
+ Styles, Synthesize,
};
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
use crate::layout::{
Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment, Fragment, Frame,
- LayoutMultiple, OuterHAlignment, Point, Regions, Size, SpecificAlignment, VAlignment,
+ InlineElem, InlineItem, OuterHAlignment, Point, Regions, Size, SpecificAlignment,
+ VAlignment,
};
use crate::math::{
scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant,
@@ -48,14 +49,7 @@ use crate::World;
/// horizontally. For more details about math syntax, see the
/// [main math page]($category/math).
#[elem(
- Locatable,
- Synthesize,
- ShowSet,
- LayoutMultiple,
- LayoutMath,
- Count,
- LocalName,
- Refable,
+ Locatable, Synthesize, Show, ShowSet, LayoutMath, Count, LocalName, Refable,
Outlinable
)]
pub struct EquationElem {
@@ -169,6 +163,16 @@ impl Synthesize for Packed<EquationElem> {
}
}
+impl Show for Packed<EquationElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if self.block(styles) {
+ Ok(BlockElem::multi_layouter(self.clone(), layout_equation_block).pack())
+ } else {
+ Ok(InlineElem::layouter(self.clone(), layout_equation_inline).pack())
+ }
+ }
+}
+
impl ShowSet for Packed<EquationElem> {
fn show_set(&self, styles: StyleChain) -> Styles {
let mut out = Styles::new();
@@ -187,178 +191,6 @@ impl ShowSet for Packed<EquationElem> {
}
}
-/// Layouted items suitable for placing in a paragraph.
-#[derive(Debug, Clone)]
-pub enum MathParItem {
- Space(Abs),
- Frame(Frame),
-}
-
-impl Packed<EquationElem> {
- pub fn layout_inline(
- &self,
- engine: &mut Engine<'_>,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Vec<MathParItem>> {
- assert!(!self.block(styles));
-
- let font = find_math_font(engine, styles, self.span())?;
-
- let mut ctx = MathContext::new(engine, styles, regions, &font);
- let run = ctx.layout_into_run(self, styles)?;
-
- let mut items = if run.row_count() == 1 {
- run.into_par_items()
- } else {
- vec![MathParItem::Frame(run.into_fragment(&ctx, styles).into_frame())]
- };
-
- // An empty equation should have a height, so we still create a frame
- // (which is then resized in the loop).
- if items.is_empty() {
- items.push(MathParItem::Frame(Frame::soft(Size::zero())));
- }
-
- for item in &mut items {
- let MathParItem::Frame(frame) = item else { continue };
-
- let font_size = scaled_font_size(&ctx, styles);
- let slack = ParElem::leading_in(styles) * 0.7;
- let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
- let bottom_edge =
- -TextElem::bottom_edge_in(styles).resolve(font_size, &font, None);
-
- let ascent = top_edge.max(frame.ascent() - slack);
- let descent = bottom_edge.max(frame.descent() - slack);
- frame.translate(Point::with_y(ascent - frame.baseline()));
- frame.size_mut().y = ascent + descent;
- }
-
- Ok(items)
- }
-}
-
-impl LayoutMultiple for Packed<EquationElem> {
- #[typst_macros::time(name = "math.equation", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- assert!(self.block(styles));
-
- let span = self.span();
- let font = find_math_font(engine, styles, span)?;
-
- let mut ctx = MathContext::new(engine, styles, regions, &font);
- let full_equation_builder = ctx
- .layout_into_run(self, styles)?
- .multiline_frame_builder(&ctx, styles);
- let width = full_equation_builder.size.x;
-
- let equation_builders = if BlockElem::breakable_in(styles) {
- let mut rows = full_equation_builder.frames.into_iter().peekable();
- let mut equation_builders = vec![];
- let mut last_first_pos = Point::zero();
-
- for region in regions.iter() {
- // Keep track of the position of the first row in this region,
- // so that the offset can be reverted later.
- let Some(&(_, first_pos)) = rows.peek() else { break };
- last_first_pos = first_pos;
-
- let mut frames = vec![];
- let mut height = Abs::zero();
- while let Some((sub, pos)) = rows.peek() {
- let mut pos = *pos;
- pos.y -= first_pos.y;
-
- // Finish this region if the line doesn't fit. Only do it if
- // we placed at least one line _or_ we still have non-last
- // regions. Crucially, we don't want to infinitely create
- // new regions which are too small.
- if !region.y.fits(sub.height() + pos.y)
- && (!frames.is_empty() || !regions.in_last())
- {
- break;
- }
-
- let (sub, _) = rows.next().unwrap();
- height = height.max(pos.y + sub.height());
- frames.push((sub, pos));
- }
-
- equation_builders
- .push(MathRunFrameBuilder { frames, size: Size::new(width, height) });
- }
-
- // Append remaining rows to the equation builder of the last region.
- if let Some(equation_builder) = equation_builders.last_mut() {
- equation_builder.frames.extend(rows.map(|(frame, mut pos)| {
- pos.y -= last_first_pos.y;
- (frame, pos)
- }));
-
- let height = equation_builder
- .frames
- .iter()
- .map(|(frame, pos)| frame.height() + pos.y)
- .max()
- .unwrap_or(equation_builder.size.y);
-
- equation_builder.size.y = height;
- }
-
- equation_builders
- } else {
- vec![full_equation_builder]
- };
-
- let Some(numbering) = (**self).numbering(styles) else {
- let frames = equation_builders
- .into_iter()
- .map(MathRunFrameBuilder::build)
- .collect();
- return Ok(Fragment::frames(frames));
- };
-
- let pod = Regions::one(regions.base(), Axes::splat(false));
- let number = Counter::of(EquationElem::elem())
- .display_at_loc(engine, self.location().unwrap(), styles, numbering)?
- .spanned(span)
- .layout(engine, styles, pod)?
- .into_frame();
-
- static NUMBER_GUTTER: Em = Em::new(0.5);
- let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
-
- let number_align = match self.number_align(styles) {
- SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon),
- SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v),
- SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v),
- };
-
- // Add equation numbers to each equation region.
- let frames = equation_builders
- .into_iter()
- .map(|builder| {
- add_equation_number(
- builder,
- number.clone(),
- number_align.resolve(styles),
- AlignElem::alignment_in(styles).resolve(styles).x,
- regions.size.x,
- full_number_width,
- )
- })
- .collect();
-
- Ok(Fragment::frames(frames))
- }
-}
-
impl Count for Packed<EquationElem> {
fn update(&self) -> Option<CounterUpdate> {
(self.block(StyleChain::default()) && self.numbering().is_some())
@@ -429,6 +261,170 @@ impl LayoutMath for Packed<EquationElem> {
}
}
+/// Layout an inline equation (in a paragraph).
+#[typst_macros::time(span = elem.span())]
+fn layout_equation_inline(
+ elem: &Packed<EquationElem>,
+ engine: &mut Engine<'_>,
+ styles: StyleChain,
+ region: Size,
+) -> SourceResult<Vec<InlineItem>> {
+ assert!(!elem.block(styles));
+
+ let font = find_math_font(engine, styles, elem.span())?;
+
+ let mut ctx = MathContext::new(engine, styles, region, &font);
+ let run = ctx.layout_into_run(elem, styles)?;
+
+ let mut items = if run.row_count() == 1 {
+ run.into_par_items()
+ } else {
+ vec![InlineItem::Frame(run.into_fragment(&ctx, styles).into_frame())]
+ };
+
+ // An empty equation should have a height, so we still create a frame
+ // (which is then resized in the loop).
+ if items.is_empty() {
+ items.push(InlineItem::Frame(Frame::soft(Size::zero())));
+ }
+
+ for item in &mut items {
+ let InlineItem::Frame(frame) = item else { continue };
+
+ let font_size = scaled_font_size(&ctx, styles);
+ let slack = ParElem::leading_in(styles) * 0.7;
+ let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
+ let bottom_edge =
+ -TextElem::bottom_edge_in(styles).resolve(font_size, &font, None);
+
+ let ascent = top_edge.max(frame.ascent() - slack);
+ let descent = bottom_edge.max(frame.descent() - slack);
+ frame.translate(Point::with_y(ascent - frame.baseline()));
+ frame.size_mut().y = ascent + descent;
+ }
+
+ Ok(items)
+}
+
+/// Layout a block-level equation (in a flow).
+#[typst_macros::time(span = elem.span())]
+fn layout_equation_block(
+ elem: &Packed<EquationElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ assert!(elem.block(styles));
+
+ let span = elem.span();
+ let font = find_math_font(engine, styles, span)?;
+
+ let mut ctx = MathContext::new(engine, styles, regions.base(), &font);
+ let full_equation_builder = ctx
+ .layout_into_run(elem, styles)?
+ .multiline_frame_builder(&ctx, styles);
+ let width = full_equation_builder.size.x;
+
+ let equation_builders = if BlockElem::breakable_in(styles) {
+ let mut rows = full_equation_builder.frames.into_iter().peekable();
+ let mut equation_builders = vec![];
+ let mut last_first_pos = Point::zero();
+
+ for region in regions.iter() {
+ // Keep track of the position of the first row in this region,
+ // so that the offset can be reverted later.
+ let Some(&(_, first_pos)) = rows.peek() else { break };
+ last_first_pos = first_pos;
+
+ let mut frames = vec![];
+ let mut height = Abs::zero();
+ while let Some((sub, pos)) = rows.peek() {
+ let mut pos = *pos;
+ pos.y -= first_pos.y;
+
+ // Finish this region if the line doesn't fit. Only do it if
+ // we placed at least one line _or_ we still have non-last
+ // regions. Crucially, we don't want to infinitely create
+ // new regions which are too small.
+ if !region.y.fits(sub.height() + pos.y)
+ && (!frames.is_empty() || !regions.in_last())
+ {
+ break;
+ }
+
+ let (sub, _) = rows.next().unwrap();
+ height = height.max(pos.y + sub.height());
+ frames.push((sub, pos));
+ }
+
+ equation_builders
+ .push(MathRunFrameBuilder { frames, size: Size::new(width, height) });
+ }
+
+ // Append remaining rows to the equation builder of the last region.
+ if let Some(equation_builder) = equation_builders.last_mut() {
+ equation_builder.frames.extend(rows.map(|(frame, mut pos)| {
+ pos.y -= last_first_pos.y;
+ (frame, pos)
+ }));
+
+ let height = equation_builder
+ .frames
+ .iter()
+ .map(|(frame, pos)| frame.height() + pos.y)
+ .max()
+ .unwrap_or(equation_builder.size.y);
+
+ equation_builder.size.y = height;
+ }
+
+ equation_builders
+ } else {
+ vec![full_equation_builder]
+ };
+
+ let Some(numbering) = (**elem).numbering(styles) else {
+ let frames = equation_builders
+ .into_iter()
+ .map(MathRunFrameBuilder::build)
+ .collect();
+ return Ok(Fragment::frames(frames));
+ };
+
+ let pod = Regions::one(regions.base(), Axes::splat(false));
+ let number = Counter::of(EquationElem::elem())
+ .display_at_loc(engine, elem.location().unwrap(), styles, numbering)?
+ .spanned(span)
+ .layout(engine, styles, pod)?
+ .into_frame();
+
+ static NUMBER_GUTTER: Em = Em::new(0.5);
+ let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
+
+ let number_align = match elem.number_align(styles) {
+ SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon),
+ SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v),
+ SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v),
+ };
+
+ // Add equation numbers to each equation region.
+ let frames = equation_builders
+ .into_iter()
+ .map(|builder| {
+ add_equation_number(
+ builder,
+ number.clone(),
+ number_align.resolve(styles),
+ AlignElem::alignment_in(styles).resolve(styles).x,
+ regions.size.x,
+ full_number_width,
+ )
+ })
+ .collect();
+
+ Ok(Fragment::frames(frames))
+}
+
fn find_math_font(
engine: &mut Engine<'_>,
styles: StyleChain,
diff --git a/crates/typst/src/math/row.rs b/crates/typst/src/math/row.rs
index 6454f491..0681752a 100644
--- a/crates/typst/src/math/row.rs
+++ b/crates/typst/src/math/row.rs
@@ -3,10 +3,10 @@ use std::iter::once;
use unicode_math_class::MathClass;
use crate::foundations::{Resolve, StyleChain};
-use crate::layout::{Abs, AlignElem, Em, Frame, Point, Size};
+use crate::layout::{Abs, AlignElem, Em, Frame, InlineItem, Point, Size};
use crate::math::{
alignments, scaled_font_size, spacing, EquationElem, FrameFragment, MathContext,
- MathFragment, MathParItem, MathSize,
+ MathFragment, MathSize,
};
use crate::model::ParElem;
@@ -251,7 +251,7 @@ impl MathRun {
frame
}
- pub fn into_par_items(self) -> Vec<MathParItem> {
+ pub fn into_par_items(self) -> Vec<InlineItem> {
let mut items = vec![];
let mut x = Abs::zero();
@@ -279,7 +279,7 @@ impl MathRun {
match fragment {
MathFragment::Space(width)
| MathFragment::Spacing(SpacingFragment { width, .. }) => {
- items.push(MathParItem::Space(width));
+ items.push(InlineItem::Space(width, true));
continue;
}
_ => {}
@@ -305,7 +305,7 @@ impl MathRun {
std::mem::replace(&mut frame, Frame::soft(Size::zero()));
finalize_frame(&mut frame_prev, x, ascent, descent);
- items.push(MathParItem::Frame(frame_prev));
+ items.push(InlineItem::Frame(frame_prev));
empty = true;
x = Abs::zero();
@@ -315,7 +315,7 @@ impl MathRun {
space_is_visible = true;
if let Some(f_next) = iter.peek() {
if !is_space(f_next) {
- items.push(MathParItem::Space(Abs::zero()));
+ items.push(InlineItem::Space(Abs::zero(), true));
}
}
} else {
@@ -327,7 +327,7 @@ impl MathRun {
// contribute width (if it had hidden content).
if !empty {
finalize_frame(&mut frame, x, ascent, descent);
- items.push(MathParItem::Frame(frame));
+ items.push(InlineItem::Frame(frame));
}
items
diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst/src/model/bibliography.rs
index 502a102b..c820bc40 100644
--- a/crates/typst/src/model/bibliography.rs
+++ b/crates/typst/src/model/bibliography.rs
@@ -29,8 +29,8 @@ use crate::foundations::{
};
use crate::introspection::{Introspector, Locatable, Location};
use crate::layout::{
- BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem, Sizing,
- TrackSizings, VElem,
+ BlockChild, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
+ Sizing, TrackSizings, VElem,
};
use crate::model::{
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
@@ -926,8 +926,10 @@ impl ElemRenderer<'_> {
match elem.display {
Some(Display::Block) => {
- content =
- BlockElem::new().with_body(Some(content)).pack().spanned(self.span);
+ content = BlockElem::new()
+ .with_body(Some(BlockChild::Content(content)))
+ .pack()
+ .spanned(self.span);
}
Some(Display::Indent) => {
content = PadElem::new(content).pack().spanned(self.span);
diff --git a/crates/typst/src/model/document.rs b/crates/typst/src/model/document.rs
index 1e614399..e613d07f 100644
--- a/crates/typst/src/model/document.rs
+++ b/crates/typst/src/model/document.rs
@@ -7,7 +7,7 @@ use crate::foundations::{
StyledElem, Value,
};
use crate::introspection::{Introspector, ManualPageCounter};
-use crate::layout::{LayoutRoot, Page, PageElem};
+use crate::layout::{Page, PageElem};
/// The root element of a document and its metadata.
///
@@ -25,7 +25,7 @@ use crate::layout::{LayoutRoot, Page, PageElem};
///
/// Note that metadata set with this function is not rendered within the
/// document. Instead, it is embedded in the compiled PDF file.
-#[elem(Construct, LayoutRoot)]
+#[elem(Construct)]
pub struct DocumentElem {
/// The document's title. This is often rendered as the title of the
/// PDF viewer window.
@@ -69,9 +69,10 @@ impl Construct for DocumentElem {
}
}
-impl LayoutRoot for Packed<DocumentElem> {
+impl Packed<DocumentElem> {
+ /// Layout this document.
#[typst_macros::time(name = "document", span = self.span())]
- fn layout_root(
+ pub fn layout(
&self,
engine: &mut Engine,
styles: StyleChain,
diff --git a/crates/typst/src/model/enum.rs b/crates/typst/src/model/enum.rs
index 98402057..0eb0f773 100644
--- a/crates/typst/src/model/enum.rs
+++ b/crates/typst/src/model/enum.rs
@@ -6,11 +6,12 @@ use smallvec::{smallvec, SmallVec};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, Context, Packed, Smart, StyleChain,
+ cast, elem, scope, Array, Content, Context, NativeElement, Packed, Show, Smart,
+ StyleChain,
};
use crate::layout::{
Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment,
- LayoutMultiple, Length, Regions, Sizing, Spacing, VAlignment,
+ Length, Regions, Sizing, Spacing, VAlignment, VElem,
};
use crate::model::{Numbering, NumberingPattern, ParElem};
use crate::text::TextElem;
@@ -71,7 +72,7 @@ use crate::text::TextElem;
/// Enumeration items can contain multiple paragraphs and other block-level
/// content. All content that is indented more than an item's marker becomes
/// part of that item.
-#[elem(scope, title = "Numbered List", LayoutMultiple)]
+#[elem(scope, title = "Numbered List", Show)]
pub struct EnumElem {
/// If this is `{false}`, the items are spaced apart with
/// [enum spacing]($enum.spacing). If it is `{true}`, they use normal
@@ -212,83 +213,95 @@ impl EnumElem {
type EnumItem;
}
-impl LayoutMultiple for Packed<EnumElem> {
- #[typst_macros::time(name = "enum", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let numbering = self.numbering(styles);
- let indent = self.indent(styles);
- let body_indent = self.body_indent(styles);
- let gutter = if self.tight(styles) {
- ParElem::leading_in(styles).into()
- } else {
- self.spacing(styles)
- .unwrap_or_else(|| *BlockElem::below_in(styles).amount())
- };
+impl Show for Packed<EnumElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let mut realized = BlockElem::multi_layouter(self.clone(), layout_enum).pack();
- let mut cells = vec![];
- let mut number = self.start(styles);
- let mut parents = EnumElem::parents_in(styles);
+ if self.tight(styles) {
+ let leading = ParElem::leading_in(styles);
+ let spacing = VElem::list_attach(leading.into()).pack();
+ realized = spacing + realized;
+ }
- let full = self.full(styles);
+ Ok(realized)
+ }
+}
- // Horizontally align based on the given respective parameter.
- // Vertically align to the top to avoid inheriting `horizon` or `bottom`
- // alignment from the context and having the number be displaced in
- // relation to the item it refers to.
- let number_align = self.number_align(styles);
+/// Layout the enumeration.
+#[typst_macros::time(span = elem.span())]
+fn layout_enum(
+ elem: &Packed<EnumElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let numbering = elem.numbering(styles);
+ let indent = elem.indent(styles);
+ let body_indent = elem.body_indent(styles);
+ let gutter = if elem.tight(styles) {
+ ParElem::leading_in(styles).into()
+ } else {
+ elem.spacing(styles)
+ .unwrap_or_else(|| *BlockElem::below_in(styles).amount())
+ };
- for item in self.children() {
- number = item.number(styles).unwrap_or(number);
+ let mut cells = vec![];
+ let mut number = elem.start(styles);
+ let mut parents = EnumElem::parents_in(styles);
- let context = Context::new(None, Some(styles));
- let resolved = if full {
- parents.push(number);
- let content =
- numbering.apply(engine, context.track(), &parents)?.display();
- parents.pop();
- content
- } else {
- match numbering {
- Numbering::Pattern(pattern) => {
- TextElem::packed(pattern.apply_kth(parents.len(), number))
- }
- other => other.apply(engine, context.track(), &[number])?.display(),
- }
- };
+ let full = elem.full(styles);
- // Disable overhang as a workaround to end-aligned dots glitching
- // and decreasing spacing between numbers and items.
- let resolved =
- resolved.aligned(number_align).styled(TextElem::set_overhang(false));
+ // Horizontally align based on the given respective parameter.
+ // Vertically align to the top to avoid inheriting `horizon` or `bottom`
+ // alignment from the context and having the number be displaced in
+ // relation to the item it refers to.
+ let number_align = elem.number_align(styles);
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(resolved));
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(
- item.body().clone().styled(EnumElem::set_parents(smallvec![number])),
- ));
- number = number.saturating_add(1);
- }
+ for item in elem.children() {
+ number = item.number(styles).unwrap_or(number);
- let grid = CellGrid::new(
- Axes::with_x(&[
- Sizing::Rel(indent.into()),
- Sizing::Auto,
- Sizing::Rel(body_indent.into()),
- Sizing::Auto,
- ]),
- Axes::with_y(&[gutter.into()]),
- cells,
- );
- let layouter = GridLayouter::new(&grid, regions, styles, self.span());
+ let context = Context::new(None, Some(styles));
+ let resolved = if full {
+ parents.push(number);
+ let content = numbering.apply(engine, context.track(), &parents)?.display();
+ parents.pop();
+ content
+ } else {
+ match numbering {
+ Numbering::Pattern(pattern) => {
+ TextElem::packed(pattern.apply_kth(parents.len(), number))
+ }
+ other => other.apply(engine, context.track(), &[number])?.display(),
+ }
+ };
- layouter.layout(engine)
+ // Disable overhang as a workaround to end-aligned dots glitching
+ // and decreasing spacing between numbers and items.
+ let resolved =
+ resolved.aligned(number_align).styled(TextElem::set_overhang(false));
+
+ cells.push(Cell::from(Content::empty()));
+ cells.push(Cell::from(resolved));
+ cells.push(Cell::from(Content::empty()));
+ cells.push(Cell::from(
+ item.body().clone().styled(EnumElem::set_parents(smallvec![number])),
+ ));
+ number = number.saturating_add(1);
}
+
+ let grid = CellGrid::new(
+ Axes::with_x(&[
+ Sizing::Rel(indent.into()),
+ Sizing::Auto,
+ Sizing::Rel(body_indent.into()),
+ Sizing::Auto,
+ ]),
+ Axes::with_y(&[gutter.into()]),
+ cells,
+ );
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+
+ layouter.layout(engine)
}
/// An enumeration item.
diff --git a/crates/typst/src/model/figure.rs b/crates/typst/src/model/figure.rs
index a21e5af5..164d1b48 100644
--- a/crates/typst/src/model/figure.rs
+++ b/crates/typst/src/model/figure.rs
@@ -14,8 +14,8 @@ use crate::introspection::{
Count, Counter, CounterKey, CounterUpdate, Locatable, Location,
};
use crate::layout::{
- AlignElem, Alignment, BlockElem, Em, HAlignment, Length, OuterVAlignment, PlaceElem,
- VAlignment, VElem,
+ AlignElem, Alignment, BlockChild, BlockElem, Em, HAlignment, Length, OuterVAlignment,
+ PlaceElem, VAlignment, VElem,
};
use crate::model::{Numbering, NumberingPattern, Outlinable, Refable, Supplement};
use crate::text::{Lang, Region, TextElem};
@@ -317,7 +317,10 @@ impl Show for Packed<FigureElem> {
}
// Wrap the contents in a block.
- realized = BlockElem::new().with_body(Some(realized)).pack().spanned(self.span());
+ realized = BlockElem::new()
+ .with_body(Some(BlockChild::Content(realized)))
+ .pack()
+ .spanned(self.span());
// Wrap in a float.
if let Some(align) = self.placement(styles) {
diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs
index c9389b38..478b4315 100644
--- a/crates/typst/src/model/heading.rs
+++ b/crates/typst/src/model/heading.rs
@@ -8,7 +8,7 @@ use crate::foundations::{
};
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
use crate::layout::{
- Abs, Axes, BlockElem, Em, HElem, LayoutMultiple, Length, Regions, VElem,
+ Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Regions, VElem,
};
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
@@ -248,7 +248,10 @@ impl Show for Packed<HeadingElem> {
realized = realized.styled(ParElem::set_hanging_indent(indent.into()));
}
- Ok(BlockElem::new().with_body(Some(realized)).pack().spanned(span))
+ Ok(BlockElem::new()
+ .with_body(Some(BlockChild::Content(realized)))
+ .pack()
+ .spanned(span))
}
}
diff --git a/crates/typst/src/model/list.rs b/crates/typst/src/model/list.rs
index 665575e8..34a4a0b2 100644
--- a/crates/typst/src/model/list.rs
+++ b/crates/typst/src/model/list.rs
@@ -3,12 +3,12 @@ use comemo::Track;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, Context, Depth, Func, Packed, Smart, StyleChain,
- Value,
+ cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
+ Smart, StyleChain, Value,
};
use crate::layout::{
- Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment,
- LayoutMultiple, Length, Regions, Sizing, Spacing, VAlignment,
+ Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, Length,
+ Regions, Sizing, Spacing, VAlignment, VElem,
};
use crate::model::ParElem;
use crate::text::TextElem;
@@ -44,7 +44,7 @@ use crate::text::TextElem;
/// followed by a space to create a list item. A list item can contain multiple
/// paragraphs and other block-level content. All content that is indented
/// more than an item's marker becomes part of that item.
-#[elem(scope, title = "Bullet List", LayoutMultiple)]
+#[elem(scope, title = "Bullet List", Show)]
pub struct ListElem {
/// If this is `{false}`, the items are spaced apart with
/// [list spacing]($list.spacing). If it is `{true}`, they use normal
@@ -137,54 +137,65 @@ impl ListElem {
type ListItem;
}
-impl LayoutMultiple for Packed<ListElem> {
- #[typst_macros::time(name = "list", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let indent = self.indent(styles);
- let body_indent = self.body_indent(styles);
- let gutter = if self.tight(styles) {
- ParElem::leading_in(styles).into()
- } else {
- self.spacing(styles)
- .unwrap_or_else(|| *BlockElem::below_in(styles).amount())
- };
-
- let Depth(depth) = ListElem::depth_in(styles);
- let marker = self
- .marker(styles)
- .resolve(engine, styles, depth)?
- // avoid '#set align' interference with the list
- .aligned(HAlignment::Start + VAlignment::Top);
-
- let mut cells = vec![];
- for item in self.children() {
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(marker.clone()));
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(
- item.body().clone().styled(ListElem::set_depth(Depth(1))),
- ));
+impl Show for Packed<ListElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let mut realized = BlockElem::multi_layouter(self.clone(), layout_list).pack();
+
+ if self.tight(styles) {
+ let leading = ParElem::leading_in(styles);
+ let spacing = VElem::list_attach(leading.into()).pack();
+ realized = spacing + realized;
}
- let grid = CellGrid::new(
- Axes::with_x(&[
- Sizing::Rel(indent.into()),
- Sizing::Auto,
- Sizing::Rel(body_indent.into()),
- Sizing::Auto,
- ]),
- Axes::with_y(&[gutter.into()]),
- cells,
- );
- let layouter = GridLayouter::new(&grid, regions, styles, self.span());
-
- layouter.layout(engine)
+ Ok(realized)
+ }
+}
+
+/// Layout the list.
+#[typst_macros::time(span = elem.span())]
+fn layout_list(
+ elem: &Packed<ListElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let indent = elem.indent(styles);
+ let body_indent = elem.body_indent(styles);
+ let gutter = if elem.tight(styles) {
+ ParElem::leading_in(styles).into()
+ } else {
+ elem.spacing(styles)
+ .unwrap_or_else(|| *BlockElem::below_in(styles).amount())
+ };
+
+ let Depth(depth) = ListElem::depth_in(styles);
+ let marker = elem
+ .marker(styles)
+ .resolve(engine, styles, depth)?
+ // avoid '#set align' interference with the list
+ .aligned(HAlignment::Start + VAlignment::Top);
+
+ let mut cells = vec![];
+ for item in elem.children() {
+ cells.push(Cell::from(Content::empty()));
+ cells.push(Cell::from(marker.clone()));
+ cells.push(Cell::from(Content::empty()));
+ cells.push(Cell::from(item.body().clone().styled(ListElem::set_depth(Depth(1)))));
}
+
+ let grid = CellGrid::new(
+ Axes::with_x(&[
+ Sizing::Rel(indent.into()),
+ Sizing::Auto,
+ Sizing::Rel(body_indent.into()),
+ Sizing::Auto,
+ ]),
+ Axes::with_y(&[gutter.into()]),
+ cells,
+ );
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+
+ layouter.layout(engine)
}
/// A bullet list item.
diff --git a/crates/typst/src/model/quote.rs b/crates/typst/src/model/quote.rs
index f4a66871..6f7f0718 100644
--- a/crates/typst/src/model/quote.rs
+++ b/crates/typst/src/model/quote.rs
@@ -4,7 +4,9 @@ use crate::foundations::{
cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart,
StyleChain, Styles,
};
-use crate::layout::{Alignment, BlockElem, Em, HElem, PadElem, Spacing, VElem};
+use crate::layout::{
+ Alignment, BlockChild, BlockElem, Em, HElem, PadElem, Spacing, VElem,
+};
use crate::model::{CitationForm, CiteElem};
use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem};
@@ -181,8 +183,10 @@ impl Show for Packed<QuoteElem> {
}
if block {
- realized =
- BlockElem::new().with_body(Some(realized)).pack().spanned(self.span());
+ realized = BlockElem::new()
+ .with_body(Some(BlockChild::Content(realized)))
+ .pack()
+ .spanned(self.span());
if let Some(attribution) = self.attribution(styles).as_ref() {
let mut seq = vec![TextElem::packed('—'), SpaceElem::new().pack()];
diff --git a/crates/typst/src/model/table.rs b/crates/typst/src/model/table.rs
index 4b93517d..04a785b6 100644
--- a/crates/typst/src/model/table.rs
+++ b/crates/typst/src/model/table.rs
@@ -6,11 +6,11 @@ use ecow::{eco_format, EcoString};
use crate::diag::{bail, SourceResult, StrResult, Trace, Tracepoint};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Content, Fold, Packed, Show, Smart, StyleChain,
+ cast, elem, scope, Content, Fold, NativeElement, Packed, Show, Smart, StyleChain,
};
use crate::layout::{
- show_grid_cell, Abs, Alignment, Axes, Cell, CellGrid, Celled, Dir, Fragment,
- GridCell, GridFooter, GridHLine, GridHeader, GridLayouter, GridVLine, LayoutMultiple,
+ show_grid_cell, Abs, Alignment, Axes, BlockElem, Cell, CellGrid, Celled, Dir,
+ Fragment, GridCell, GridFooter, GridHLine, GridHeader, GridLayouter, GridVLine,
Length, LinePosition, OuterHAlignment, OuterVAlignment, Regions, Rel, ResolvableCell,
ResolvableGridChild, ResolvableGridItem, Sides, TrackSizings,
};
@@ -120,7 +120,7 @@ use crate::visualize::{Paint, Stroke};
/// [Robert], b, a, b,
/// )
/// ```
-#[elem(scope, LayoutMultiple, LocalName, Figurable)]
+#[elem(scope, Show, LocalName, Figurable)]
pub struct TableElem {
/// The column sizes. See the [grid documentation]($grid) for more
/// information on track sizing.
@@ -260,62 +260,65 @@ impl TableElem {
type TableFooter;
}
-impl LayoutMultiple for Packed<TableElem> {
- #[typst_macros::time(name = "table", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let inset = self.inset(styles);
- let align = self.align(styles);
- let columns = self.columns(styles);
- let rows = self.rows(styles);
- let column_gutter = self.column_gutter(styles);
- let row_gutter = self.row_gutter(styles);
- let fill = self.fill(styles);
- let stroke = self.stroke(styles);
-
- let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
- let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
- // Use trace to link back to the table when a specific cell errors
- let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
- let resolve_item = |item: &TableItem| item.to_resolvable(styles);
- let children = self.children().iter().map(|child| match child {
- TableChild::Header(header) => ResolvableGridChild::Header {
- repeat: header.repeat(styles),
- span: header.span(),
- items: header.children().iter().map(resolve_item),
- },
- TableChild::Footer(footer) => ResolvableGridChild::Footer {
- repeat: footer.repeat(styles),
- span: footer.span(),
- items: footer.children().iter().map(resolve_item),
- },
- TableChild::Item(item) => {
- ResolvableGridChild::Item(item.to_resolvable(styles))
- }
- });
- let grid = CellGrid::resolve(
- tracks,
- gutter,
- children,
- fill,
- align,
- &inset,
- &stroke,
- engine,
- styles,
- self.span(),
- )
- .trace(engine.world, tracepoint, self.span())?;
-
- let layouter = GridLayouter::new(&grid, regions, styles, self.span());
- layouter.layout(engine)
+impl Show for Packed<TableElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), layout_table).pack())
}
}
+/// Layout the table.
+#[typst_macros::time(span = elem.span())]
+fn layout_table(
+ elem: &Packed<TableElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let inset = elem.inset(styles);
+ let align = elem.align(styles);
+ let columns = elem.columns(styles);
+ let rows = elem.rows(styles);
+ let column_gutter = elem.column_gutter(styles);
+ let row_gutter = elem.row_gutter(styles);
+ let fill = elem.fill(styles);
+ let stroke = elem.stroke(styles);
+
+ let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
+ let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
+ // Use trace to link back to the table when a specific cell errors
+ let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
+ let resolve_item = |item: &TableItem| item.to_resolvable(styles);
+ let children = elem.children().iter().map(|child| match child {
+ TableChild::Header(header) => ResolvableGridChild::Header {
+ repeat: header.repeat(styles),
+ span: header.span(),
+ items: header.children().iter().map(resolve_item),
+ },
+ TableChild::Footer(footer) => ResolvableGridChild::Footer {
+ repeat: footer.repeat(styles),
+ span: footer.span(),
+ items: footer.children().iter().map(resolve_item),
+ },
+ TableChild::Item(item) => ResolvableGridChild::Item(item.to_resolvable(styles)),
+ });
+ let grid = CellGrid::resolve(
+ tracks,
+ gutter,
+ children,
+ fill,
+ align,
+ &inset,
+ &stroke,
+ engine,
+ styles,
+ elem.span(),
+ )
+ .trace(engine.world, tracepoint, elem.span())?;
+
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+ layouter.layout(engine)
+}
+
impl LocalName for Packed<TableElem> {
const KEY: &'static str = "table";
}
diff --git a/crates/typst/src/model/terms.rs b/crates/typst/src/model/terms.rs
index 84ae7730..da81c2ec 100644
--- a/crates/typst/src/model/terms.rs
+++ b/crates/typst/src/model/terms.rs
@@ -1,11 +1,10 @@
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, NativeElement, Packed, Smart, StyleChain,
+ cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
};
use crate::layout::{
- BlockElem, Dir, Em, Fragment, HElem, LayoutMultiple, Length, Regions, Sides, Spacing,
- StackChild, StackElem,
+ BlockElem, Dir, Em, HElem, Length, Sides, Spacing, StackChild, StackElem, VElem,
};
use crate::model::ParElem;
use crate::text::TextElem;
@@ -27,7 +26,7 @@ use crate::utils::Numeric;
/// # Syntax
/// This function also has dedicated syntax: Starting a line with a slash,
/// followed by a term, a colon and a description creates a term list item.
-#[elem(scope, title = "Term List", LayoutMultiple)]
+#[elem(scope, title = "Term List", Show)]
pub struct TermsElem {
/// If this is `{false}`, the items are spaced apart with
/// [term list spacing]($terms.spacing). If it is `{true}`, they use normal
@@ -109,14 +108,8 @@ impl TermsElem {
type TermItem;
}
-impl LayoutMultiple for Packed<TermsElem> {
- #[typst_macros::time(name = "terms", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
+impl Show for Packed<TermsElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let separator = self.separator(styles);
let indent = self.indent(styles);
let hanging_indent = self.hanging_indent(styles);
@@ -148,11 +141,18 @@ impl LayoutMultiple for Packed<TermsElem> {
padding.right = pad.into();
}
- StackElem::new(children)
+ let mut realized = StackElem::new(children)
.with_spacing(Some(gutter))
.pack()
- .padded(padding)
- .layout(engine, styles, regions)
+ .padded(padding);
+
+ if self.tight(styles) {
+ let leading = ParElem::leading_in(styles);
+ let spacing = VElem::list_attach(leading.into()).pack();
+ realized = spacing + realized;
+ }
+
+ Ok(realized)
}
}
diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs
index 600df1bb..d6b0032c 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, FlushElem, HElem,
- LayoutMultiple, LayoutSingle, PageElem, PagebreakElem, Parity, PlaceElem, VElem,
+ AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, FlushElem, HElem, InlineElem,
+ PageElem, PagebreakElem, Parity, PlaceElem, VElem,
};
use crate::math::{EquationElem, LayoutMath};
use crate::model::{
@@ -377,8 +377,14 @@ impl<'a> FlowBuilder<'a> {
let last_was_parbreak = self.1;
self.1 = false;
- if content.is::<VElem>()
- || content.is::<ColbreakElem>()
+ if let Some(elem) = content.to_packed::<VElem>() {
+ if !elem.attach(styles) || !last_was_parbreak {
+ self.0.push(content, styles);
+ }
+ return true;
+ }
+
+ if content.is::<ColbreakElem>()
|| content.is::<TagElem>()
|| content.is::<PlaceElem>()
|| content.is::<FlushElem>()
@@ -387,35 +393,17 @@ impl<'a> FlowBuilder<'a> {
return true;
}
- if content.can::<dyn LayoutSingle>()
- || content.can::<dyn LayoutMultiple>()
- || content.is::<ParElem>()
- {
- let is_tight_list = if let Some(elem) = content.to_packed::<ListElem>() {
- elem.tight(styles)
- } else if let Some(elem) = content.to_packed::<EnumElem>() {
- elem.tight(styles)
- } else if let Some(elem) = content.to_packed::<TermsElem>() {
- elem.tight(styles)
- } else {
- false
- };
-
- if !last_was_parbreak && is_tight_list {
- let leading = ParElem::leading_in(styles);
- let spacing = VElem::list_attach(leading.into());
- self.0.push(arenas.store(spacing.pack()), styles);
- }
-
- let (above, below) = if let Some(block) = content.to_packed::<BlockElem>() {
- (block.above(styles), block.below(styles))
- } else {
- (BlockElem::above_in(styles), BlockElem::below_in(styles))
- };
+ if let Some(elem) = content.to_packed::<BlockElem>() {
+ self.0.push(arenas.store(elem.above(styles).pack()), styles);
+ self.0.push(content, styles);
+ self.0.push(arenas.store(elem.below(styles).pack()), styles);
+ return true;
+ }
- self.0.push(arenas.store(above.pack()), styles);
+ if content.is::<ParElem>() {
+ self.0.push(arenas.store(BlockElem::above_in(styles).pack()), styles);
self.0.push(content, styles);
- self.0.push(arenas.store(below.pack()), styles);
+ self.0.push(arenas.store(BlockElem::below_in(styles).pack()), styles);
return true;
}
@@ -452,9 +440,7 @@ impl<'a> ParBuilder<'a> {
|| content.is::<HElem>()
|| content.is::<LinebreakElem>()
|| content.is::<SmartQuoteElem>()
- || content
- .to_packed::<EquationElem>()
- .is_some_and(|elem| !elem.block(styles))
+ || content.is::<InlineElem>()
|| content.is::<BoxElem>()
{
self.0.push(content, styles);
diff --git a/crates/typst/src/text/deco.rs b/crates/typst/src/text/deco.rs
index 45b7cf7b..ce819993 100644
--- a/crates/typst/src/text/deco.rs
+++ b/crates/typst/src/text/deco.rs
@@ -423,7 +423,7 @@ pub(crate) fn decorate(
{
let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge);
let size = Size::new(width + 2.0 * deco.extent, top - bottom);
- let rects = styled_rect(size, *radius, fill.clone(), stroke.clone());
+ let rects = styled_rect(size, radius, fill.clone(), stroke);
let origin = Point::new(pos.x - deco.extent, pos.y - top - shift);
frame.prepend_multiple(
rects
diff --git a/crates/typst/src/text/raw.rs b/crates/typst/src/text/raw.rs
index f20dfce4..6f900927 100644
--- a/crates/typst/src/text/raw.rs
+++ b/crates/typst/src/text/raw.rs
@@ -16,7 +16,7 @@ use crate::foundations::{
cast, elem, scope, Args, Array, Bytes, Content, Fold, NativeElement, Packed,
PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, Value,
};
-use crate::layout::{BlockElem, Em, HAlignment};
+use crate::layout::{BlockChild, BlockElem, Em, HAlignment};
use crate::model::{Figurable, ParElem};
use crate::syntax::{split_newlines, LinkedNode, Span, Spanned};
use crate::text::{
@@ -444,8 +444,10 @@ impl Show for Packed<RawElem> {
if self.block(styles) {
// Align the text before inserting it into the block.
realized = realized.aligned(self.align(styles).into());
- realized =
- BlockElem::new().with_body(Some(realized)).pack().spanned(self.span());
+ realized = BlockElem::new()
+ .with_body(Some(BlockChild::Content(realized)))
+ .pack()
+ .spanned(self.span());
}
Ok(realized)
diff --git a/crates/typst/src/visualize/image/mod.rs b/crates/typst/src/visualize/image/mod.rs
index a5151e6e..91922d1b 100644
--- a/crates/typst/src/visualize/image/mod.rs
+++ b/crates/typst/src/visualize/image/mod.rs
@@ -16,12 +16,12 @@ use ecow::EcoString;
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, func, scope, Bytes, Cast, Content, NativeElement, Packed, Resolve, Smart,
+ cast, elem, func, scope, Bytes, Cast, Content, NativeElement, Packed, Show, Smart,
StyleChain,
};
use crate::layout::{
- Abs, Axes, FixedAlignment, Frame, FrameItem, LayoutSingle, Length, Point, Regions,
- Rel, Size,
+ Abs, Axes, BlockElem, FixedAlignment, Frame, FrameItem, Length, Point, Region, Rel,
+ Size,
};
use crate::loading::Readable;
use crate::model::Figurable;
@@ -51,7 +51,7 @@ use crate::World;
/// ```
///
/// [gh-svg]: https://github.com/typst/typst/issues?q=is%3Aopen+is%3Aissue+label%3Asvg
-#[elem(scope, LayoutSingle, LocalName, Figurable)]
+#[elem(scope, Show, LocalName, Figurable)]
pub struct ImageElem {
/// Path to an image file.
#[required]
@@ -154,112 +154,12 @@ impl ImageElem {
}
}
-impl LayoutSingle for Packed<ImageElem> {
- #[typst_macros::time(name = "image", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- // Take the format that was explicitly defined, or parse the extension,
- // or try to detect the format.
- let data = self.data();
- let format = match self.format(styles) {
- Smart::Custom(v) => v,
- Smart::Auto => {
- let ext = std::path::Path::new(self.path().as_str())
- .extension()
- .and_then(OsStr::to_str)
- .unwrap_or_default()
- .to_lowercase();
-
- match ext.as_str() {
- "png" => ImageFormat::Raster(RasterFormat::Png),
- "jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg),
- "gif" => ImageFormat::Raster(RasterFormat::Gif),
- "svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg),
- _ => match &data {
- Readable::Str(_) => ImageFormat::Vector(VectorFormat::Svg),
- Readable::Bytes(bytes) => match RasterFormat::detect(bytes) {
- Some(f) => ImageFormat::Raster(f),
- None => bail!(self.span(), "unknown image format"),
- },
- },
- }
- }
- };
-
- let image = Image::with_fonts(
- data.clone().into(),
- format,
- self.alt(styles),
- engine.world,
- &families(styles).map(|s| s.into()).collect::<Vec<_>>(),
- )
- .at(self.span())?;
-
- let sizing = Axes::new(self.width(styles), self.height(styles));
- let region = sizing
- .zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r)))
- .unwrap_or(regions.base());
-
- let expand = sizing.as_ref().map(Smart::is_custom) | regions.expand;
- let region_ratio = region.x / region.y;
-
- // Find out whether the image is wider or taller than the target size.
- let pxw = image.width();
- let pxh = image.height();
- let px_ratio = pxw / pxh;
- let wide = px_ratio > region_ratio;
-
- // The space into which the image will be placed according to its fit.
- let target = if expand.x && expand.y {
- // If both width and height are forced, take them.
- region
- } else if expand.x {
- // If just width is forced, take it.
- Size::new(region.x, region.y.min(region.x / px_ratio))
- } else if expand.y {
- // If just height is forced, take it.
- Size::new(region.x.min(region.y * px_ratio), region.y)
- } else {
- // If neither is forced, take the natural image size at the image's
- // DPI bounded by the available space.
- let dpi = image.dpi().unwrap_or(Image::DEFAULT_DPI);
- let natural = Axes::new(pxw, pxh).map(|v| Abs::inches(v / dpi));
- Size::new(
- natural.x.min(region.x).min(region.y * px_ratio),
- natural.y.min(region.y).min(region.x / px_ratio),
- )
- };
-
- // Compute the actual size of the fitted image.
- let fit = self.fit(styles);
- let fitted = match fit {
- ImageFit::Cover | ImageFit::Contain => {
- if wide == (fit == ImageFit::Contain) {
- Size::new(target.x, target.x / px_ratio)
- } else {
- Size::new(target.y * px_ratio, target.y)
- }
- }
- ImageFit::Stretch => target,
- };
-
- // First, place the image in a frame of exactly its size and then resize
- // the frame to the target size, center aligning the image in the
- // process.
- let mut frame = Frame::soft(fitted);
- frame.push(Point::zero(), FrameItem::Image(image, fitted, self.span()));
- frame.resize(target, Axes::splat(FixedAlignment::Center));
-
- // Create a clipping group if only part of the image should be visible.
- if fit == ImageFit::Cover && !target.fits(fitted) {
- frame.clip(Path::rect(frame.size()));
- }
-
- Ok(frame)
+impl Show for Packed<ImageElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), layout_image)
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack())
}
}
@@ -269,6 +169,117 @@ impl LocalName for Packed<ImageElem> {
impl Figurable for Packed<ImageElem> {}
+/// Layout the image.
+#[typst_macros::time(span = elem.span())]
+fn layout_image(
+ elem: &Packed<ImageElem>,
+ engine: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let span = elem.span();
+
+ // Take the format that was explicitly defined, or parse the extension,
+ // or try to detect the format.
+ let data = elem.data();
+ let format = match elem.format(styles) {
+ Smart::Custom(v) => v,
+ Smart::Auto => determine_format(elem.path().as_str(), data).at(span)?,
+ };
+
+ // Construct the image itself.
+ let image = Image::with_fonts(
+ data.clone().into(),
+ format,
+ elem.alt(styles),
+ engine.world,
+ &families(styles).map(|s| s.into()).collect::<Vec<_>>(),
+ )
+ .at(span)?;
+
+ // Determine the image's pixel aspect ratio.
+ let pxw = image.width();
+ let pxh = image.height();
+ let px_ratio = pxw / pxh;
+
+ // Determine the region's aspect ratio.
+ let region_ratio = region.size.x / region.size.y;
+
+ // Find out whether the image is wider or taller than the region.
+ let wide = px_ratio > region_ratio;
+
+ // The space into which the image will be placed according to its fit.
+ let target = if region.expand.x && region.expand.y {
+ // If both width and height are forced, take them.
+ region.size
+ } else if region.expand.x {
+ // If just width is forced, take it.
+ Size::new(region.size.x, region.size.y.min(region.size.x / px_ratio))
+ } else if region.expand.y {
+ // If just height is forced, take it.
+ Size::new(region.size.x.min(region.size.y * px_ratio), region.size.y)
+ } else {
+ // If neither is forced, take the natural image size at the image's
+ // DPI bounded by the available space.
+ let dpi = image.dpi().unwrap_or(Image::DEFAULT_DPI);
+ let natural = Axes::new(pxw, pxh).map(|v| Abs::inches(v / dpi));
+ Size::new(
+ natural.x.min(region.size.x).min(region.size.y * px_ratio),
+ natural.y.min(region.size.y).min(region.size.x / px_ratio),
+ )
+ };
+
+ // Compute the actual size of the fitted image.
+ let fit = elem.fit(styles);
+ let fitted = match fit {
+ ImageFit::Cover | ImageFit::Contain => {
+ if wide == (fit == ImageFit::Contain) {
+ Size::new(target.x, target.x / px_ratio)
+ } else {
+ Size::new(target.y * px_ratio, target.y)
+ }
+ }
+ ImageFit::Stretch => target,
+ };
+
+ // First, place the image in a frame of exactly its size and then resize
+ // the frame to the target size, center aligning the image in the
+ // process.
+ let mut frame = Frame::soft(fitted);
+ frame.push(Point::zero(), FrameItem::Image(image, fitted, span));
+ frame.resize(target, Axes::splat(FixedAlignment::Center));
+
+ // Create a clipping group if only part of the image should be visible.
+ if fit == ImageFit::Cover && !target.fits(fitted) {
+ frame.clip(Path::rect(frame.size()));
+ }
+
+ Ok(frame)
+}
+
+/// Determine the image format based on path and data.
+fn determine_format(path: &str, data: &Readable) -> StrResult<ImageFormat> {
+ let ext = std::path::Path::new(path)
+ .extension()
+ .and_then(OsStr::to_str)
+ .unwrap_or_default()
+ .to_lowercase();
+
+ Ok(match ext.as_str() {
+ "png" => ImageFormat::Raster(RasterFormat::Png),
+ "jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg),
+ "gif" => ImageFormat::Raster(RasterFormat::Gif),
+ "svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg),
+ _ => match &data {
+ Readable::Str(_) => ImageFormat::Vector(VectorFormat::Svg),
+ Readable::Bytes(bytes) => match RasterFormat::detect(bytes) {
+ Some(f) => ImageFormat::Raster(f),
+ None => bail!("unknown image format"),
+ },
+ },
+ })
+}
+
/// How an image should adjust itself to a given area,
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum ImageFit {
diff --git a/crates/typst/src/visualize/line.rs b/crates/typst/src/visualize/line.rs
index d84ea62f..0d5cb4b7 100644
--- a/crates/typst/src/visualize/line.rs
+++ b/crates/typst/src/visualize/line.rs
@@ -1,8 +1,8 @@
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
-use crate::foundations::{elem, Packed, StyleChain};
+use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
use crate::layout::{
- Abs, Angle, Axes, Frame, FrameItem, LayoutSingle, Length, Regions, Rel, Size,
+ Abs, Angle, Axes, BlockElem, Frame, FrameItem, Length, Region, Rel, Size,
};
use crate::utils::Numeric;
use crate::visualize::{Geometry, Stroke};
@@ -20,7 +20,7 @@ use crate::visualize::{Geometry, Stroke};
/// stroke: 2pt + maroon,
/// )
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct LineElem {
/// The start point of the line.
///
@@ -58,37 +58,39 @@ pub struct LineElem {
pub stroke: Stroke,
}
-impl LayoutSingle for Packed<LineElem> {
- #[typst_macros::time(name = "line", span = self.span())]
- fn layout(
- &self,
- _: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- let resolve =
- |axes: Axes<Rel<Abs>>| axes.zip_map(regions.base(), Rel::relative_to);
- let start = resolve(self.start(styles));
- let delta =
- self.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| {
- let length = self.length(styles);
- let angle = self.angle(styles);
- let x = angle.cos() * length;
- let y = angle.sin() * length;
- resolve(Axes::new(x, y))
- });
+impl Show for Packed<LineElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), layout_line).pack())
+ }
+}
- let stroke = self.stroke(styles).unwrap_or_default();
- let size = start.max(start + delta).max(Size::zero());
- let target = regions.expand.select(regions.size, size);
+/// Layout the line.
+#[typst_macros::time(span = elem.span())]
+fn layout_line(
+ elem: &Packed<LineElem>,
+ _: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let resolve = |axes: Axes<Rel<Abs>>| axes.zip_map(region.size, Rel::relative_to);
+ let start = resolve(elem.start(styles));
+ let delta = elem.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| {
+ let length = elem.length(styles);
+ let angle = elem.angle(styles);
+ let x = angle.cos() * length;
+ let y = angle.sin() * length;
+ resolve(Axes::new(x, y))
+ });
- if !target.is_finite() {
- bail!(self.span(), "cannot create line with infinite length");
- }
+ let stroke = elem.stroke(styles).unwrap_or_default();
+ let size = start.max(start + delta).max(Size::zero());
- 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()));
- Ok(frame)
+ if !size.is_finite() {
+ bail!(elem.span(), "cannot create line with infinite length");
}
+
+ let mut frame = Frame::soft(size);
+ let shape = Geometry::Line(delta.to_point()).stroked(stroke);
+ frame.push(start.to_point(), FrameItem::Shape(shape, elem.span()));
+ Ok(frame)
}
diff --git a/crates/typst/src/visualize/path.rs b/crates/typst/src/visualize/path.rs
index 170a1386..0005618e 100644
--- a/crates/typst/src/visualize/path.rs
+++ b/crates/typst/src/visualize/path.rs
@@ -3,10 +3,11 @@ use kurbo::{CubicBez, ParamCurveExtrema};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- array, cast, elem, Array, Packed, Reflect, Resolve, Smart, StyleChain,
+ array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Resolve, Show,
+ Smart, StyleChain,
};
use crate::layout::{
- Abs, Axes, Frame, FrameItem, LayoutSingle, Length, Point, Regions, Rel, Size,
+ Abs, Axes, BlockElem, Frame, FrameItem, Length, Point, Region, Rel, Size,
};
use crate::visualize::{FixedStroke, Geometry, Paint, Shape, Stroke};
@@ -25,7 +26,7 @@ use PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
/// ((50%, 0pt), (40pt, 0pt)),
/// )
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct PathElem {
/// How to fill the path.
///
@@ -69,86 +70,89 @@ pub struct PathElem {
pub vertices: Vec<PathVertex>,
}
-impl LayoutSingle for Packed<PathElem> {
- #[typst_macros::time(name = "path", span = self.span())]
- fn layout(
- &self,
- _: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- let resolve = |axes: Axes<Rel<Length>>| {
- axes.resolve(styles)
- .zip_map(regions.base(), Rel::relative_to)
- .to_point()
- };
-
- let vertices = self.vertices();
- let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
-
- let mut size = Size::zero();
- if points.is_empty() {
- return Ok(Frame::soft(size));
- }
+impl Show for Packed<PathElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), layout_path).pack())
+ }
+}
- // Only create a path if there are more than zero points.
- // Construct a closed path given all points.
- let mut path = Path::new();
- path.move_to(points[0]);
-
- let mut add_cubic =
- |from_point: Point, to_point: Point, from: PathVertex, to: PathVertex| {
- let from_control_point = resolve(from.control_point_from()) + from_point;
- let to_control_point = resolve(to.control_point_to()) + to_point;
- path.cubic_to(from_control_point, to_control_point, to_point);
-
- let p0 = kurbo::Point::new(from_point.x.to_raw(), from_point.y.to_raw());
- let p1 = kurbo::Point::new(
- from_control_point.x.to_raw(),
- from_control_point.y.to_raw(),
- );
- let p2 = kurbo::Point::new(
- to_control_point.x.to_raw(),
- to_control_point.y.to_raw(),
- );
- let p3 = kurbo::Point::new(to_point.x.to_raw(), to_point.y.to_raw());
- let extrema = CubicBez::new(p0, p1, p2, p3).bounding_box();
- size.x.set_max(Abs::raw(extrema.x1));
- size.y.set_max(Abs::raw(extrema.y1));
- };
-
- for (vertex_window, point_window) in vertices.windows(2).zip(points.windows(2)) {
- let from = vertex_window[0];
- let to = vertex_window[1];
- let from_point = point_window[0];
- let to_point = point_window[1];
-
- add_cubic(from_point, to_point, from, to);
- }
+/// Layout the path.
+#[typst_macros::time(span = elem.span())]
+fn layout_path(
+ elem: &Packed<PathElem>,
+ _: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let resolve = |axes: Axes<Rel<Length>>| {
+ axes.resolve(styles).zip_map(region.size, Rel::relative_to).to_point()
+ };
+
+ let vertices = elem.vertices();
+ let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
+
+ let mut size = Size::zero();
+ if points.is_empty() {
+ return Ok(Frame::soft(size));
+ }
- if self.closed(styles) {
- let from = *vertices.last().unwrap(); // We checked that we have at least one element.
- let to = vertices[0];
- let from_point = *points.last().unwrap();
- let to_point = points[0];
+ // Only create a path if there are more than zero points.
+ // Construct a closed path given all points.
+ let mut path = Path::new();
+ path.move_to(points[0]);
+
+ let mut add_cubic = |from_point: Point,
+ to_point: Point,
+ from: PathVertex,
+ to: PathVertex| {
+ let from_control_point = resolve(from.control_point_from()) + from_point;
+ let to_control_point = resolve(to.control_point_to()) + to_point;
+ path.cubic_to(from_control_point, to_control_point, to_point);
+
+ let p0 = kurbo::Point::new(from_point.x.to_raw(), from_point.y.to_raw());
+ let p1 = kurbo::Point::new(
+ from_control_point.x.to_raw(),
+ from_control_point.y.to_raw(),
+ );
+ let p2 =
+ kurbo::Point::new(to_control_point.x.to_raw(), to_control_point.y.to_raw());
+ let p3 = kurbo::Point::new(to_point.x.to_raw(), to_point.y.to_raw());
+ let extrema = CubicBez::new(p0, p1, p2, p3).bounding_box();
+ size.x.set_max(Abs::raw(extrema.x1));
+ size.y.set_max(Abs::raw(extrema.y1));
+ };
+
+ for (vertex_window, point_window) in vertices.windows(2).zip(points.windows(2)) {
+ let from = vertex_window[0];
+ let to = vertex_window[1];
+ let from_point = point_window[0];
+ let to_point = point_window[1];
+
+ add_cubic(from_point, to_point, from, to);
+ }
- add_cubic(from_point, to_point, from, to);
- path.close_path();
- }
+ if elem.closed(styles) {
+ let from = *vertices.last().unwrap(); // We checked that we have at least one element.
+ let to = vertices[0];
+ let from_point = *points.last().unwrap();
+ let to_point = points[0];
- // Prepare fill and stroke.
- let fill = self.fill(styles);
- let stroke = match self.stroke(styles) {
- Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
- Smart::Auto => None,
- Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
- };
-
- let mut frame = Frame::soft(size);
- let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
- frame.push(Point::zero(), FrameItem::Shape(shape, self.span()));
- Ok(frame)
+ add_cubic(from_point, to_point, from, to);
+ path.close_path();
}
+
+ // Prepare fill and stroke.
+ let fill = elem.fill(styles);
+ let stroke = match elem.stroke(styles) {
+ Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
+ Smart::Auto => None,
+ Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
+ };
+
+ let mut frame = Frame::soft(size);
+ let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
+ frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
+ Ok(frame)
}
/// A component used for path creation.
diff --git a/crates/typst/src/visualize/pattern.rs b/crates/typst/src/visualize/pattern.rs
index 0f12b5d5..e467d789 100644
--- a/crates/typst/src/visualize/pattern.rs
+++ b/crates/typst/src/visualize/pattern.rs
@@ -6,7 +6,7 @@ use ecow::{eco_format, EcoString};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, repr, scope, ty, Content, Smart, StyleChain};
-use crate::layout::{Abs, Axes, Frame, LayoutMultiple, Length, Regions, Size};
+use crate::layout::{Abs, Axes, Frame, Length, Regions, Size};
use crate::syntax::{Span, Spanned};
use crate::utils::{LazyHash, Numeric};
use crate::visualize::RelativeTo;
diff --git a/crates/typst/src/visualize/polygon.rs b/crates/typst/src/visualize/polygon.rs
index 21db30a8..305f3cb1 100644
--- a/crates/typst/src/visualize/polygon.rs
+++ b/crates/typst/src/visualize/polygon.rs
@@ -3,11 +3,9 @@ use std::f64::consts::PI;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, func, scope, Content, NativeElement, Packed, Resolve, Smart, StyleChain,
-};
-use crate::layout::{
- Axes, Em, Frame, FrameItem, LayoutSingle, Length, Point, Regions, Rel,
+ elem, func, scope, Content, NativeElement, Packed, Resolve, Show, Smart, StyleChain,
};
+use crate::layout::{Axes, BlockElem, Em, Frame, FrameItem, Length, Point, Region, Rel};
use crate::syntax::Span;
use crate::utils::Numeric;
use crate::visualize::{FixedStroke, Geometry, Paint, Path, Shape, Stroke};
@@ -27,7 +25,7 @@ use crate::visualize::{FixedStroke, Geometry, Paint, Path, Shape, Stroke};
/// (0%, 2cm),
/// )
/// ```
-#[elem(scope, LayoutSingle)]
+#[elem(scope, Show)]
pub struct PolygonElem {
/// How to fill the polygon.
///
@@ -125,52 +123,55 @@ impl PolygonElem {
}
}
-impl LayoutSingle for Packed<PolygonElem> {
- #[typst_macros::time(name = "polygon", span = self.span())]
- fn layout(
- &self,
- _: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- let points: Vec<Point> = self
- .vertices()
- .iter()
- .map(|c| {
- c.resolve(styles).zip_map(regions.base(), Rel::relative_to).to_point()
- })
- .collect();
-
- let size = points.iter().fold(Point::zero(), |max, c| c.max(max)).to_size();
- if !size.is_finite() {
- bail!(self.span(), "cannot create polygon with infinite size");
- }
-
- let mut frame = Frame::hard(size);
+impl Show for Packed<PolygonElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), layout_polygon).pack())
+ }
+}
- // Only create a path if there are more than zero points.
- if points.is_empty() {
- return Ok(frame);
- }
+/// Layout the polygon.
+#[typst_macros::time(span = elem.span())]
+fn layout_polygon(
+ elem: &Packed<PolygonElem>,
+ _: &mut Engine,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let points: Vec<Point> = elem
+ .vertices()
+ .iter()
+ .map(|c| c.resolve(styles).zip_map(region.size, Rel::relative_to).to_point())
+ .collect();
+
+ let size = points.iter().fold(Point::zero(), |max, c| c.max(max)).to_size();
+ if !size.is_finite() {
+ bail!(elem.span(), "cannot create polygon with infinite size");
+ }
- // Prepare fill and stroke.
- let fill = self.fill(styles);
- let stroke = match self.stroke(styles) {
- Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
- Smart::Auto => None,
- Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
- };
+ let mut frame = Frame::hard(size);
- // Construct a closed path given all points.
- let mut path = Path::new();
- path.move_to(points[0]);
- for &point in &points[1..] {
- path.line_to(point);
- }
- path.close_path();
+ // Only create a path if there are more than zero points.
+ if points.is_empty() {
+ return Ok(frame);
+ }
- let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
- frame.push(Point::zero(), FrameItem::Shape(shape, self.span()));
- Ok(frame)
+ // Prepare fill and stroke.
+ let fill = elem.fill(styles);
+ let stroke = match elem.stroke(styles) {
+ Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
+ Smart::Auto => None,
+ Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
+ };
+
+ // Construct a closed path given all points.
+ let mut path = Path::new();
+ path.move_to(points[0]);
+ for &point in &points[1..] {
+ path.line_to(point);
}
+ path.close_path();
+
+ let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
+ frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
+ Ok(frame)
}
diff --git a/crates/typst/src/visualize/shape.rs b/crates/typst/src/visualize/shape.rs
index 88504121..7404763e 100644
--- a/crates/typst/src/visualize/shape.rs
+++ b/crates/typst/src/visualize/shape.rs
@@ -2,10 +2,10 @@ use std::f64::consts::SQRT_2;
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Resolve, Smart, StyleChain};
+use crate::foundations::{elem, Content, NativeElement, Packed, Show, Smart, StyleChain};
use crate::layout::{
- Abs, Axes, Corner, Corners, Frame, FrameItem, LayoutMultiple, LayoutSingle, Length,
- Point, Ratio, Regions, Rel, Sides, Size,
+ Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point, Ratio,
+ Region, Regions, Rel, Sides, Size,
};
use crate::syntax::Span;
use crate::utils::Get;
@@ -24,7 +24,7 @@ use crate::visualize::{FixedStroke, Paint, Path, Stroke};
/// to fit the content.
/// ]
/// ```
-#[elem(title = "Rectangle", LayoutSingle)]
+#[elem(title = "Rectangle", Show)]
pub struct RectElem {
/// The rectangle's width, relative to its parent container.
pub width: Smart<Rel<Length>>,
@@ -128,31 +128,30 @@ pub struct RectElem {
/// When this is omitted, the rectangle takes on a default size of at most
/// `{45pt}` by `{30pt}`.
#[positional]
+ #[borrowed]
pub body: Option<Content>,
}
-impl LayoutSingle for Packed<RectElem> {
- #[typst_macros::time(name = "rect", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- layout(
- engine,
- styles,
- regions,
- ShapeKind::Rect,
- &self.body(styles),
- Axes::new(self.width(styles), self.height(styles)),
- self.fill(styles),
- self.stroke(styles),
- self.inset(styles),
- self.outset(styles),
- self.radius(styles),
- self.span(),
- )
+impl Show for Packed<RectElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, region| {
+ layout_shape(
+ engine,
+ styles,
+ region,
+ ShapeKind::Rect,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles),
+ elem.inset(styles),
+ elem.outset(styles),
+ elem.radius(styles),
+ elem.span(),
+ )
+ })
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack())
}
}
@@ -169,7 +168,7 @@ impl LayoutSingle for Packed<RectElem> {
/// sized to fit.
/// ]
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct SquareElem {
/// The square's side length. This is mutually exclusive with `width` and
/// `height`.
@@ -234,31 +233,30 @@ pub struct SquareElem {
/// When this is omitted, the square takes on a default size of at most
/// `{30pt}`.
#[positional]
+ #[borrowed]
pub body: Option<Content>,
}
-impl LayoutSingle for Packed<SquareElem> {
- #[typst_macros::time(name = "square", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- layout(
- engine,
- styles,
- regions,
- ShapeKind::Square,
- &self.body(styles),
- Axes::new(self.width(styles), self.height(styles)),
- self.fill(styles),
- self.stroke(styles),
- self.inset(styles),
- self.outset(styles),
- self.radius(styles),
- self.span(),
- )
+impl Show for Packed<SquareElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, regions| {
+ layout_shape(
+ engine,
+ styles,
+ regions,
+ ShapeKind::Square,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles),
+ elem.inset(styles),
+ elem.outset(styles),
+ elem.radius(styles),
+ elem.span(),
+ )
+ })
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack())
}
}
@@ -276,7 +274,7 @@ impl LayoutSingle for Packed<SquareElem> {
/// to fit the content.
/// ]
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct EllipseElem {
/// The ellipse's width, relative to its parent container.
pub width: Smart<Rel<Length>>,
@@ -312,31 +310,30 @@ pub struct EllipseElem {
/// When this is omitted, the ellipse takes on a default size of at most
/// `{45pt}` by `{30pt}`.
#[positional]
+ #[borrowed]
pub body: Option<Content>,
}
-impl LayoutSingle for Packed<EllipseElem> {
- #[typst_macros::time(name = "ellipse", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- layout(
- engine,
- styles,
- regions,
- ShapeKind::Ellipse,
- &self.body(styles),
- Axes::new(self.width(styles), self.height(styles)),
- self.fill(styles),
- self.stroke(styles).map(|s| Sides::splat(Some(s))),
- self.inset(styles),
- self.outset(styles),
- Corners::splat(None),
- self.span(),
- )
+impl Show for Packed<EllipseElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, regions| {
+ layout_shape(
+ engine,
+ styles,
+ regions,
+ ShapeKind::Ellipse,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles).map(|s| Sides::splat(Some(s))),
+ elem.inset(styles),
+ elem.outset(styles),
+ Corners::splat(None),
+ elem.span(),
+ )
+ })
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack())
}
}
@@ -354,7 +351,7 @@ impl LayoutSingle for Packed<EllipseElem> {
/// sized to fit.
/// ]
/// ```
-#[elem(LayoutSingle)]
+#[elem(Show)]
pub struct CircleElem {
/// The circle's radius. This is mutually exclusive with `width` and
/// `height`.
@@ -415,43 +412,42 @@ pub struct CircleElem {
/// The content to place into the circle. The circle expands to fit this
/// content, keeping the 1-1 aspect ratio.
#[positional]
+ #[borrowed]
pub body: Option<Content>,
}
-impl LayoutSingle for Packed<CircleElem> {
- #[typst_macros::time(name = "circle", span = self.span())]
- fn layout(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Frame> {
- layout(
- engine,
- styles,
- regions,
- ShapeKind::Circle,
- &self.body(styles),
- Axes::new(self.width(styles), self.height(styles)),
- self.fill(styles),
- self.stroke(styles).map(|s| Sides::splat(Some(s))),
- self.inset(styles),
- self.outset(styles),
- Corners::splat(None),
- self.span(),
- )
+impl Show for Packed<CircleElem> {
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, regions| {
+ layout_shape(
+ engine,
+ styles,
+ regions,
+ ShapeKind::Circle,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles).map(|s| Sides::splat(Some(s))),
+ elem.inset(styles),
+ elem.outset(styles),
+ Corners::splat(None),
+ elem.span(),
+ )
+ })
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack())
}
}
/// Layout a shape.
+#[typst_macros::time(span = span)]
#[allow(clippy::too_many_arguments)]
-fn layout(
+fn layout_shape(
engine: &mut Engine,
styles: StyleChain,
- regions: Regions,
+ region: Region,
kind: ShapeKind,
body: &Option<Content>,
- sizing: Axes<Smart<Rel<Length>>>,
fill: Option<Paint>,
stroke: Smart<Sides<Option<Option<Stroke<Abs>>>>>,
inset: Sides<Option<Rel<Abs>>>,
@@ -459,47 +455,41 @@ fn layout(
radius: Corners<Option<Rel<Abs>>>,
span: Span,
) -> SourceResult<Frame> {
- let resolved = sizing
- .zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r)));
-
let mut frame;
- let mut inset = inset.unwrap_or_default();
-
if let Some(child) = body {
- let region = resolved.unwrap_or(regions.base());
-
+ let mut inset = inset.unwrap_or_default();
if kind.is_round() {
- inset = inset.map(|side| side + Ratio::new(0.5 - SQRT_2 / 4.0));
+ // Apply extra inset to round shapes.
+ inset = inset.map(|v| v + Ratio::new(0.5 - SQRT_2 / 4.0));
}
+ let has_inset = !inset.is_zero();
- // Pad the child.
- let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
- let expand = sizing.as_ref().map(Smart::is_custom);
- let pod = Regions::one(region, expand);
- frame = child.layout(engine, styles, pod)?.into_frame();
+ // Take the inset, if any, into account.
+ let mut pod = region;
+ if has_inset {
+ pod.size = crate::layout::shrink(region.size, &inset);
+ }
- // Enforce correct size.
- *frame.size_mut() = expand.select(region, frame.size());
+ // Layout the child.
+ frame = child.layout(engine, styles, pod.into_regions())?.into_frame();
- // Relayout with full expansion into square region to make sure
- // the result is really a square or circle.
+ // If the child is a square or circle, relayout with full expansion into
+ // square region to make sure the result is really quadratic.
if kind.is_quadratic() {
- frame.set_size(Size::splat(frame.size().max_by_side()));
- let length = frame.size().max_by_side().min(region.min_by_side());
- let pod = Regions::one(Size::splat(length), Axes::splat(true));
- frame = child.layout(engine, styles, pod)?.into_frame();
+ let length = frame.size().max_by_side().min(pod.size.min_by_side());
+ let quad_pod = Regions::one(Size::splat(length), Axes::splat(true));
+ frame = child.layout(engine, styles, quad_pod)?.into_frame();
}
- // Enforce correct size again.
- *frame.size_mut() = expand.select(region, frame.size());
- if kind.is_quadratic() {
- frame.set_size(Size::splat(frame.size().max_by_side()));
+ // Apply the inset.
+ if has_inset {
+ crate::layout::grow(&mut frame, &inset);
}
} else {
// The default size that a shape takes on if it has no child and
// enough space.
let default = Size::new(Abs::pt(45.0), Abs::pt(30.0));
- let mut size = resolved.unwrap_or(default.min(regions.base()));
+ let mut size = region.expand.select(region.size, default.min(region.size));
if kind.is_quadratic() {
size = Size::splat(size.min_by_side());
}
@@ -526,9 +516,9 @@ fn layout(
} else {
frame.fill_and_stroke(
fill,
- stroke,
- outset.unwrap_or_default(),
- radius.unwrap_or_default(),
+ &stroke,
+ &outset.unwrap_or_default(),
+ &radius.unwrap_or_default(),
span,
);
}
@@ -633,7 +623,7 @@ pub(crate) fn ellipse(
/// Creates a new rectangle as a path.
pub(crate) fn clip_rect(
size: Size,
- radius: Corners<Rel<Abs>>,
+ radius: &Corners<Rel<Abs>>,
stroke: &Sides<Option<FixedStroke>>,
) -> Path {
let stroke_widths = stroke
@@ -644,8 +634,7 @@ pub(crate) fn clip_rect(
+ stroke_widths.iter().cloned().min().unwrap_or(Abs::zero());
let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius));
-
- let corners = corners_control_points(size, radius, stroke, stroke_widths);
+ let corners = corners_control_points(size, &radius, stroke, &stroke_widths);
let mut path = Path::new();
if corners.top_left.arc_inner() {
@@ -674,12 +663,12 @@ pub(crate) fn clip_rect(
/// - use fill for sides for best looks
pub(crate) fn styled_rect(
size: Size,
- radius: Corners<Rel<Abs>>,
+ radius: &Corners<Rel<Abs>>,
fill: Option<Paint>,
- stroke: Sides<Option<FixedStroke>>,
+ stroke: &Sides<Option<FixedStroke>>,
) -> Vec<Shape> {
if stroke.is_uniform() && radius.iter().cloned().all(Rel::is_zero) {
- simple_rect(size, fill, stroke.top)
+ simple_rect(size, fill, stroke.top.clone())
} else {
segmented_rect(size, radius, fill, stroke)
}
@@ -696,9 +685,9 @@ fn simple_rect(
fn corners_control_points(
size: Size,
- radius: Corners<Abs>,
+ radius: &Corners<Abs>,
strokes: &Sides<Option<FixedStroke>>,
- stroke_widths: Sides<Abs>,
+ stroke_widths: &Sides<Abs>,
) -> Corners<ControlPoints> {
Corners {
top_left: Corner::TopLeft,
@@ -726,9 +715,9 @@ fn corners_control_points(
/// Use stroke and fill for the rectangle
fn segmented_rect(
size: Size,
- radius: Corners<Rel<Abs>>,
+ radius: &Corners<Rel<Abs>>,
fill: Option<Paint>,
- strokes: Sides<Option<FixedStroke>>,
+ strokes: &Sides<Option<FixedStroke>>,
) -> Vec<Shape> {
let mut res = vec![];
let stroke_widths = strokes
@@ -739,8 +728,7 @@ fn segmented_rect(
+ stroke_widths.iter().cloned().min().unwrap_or(Abs::zero());
let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius));
-
- let corners = corners_control_points(size, radius, &strokes, stroke_widths);
+ let corners = corners_control_points(size, &radius, strokes, &stroke_widths);
// insert stroked sides below filled sides
let mut stroke_insert = 0;
@@ -786,10 +774,7 @@ fn segmented_rect(
let start = last;
let end = current;
last = current;
- let stroke = match strokes.get_ref(start.side_cw()) {
- None => continue,
- Some(stroke) => stroke.clone(),
- };
+ let Some(stroke) = strokes.get_ref(start.side_cw()) else { continue };
let (shape, ontop) = segment(start, end, &corners, stroke);
if ontop {
res.push(shape);
@@ -798,7 +783,7 @@ fn segmented_rect(
stroke_insert += 1;
}
}
- } else if let Some(stroke) = strokes.top {
+ } else if let Some(stroke) = &strokes.top {
// single segment
let (shape, _) = segment(Corner::TopLeft, Corner::TopLeft, &corners, stroke);
res.push(shape);
@@ -848,7 +833,7 @@ fn segment(
start: Corner,
end: Corner,
corners: &Corners<ControlPoints>,
- stroke: FixedStroke,
+ stroke: &FixedStroke,
) -> (Shape, bool) {
fn fill_corner(corner: &ControlPoints) -> bool {
corner.stroke_before != corner.stroke_after
@@ -883,12 +868,12 @@ fn segment(
.unwrap_or(true);
let use_fill = solid && fill_corners(start, end, corners);
-
let shape = if use_fill {
fill_segment(start, end, corners, stroke)
} else {
- stroke_segment(start, end, corners, stroke)
+ stroke_segment(start, end, corners, stroke.clone())
};
+
(shape, use_fill)
}
@@ -899,7 +884,7 @@ fn stroke_segment(
corners: &Corners<ControlPoints>,
stroke: FixedStroke,
) -> Shape {
- // create start corner
+ // Create start corner.
let mut path = Path::new();
path_segment(start, end, corners, &mut path);
@@ -915,7 +900,7 @@ fn fill_segment(
start: Corner,
end: Corner,
corners: &Corners<ControlPoints>,
- stroke: FixedStroke,
+ stroke: &FixedStroke,
) -> Shape {
let mut path = Path::new();
@@ -1004,7 +989,7 @@ fn fill_segment(
Shape {
geometry: Geometry::Path(path),
stroke: None,
- fill: Some(stroke.paint),
+ fill: Some(stroke.paint.clone()),
}
}