From b0687198b196407e7938978174db2745d615bdce Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 16 Aug 2024 16:00:25 +0200 Subject: [PATCH] Consolidate flow layout (#4772) --- crates/typst/src/layout/columns.rs | 9 +- crates/typst/src/layout/container.rs | 12 +- crates/typst/src/layout/flow.rs | 494 ++++++++++++++++++++--- crates/typst/src/layout/grid/cells.rs | 5 +- crates/typst/src/layout/grid/layout.rs | 12 +- crates/typst/src/layout/grid/mod.rs | 2 +- crates/typst/src/layout/grid/rowspans.rs | 6 +- crates/typst/src/layout/inline/mod.rs | 83 ++-- crates/typst/src/layout/layout.rs | 4 +- crates/typst/src/layout/measure.rs | 6 +- crates/typst/src/layout/mod.rs | 126 +----- crates/typst/src/layout/pad.rs | 5 +- crates/typst/src/layout/page.rs | 261 +----------- crates/typst/src/layout/place.rs | 9 +- crates/typst/src/layout/regions.rs | 32 +- crates/typst/src/layout/repeat.rs | 6 +- crates/typst/src/layout/stack.rs | 7 +- crates/typst/src/layout/transform.rs | 25 +- crates/typst/src/lib.rs | 13 +- crates/typst/src/math/ctx.rs | 48 +-- crates/typst/src/math/equation.rs | 15 +- crates/typst/src/math/matrix.rs | 4 +- crates/typst/src/model/document.rs | 56 +-- crates/typst/src/model/heading.rs | 13 +- crates/typst/src/model/par.rs | 30 +- crates/typst/src/realize/mod.rs | 44 +- crates/typst/src/visualize/pattern.rs | 6 +- crates/typst/src/visualize/shape.rs | 12 +- 28 files changed, 640 insertions(+), 705 deletions(-) diff --git a/crates/typst/src/layout/columns.rs b/crates/typst/src/layout/columns.rs index 503cb857d..7018a8ed8 100644 --- a/crates/typst/src/layout/columns.rs +++ b/crates/typst/src/layout/columns.rs @@ -5,7 +5,8 @@ use crate::engine::Engine; use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain}; use crate::introspection::Locator; use crate::layout::{ - Abs, Axes, BlockElem, Dir, Fragment, Frame, Length, Point, Ratio, Regions, Rel, Size, + layout_fragment, Abs, Axes, BlockElem, Dir, Fragment, Frame, Length, Point, Ratio, + Regions, Rel, Size, }; use crate::realize::{Behave, Behaviour}; use crate::text::TextElem; @@ -77,12 +78,12 @@ fn layout_columns( styles: StyleChain, regions: Regions, ) -> SourceResult { - let body = elem.body(); + 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, locator, styles, regions); + return layout_fragment(engine, body, locator, styles, regions); } // Determine the width of the gutter and each column. @@ -107,7 +108,7 @@ fn layout_columns( }; // Layout the children. - let mut frames = body.layout(engine, locator, styles, pod)?.into_iter(); + let mut frames = layout_fragment(engine, body, locator, styles, pod)?.into_iter(); let mut finished = vec![]; let dir = TextElem::dir_in(styles); diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs index 58a8d1077..83523861a 100644 --- a/crates/typst/src/layout/container.rs +++ b/crates/typst/src/layout/container.rs @@ -9,8 +9,8 @@ use crate::foundations::{ }; use crate::introspection::Locator; use crate::layout::{ - Abs, Axes, Corners, Em, Fr, Fragment, Frame, FrameKind, Length, Region, Regions, Rel, - Sides, Size, Spacing, + layout_fragment, layout_frame, Abs, Axes, Corners, Em, Fr, Fragment, Frame, + FrameKind, Length, Region, Regions, Rel, Sides, Size, Spacing, }; use crate::utils::Numeric; use crate::visualize::{clip_rect, Paint, Stroke}; @@ -141,9 +141,7 @@ impl Packed { // 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, locator, styles, pod.into_regions())? - .into_frame() + Some(body) => layout_frame(engine, body, locator, styles, pod)? .with_kind(FrameKind::Hard), }; @@ -531,7 +529,7 @@ impl Packed { // If we have content as our body, just layout it. Some(BlockChild::Content(body)) => { let mut fragment = - body.layout(engine, locator.relayout(), styles, pod)?; + layout_fragment(engine, body, locator.relayout(), styles, pod)?; // If the body is automatically sized and produced more than one // fragment, ensure that the width was consistent across all @@ -552,7 +550,7 @@ impl Packed { expand: Axes::new(true, pod.expand.y), ..pod }; - fragment = body.layout(engine, locator, styles, pod)?; + fragment = layout_fragment(engine, body, locator, styles, pod)?; } fragment diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs index e15159e11..080fc372d 100644 --- a/crates/typst/src/layout/flow.rs +++ b/crates/typst/src/layout/flow.rs @@ -1,31 +1,423 @@ -//! Layout flows. -//! -//! A *flow* is a collection of block-level layoutable elements. -//! This is analogous to a paragraph, which is a collection of -//! inline-level layoutable elements. +//! Layout of content +//! - at the top-level, into a [`Document`]. +//! - inside of a container, into a [`Frame`] or [`Fragment`]. use std::fmt::{self, Debug, Formatter}; +use std::ptr; + +use comemo::{Track, Tracked, TrackedMut}; use crate::diag::{bail, SourceResult}; -use crate::engine::Engine; +use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{ elem, Args, Construct, Content, NativeElement, Packed, Resolve, Smart, StyleChain, }; -use crate::introspection::{Locator, SplitLocator, Tag, TagElem}; -use crate::layout::{ - Abs, AlignElem, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem, Fr, - Fragment, Frame, FrameItem, PlaceElem, Point, Ratio, Regions, Rel, Size, Spacing, - VElem, +use crate::introspection::{ + Counter, CounterDisplayElem, CounterKey, Introspector, Locator, LocatorLink, + ManualPageCounter, SplitLocator, Tag, TagElem, }; +use crate::layout::{ + Abs, AlignElem, Alignment, Axes, Binding, BlockElem, ColbreakElem, ColumnsElem, Dir, + FixedAlignment, FlushElem, Fr, Fragment, Frame, FrameItem, HAlignment, Length, + OuterVAlignment, Page, PageElem, Paper, Parity, PlaceElem, Point, Ratio, Region, + Regions, Rel, Sides, Size, Spacing, VAlignment, VElem, +}; +use crate::model::{Document, Numbering}; use crate::model::{FootnoteElem, FootnoteEntry, ParElem}; use crate::realize::StyleVec; +use crate::realize::{realize_flow, realize_root, Arenas}; use crate::text::TextElem; use crate::utils::Numeric; +use crate::World; -/// Arranges spacing, paragraphs and block-level elements into a flow. +/// Layout content into a document. +/// +/// This first performs root-level realization and then lays out the resulting +/// elements. In contrast to [`layout_fragment`], this does not take regions +/// since the regions are defined by the page configuration in the content and +/// style chain. +#[typst_macros::time(name = "document")] +pub fn layout_document( + engine: &mut Engine, + content: &Content, + styles: StyleChain, +) -> SourceResult { + layout_document_impl( + engine.world, + engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), + engine.route.track(), + content, + styles, + ) +} + +/// The internal implementation of `layout_document`. +#[comemo::memoize] +fn layout_document_impl( + world: Tracked, + introspector: Tracked, + traced: Tracked, + sink: TrackedMut, + route: Tracked, + content: &Content, + styles: StyleChain, +) -> SourceResult { + let mut locator = Locator::root().split(); + let mut engine = Engine { + world, + introspector, + traced, + sink, + route: Route::extend(route).unnested(), + }; + + let arenas = Arenas::default(); + let (children, styles, info) = + realize_root(&mut engine, &mut locator, &arenas, content, styles)?; + + let mut peekable = children.chain(&styles).peekable(); + let iter = std::iter::from_fn(|| { + let (child, styles) = peekable.next()?; + let extend_to = peekable + .peek() + .and_then(|(next, _)| *next.to_packed::()?.clear_to()?); + let locator = locator.next(&child.span()); + Some((child, styles, extend_to, locator)) + }); + + let layouts = + engine.parallelize(iter, |engine, (child, styles, extend_to, locator)| { + if let Some(page) = child.to_packed::() { + layout_page_run(engine, page, locator, styles, extend_to) + } else { + bail!(child.span(), "expected page element"); + } + }); + + let mut page_counter = ManualPageCounter::new(); + let mut pages = Vec::with_capacity(children.len()); + for result in layouts { + let layout = result?; + pages.extend(finalize_page_run(&mut engine, layout, &mut page_counter)?); + } + + Ok(Document { pages, info, introspector: Introspector::default() }) +} + +/// A prepared layout of a page run that can be finalized with access to the +/// page counter. +struct PageRunLayout<'a> { + page: &'a Packed, + locator: SplitLocator<'a>, + styles: StyleChain<'a>, + extend_to: Option, + area: Size, + margin: Sides, + two_sided: bool, + frames: Vec, +} + +/// A document can consist of multiple `PageElem`s, one per run of pages +/// with equal properties (not one per actual output page!). The `number` is +/// the physical page number of the first page of this run. It is mutated +/// while we post-process the pages in this function. This function returns +/// a fragment consisting of multiple frames, one per output page of this +/// page run. +#[typst_macros::time(name = "pages", span = page.span())] +fn layout_page_run<'a>( + engine: &mut Engine, + page: &'a Packed, + locator: Locator<'a>, + styles: StyleChain<'a>, + extend_to: Option, +) -> SourceResult> { + let mut locator = locator.split(); + + // When one of the lengths is infinite the page fits its content along + // that axis. + let width = page.width(styles).unwrap_or(Abs::inf()); + let height = page.height(styles).unwrap_or(Abs::inf()); + let mut size = Size::new(width, height); + if page.flipped(styles) { + std::mem::swap(&mut size.x, &mut size.y); + } + + let mut min = width.min(height); + if !min.is_finite() { + min = Paper::A4.width(); + } + + // Determine the margins. + let default = Rel::::from((2.5 / 21.0) * min); + let margin = page.margin(styles); + let two_sided = margin.two_sided.unwrap_or(false); + let margin = margin + .sides + .map(|side| side.and_then(Smart::custom).unwrap_or(default)) + .resolve(styles) + .relative_to(size); + + // Realize columns. + let mut child = page.body().clone(); + let columns = page.columns(styles); + if columns.get() > 1 { + child = ColumnsElem::new(child) + .with_count(columns) + .pack() + .spanned(page.span()); + } + + let area = size - margin.sum_by_axis(); + let mut regions = Regions::repeat(area, area.map(Abs::is_finite)); + regions.root = true; + + // Layout the child. + let frames = + layout_fragment(engine, &child, locator.next(&page.span()), styles, regions)? + .into_frames(); + + Ok(PageRunLayout { + page, + locator, + styles, + extend_to, + area, + margin, + two_sided, + frames, + }) +} + +/// Finalize the layout with access to the next page counter. +#[typst_macros::time(name = "finalize pages", span = page.span())] +fn finalize_page_run( + engine: &mut Engine, + PageRunLayout { + page, + mut locator, + styles, + extend_to, + area, + margin, + two_sided, + mut frames, + }: PageRunLayout<'_>, + page_counter: &mut ManualPageCounter, +) -> SourceResult> { + // Align the child to the pagebreak's parity. + // Check for page count after adding the pending frames + if extend_to.is_some_and(|p| !p.matches(page_counter.physical().get() + frames.len())) + { + // Insert empty page after the current pages. + let size = area.map(Abs::is_finite).select(area, Size::zero()); + frames.push(Frame::hard(size)); + } + + let fill = page.fill(styles); + let foreground = page.foreground(styles); + let background = page.background(styles); + let header_ascent = page.header_ascent(styles); + let footer_descent = page.footer_descent(styles); + let numbering = page.numbering(styles); + let number_align = page.number_align(styles); + let binding = + page.binding(styles) + .unwrap_or_else(|| match TextElem::dir_in(styles) { + Dir::LTR => Binding::Left, + _ => Binding::Right, + }); + + // Construct the numbering (for header or footer). + let numbering_marginal = numbering.as_ref().map(|numbering| { + let both = match numbering { + Numbering::Pattern(pattern) => pattern.pieces() >= 2, + Numbering::Func(_) => true, + }; + + let mut counter = CounterDisplayElem::new( + Counter::new(CounterKey::Page), + Smart::Custom(numbering.clone()), + both, + ) + .pack() + .spanned(page.span()); + + // We interpret the Y alignment as selecting header or footer + // and then ignore it for aligning the actual number. + if let Some(x) = number_align.x() { + counter = counter.aligned(x.into()); + } + + counter + }); + + let header = page.header(styles); + let footer = page.footer(styles); + let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) { + (header.as_ref().unwrap_or(&numbering_marginal), footer.as_ref().unwrap_or(&None)) + } else { + (header.as_ref().unwrap_or(&None), footer.as_ref().unwrap_or(&numbering_marginal)) + }; + + // Post-process pages. + let mut pages = Vec::with_capacity(frames.len()); + for mut frame in frames { + // The padded width of the page's content without margins. + let pw = frame.width(); + + // If two sided, left becomes inside and right becomes outside. + // Thus, for left-bound pages, we want to swap on even pages and + // for right-bound pages, we want to swap on odd pages. + let mut margin = margin; + if two_sided && binding.swap(page_counter.physical()) { + std::mem::swap(&mut margin.left, &mut margin.right); + } + + // Realize margins. + frame.set_size(frame.size() + margin.sum_by_axis()); + frame.translate(Point::new(margin.left, margin.top)); + + // The page size with margins. + let size = frame.size(); + + // Realize overlays. + for marginal in [header, footer, background, foreground] { + let Some(content) = marginal.as_ref() else { continue }; + + let (pos, area, align); + if ptr::eq(marginal, header) { + let ascent = header_ascent.relative_to(margin.top); + pos = Point::with_x(margin.left); + area = Size::new(pw, margin.top - ascent); + align = Alignment::BOTTOM; + } else if ptr::eq(marginal, footer) { + let descent = footer_descent.relative_to(margin.bottom); + pos = Point::new(margin.left, size.y - margin.bottom + descent); + area = Size::new(pw, margin.bottom - descent); + align = Alignment::TOP; + } else { + pos = Point::zero(); + area = size; + align = HAlignment::Center + VAlignment::Horizon; + }; + + let aligned = content.clone().styled(AlignElem::set_alignment(align)); + let sub = layout_frame( + engine, + &aligned, + locator.next(&content.span()), + styles, + Region::new(area, Axes::splat(true)), + )?; + + if ptr::eq(marginal, header) || ptr::eq(marginal, background) { + frame.prepend_frame(pos, sub); + } else { + frame.push_frame(pos, sub); + } + } + + page_counter.visit(engine, &frame)?; + pages.push(Page { + frame, + fill: fill.clone(), + numbering: numbering.clone(), + number: page_counter.logical(), + }); + + page_counter.step(); + } + + Ok(pages) +} + +/// Layout content into a single region. +pub fn layout_frame( + engine: &mut Engine, + content: &Content, + locator: Locator, + styles: StyleChain, + region: Region, +) -> SourceResult { + layout_fragment(engine, content, locator, styles, region.into()) + .map(Fragment::into_frame) +} + +/// Layout content into multiple regions. +/// +/// When just layouting into a single region, prefer [`layout_frame`]. +pub fn layout_fragment( + engine: &mut Engine, + content: &Content, + locator: Locator, + styles: StyleChain, + regions: Regions, +) -> SourceResult { + layout_fragment_impl( + engine.world, + engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), + engine.route.track(), + content, + locator.track(), + styles, + regions, + ) +} + +/// The internal implementation of [`layout_fragment`]. +#[allow(clippy::too_many_arguments)] +#[comemo::memoize] +fn layout_fragment_impl( + world: Tracked, + introspector: Tracked, + traced: Tracked, + sink: TrackedMut, + route: Tracked, + content: &Content, + locator: Tracked, + styles: StyleChain, + regions: Regions, +) -> SourceResult { + let link = LocatorLink::new(locator); + let mut locator = Locator::link(&link).split(); + let mut engine = Engine { + world, + introspector, + traced, + sink, + route: Route::extend(route), + }; + + if !engine.route.within(Route::MAX_LAYOUT_DEPTH) { + bail!( + content.span(), "maximum layout depth exceeded"; + hint: "try to reduce the amount of nesting in your layout", + ); + } + + // If we are in a `PageElem`, this might already be a realized flow. + if let Some(flow) = content.to_packed::() { + return FlowLayouter::new(&mut engine, flow, locator, &styles, regions).layout(); + } + + // Layout the content by first turning it into a `FlowElem` and then + // layouting that. + let arenas = Arenas::default(); + let (flow, styles) = + realize_flow(&mut engine, &mut locator, &arenas, content, styles)?; + + FlowLayouter::new(&mut engine, &flow, locator, &styles, regions).layout() +} + +/// A collection of block-level layoutable elements. This is analogous to a +/// paragraph, which is a collection of inline-level layoutable elements. /// /// This element is responsible for layouting both the top-level content flow -/// and the contents of boxes. +/// and the contents of any containers. #[elem(Debug, Construct)] pub struct FlowElem { /// The children that will be arranged into a flow. @@ -40,19 +432,6 @@ impl Construct for FlowElem { } } -impl Packed { - #[typst_macros::time(name = "flow", span = self.span())] - pub fn layout( - &self, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - FlowLayouter::new(engine, self, locator, &styles, regions).layout() - } -} - impl Debug for FlowElem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "Flow ")?; @@ -169,7 +548,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { fn new( engine: &'a mut Engine<'e>, flow: &'a Packed, - locator: Locator<'a>, + locator: SplitLocator<'a>, styles: &'a StyleChain<'a>, mut regions: Regions<'a>, ) -> Self { @@ -200,7 +579,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { engine, flow, root, - locator: locator.split(), + locator, styles, regions, expand, @@ -272,6 +651,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { } /// Layout a paragraph. + #[typst_macros::time(name = "par", span = par.span())] fn handle_par( &mut self, par: &'a Packed, @@ -286,16 +666,16 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { // not on the Y position. let consecutive = self.last_was_par; let locator = self.locator.next(&par.span()); - let lines = par - .layout( - self.engine, - locator, - styles, - consecutive, - self.regions.base(), - self.regions.expand.x, - )? - .into_frames(); + let lines = crate::layout::layout_inline( + self.engine, + &par.children, + locator, + styles, + consecutive, + self.regions.base(), + self.regions.expand.x, + )? + .into_frames(); // If the first line doesn’t fit in this region, then defer any // previous sticky frame to the next region (if available) @@ -440,14 +820,12 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { }); let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles))); - let mut frame = placed - .layout( - self.engine, - self.locator.next(&placed.span()), - styles, - self.regions.base(), - )? - .into_frame(); + let mut frame = placed.layout( + self.engine, + self.locator.next(&placed.span()), + styles, + self.regions.base(), + )?; frame.post_process(styles); @@ -830,15 +1208,14 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { } self.regions.size.y -= self.footnote_config.gap; - let frames = FootnoteEntry::new(notes[k].clone()) - .pack() - .layout( - self.engine, - Locator::synthesize(notes[k].location().unwrap()), - *self.styles, - self.regions.with_root(false), - )? - .into_frames(); + let frames = layout_fragment( + self.engine, + &FootnoteEntry::new(notes[k].clone()).pack(), + Locator::synthesize(notes[k].location().unwrap()), + *self.styles, + self.regions.with_root(false), + )? + .into_frames(); // If the entries didn't fit, abort (to keep footnote and entry // together). @@ -882,13 +1259,12 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { /// Layout and save the footnote separator, typically a line. fn layout_footnote_separator(&mut self) -> SourceResult<()> { let expand = Axes::new(self.regions.expand.x, false); - let pod = Regions::one(self.regions.base(), expand); + let pod = Region::new(self.regions.base(), expand); let separator = &self.footnote_config.separator; // FIXME: Shouldn't use `root()` here. - let mut frame = separator - .layout(self.engine, Locator::root(), *self.styles, pod)? - .into_frame(); + let mut frame = + layout_frame(self.engine, separator, Locator::root(), *self.styles, pod)?; frame.size_mut().y += self.footnote_config.clearance; frame.translate(Point::with_y(self.footnote_config.clearance)); diff --git a/crates/typst/src/layout/grid/cells.rs b/crates/typst/src/layout/grid/cells.rs index e45d58d41..2e788d344 100644 --- a/crates/typst/src/layout/grid/cells.rs +++ b/crates/typst/src/layout/grid/cells.rs @@ -14,7 +14,8 @@ use crate::foundations::{ }; use crate::introspection::Locator; use crate::layout::{ - Abs, Alignment, Axes, Fragment, Length, LinePosition, Regions, Rel, Sides, Sizing, + layout_fragment, Abs, Alignment, Axes, Fragment, Length, LinePosition, Regions, Rel, + Sides, Sizing, }; use crate::syntax::Span; use crate::utils::NonZeroExt; @@ -220,7 +221,7 @@ impl<'a> Cell<'a> { if disambiguator > 0 { locator = locator.split().next_inner(disambiguator as u128); } - self.body.layout(engine, locator, styles, regions) + layout_fragment(engine, &self.body, locator, styles, regions) } } diff --git a/crates/typst/src/layout/grid/layout.rs b/crates/typst/src/layout/grid/layout.rs index 9c5d7c21b..53eda0f0a 100644 --- a/crates/typst/src/layout/grid/layout.rs +++ b/crates/typst/src/layout/grid/layout.rs @@ -11,7 +11,7 @@ use crate::engine::Engine; use crate::foundations::{Resolve, StyleChain}; use crate::layout::{ Abs, Axes, Cell, CellGrid, Dir, Fr, Fragment, Frame, FrameItem, Length, Point, - Regions, Rel, Size, Sizing, + Region, Regions, Rel, Size, Sizing, }; use crate::syntax::Span; use crate::text::TextElem; @@ -843,8 +843,8 @@ impl<'a> GridLayouter<'a> { let already_covered_width = self.cell_spanned_width(cell, parent.x); let size = Size::new(available, height); - let pod = Regions::one(size, Axes::splat(false)); - let frame = cell.layout(engine, 0, self.styles, pod)?.into_frame(); + let pod = Region::new(size, Axes::splat(false)); + let frame = cell.layout(engine, 0, self.styles, pod.into())?.into_frame(); resolved.set_max(frame.width() - already_covered_width); } @@ -1062,7 +1062,7 @@ impl<'a> GridLayouter<'a> { // Force cell to fit into a single region when the row is // unbreakable, even when it is a breakable rowspan, as a best // effort. - let mut pod = Regions::one(size, self.regions.expand); + let mut pod: Regions = Region::new(size, self.regions.expand).into(); pod.full = measurement_data.full; if measurement_data.frames_in_previous_regions > 0 { @@ -1244,7 +1244,7 @@ impl<'a> GridLayouter<'a> { if cell.rowspan.get() == 1 { let width = self.cell_spanned_width(cell, x); let size = Size::new(width, height); - let mut pod = Regions::one(size, Axes::splat(true)); + let mut pod: Regions = Region::new(size, Axes::splat(true)).into(); if self.grid.rows[y] == Sizing::Auto && self.unbreakable_rows_left == 0 { @@ -1296,7 +1296,7 @@ impl<'a> GridLayouter<'a> { // Prepare regions. let size = Size::new(self.width, heights[0]); - let mut pod = Regions::one(size, Axes::splat(true)); + let mut pod: Regions = Region::new(size, Axes::splat(true)).into(); pod.full = self.regions.full; pod.backlog = &heights[1..]; diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst/src/layout/grid/mod.rs index 326e356a6..08257a5ca 100644 --- a/crates/typst/src/layout/grid/mod.rs +++ b/crates/typst/src/layout/grid/mod.rs @@ -967,7 +967,7 @@ impl From for GridCell { } /// Function with common code to display a grid cell or table cell. -pub fn show_grid_cell( +pub(crate) fn show_grid_cell( mut body: Content, inset: Smart>>>, align: Smart, diff --git a/crates/typst/src/layout/grid/rowspans.rs b/crates/typst/src/layout/grid/rowspans.rs index 85ec49c99..91e7d8efe 100644 --- a/crates/typst/src/layout/grid/rowspans.rs +++ b/crates/typst/src/layout/grid/rowspans.rs @@ -3,7 +3,9 @@ use super::repeated::Repeatable; use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::Resolve; -use crate::layout::{Abs, Axes, Cell, Frame, GridLayouter, Point, Regions, Size, Sizing}; +use crate::layout::{ + Abs, Axes, Cell, Frame, GridLayouter, Point, Region, Regions, Size, Sizing, +}; use crate::utils::MaybeReverseIter; /// All information needed to layout a single rowspan. @@ -123,7 +125,7 @@ impl<'a> GridLayouter<'a> { // Prepare regions. let size = Size::new(width, *first_height); - let mut pod = Regions::one(size, Axes::splat(true)); + let mut pod: Regions = Region::new(size, Axes::splat(true)).into(); pod.backlog = backlog; if !is_effectively_unbreakable diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs index 821b4f57e..44b6ee3c2 100644 --- a/crates/typst/src/layout/inline/mod.rs +++ b/crates/typst/src/layout/inline/mod.rs @@ -30,54 +30,15 @@ type Range = std::ops::Range; /// Layouts content inline. pub(crate) fn layout_inline( - children: &StyleVec, engine: &mut Engine, + children: &StyleVec, locator: Locator, styles: StyleChain, consecutive: bool, region: Size, expand: bool, ) -> SourceResult { - #[comemo::memoize] - #[allow(clippy::too_many_arguments)] - fn cached( - children: &StyleVec, - world: Tracked, - introspector: Tracked, - traced: Tracked, - sink: TrackedMut, - route: Tracked, - locator: Tracked, - styles: StyleChain, - consecutive: bool, - region: Size, - expand: bool, - ) -> SourceResult { - let link = LocatorLink::new(locator); - let locator = Locator::link(&link); - let mut engine = Engine { - world, - introspector, - traced, - sink, - route: Route::extend(route), - }; - - // Collect all text into one string for BiDi analysis. - let (text, segments, spans) = - collect(children, &mut engine, locator, &styles, region, consecutive)?; - - // Perform BiDi analysis and then prepares paragraph layout. - let p = prepare(&mut engine, children, &text, segments, spans, styles)?; - - // Break the paragraph into lines. - let lines = linebreak(&engine, &p, region.x - p.hang); - - // Turn the selected lines into frames. - finalize(&mut engine, &p, &lines, styles, region, expand) - } - - cached( + layout_inline_impl( children, engine.world, engine.introspector, @@ -91,3 +52,43 @@ pub(crate) fn layout_inline( expand, ) } + +/// The internal, memoized implementation of `layout_inline`. +#[comemo::memoize] +#[allow(clippy::too_many_arguments)] +fn layout_inline_impl( + children: &StyleVec, + world: Tracked, + introspector: Tracked, + traced: Tracked, + sink: TrackedMut, + route: Tracked, + locator: Tracked, + styles: StyleChain, + consecutive: bool, + region: Size, + expand: bool, +) -> SourceResult { + let link = LocatorLink::new(locator); + let locator = Locator::link(&link); + let mut engine = Engine { + world, + introspector, + traced, + sink, + route: Route::extend(route), + }; + + // Collect all text into one string for BiDi analysis. + let (text, segments, spans) = + collect(children, &mut engine, locator, &styles, region, consecutive)?; + + // Perform BiDi analysis and then prepares paragraph layout. + let p = prepare(&mut engine, children, &text, segments, spans, styles)?; + + // Break the paragraph into lines. + let lines = linebreak(&engine, &p, region.x - p.hang); + + // Turn the selected lines into frames. + finalize(&mut engine, &p, &lines, styles, region, expand) +} diff --git a/crates/typst/src/layout/layout.rs b/crates/typst/src/layout/layout.rs index efe5d1247..bf6627409 100644 --- a/crates/typst/src/layout/layout.rs +++ b/crates/typst/src/layout/layout.rs @@ -6,7 +6,7 @@ use crate::foundations::{ dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain, }; use crate::introspection::Locatable; -use crate::layout::{BlockElem, Size}; +use crate::layout::{layout_fragment, BlockElem, Size}; use crate::syntax::Span; /// Provides access to the current outer container's (or page's, if none) @@ -92,7 +92,7 @@ impl Show for Packed { [dict! { "width" => x, "height" => y }], )? .display(); - result.layout(engine, locator, styles, regions) + layout_fragment(engine, &result, locator, styles, regions) }, ) .pack() diff --git a/crates/typst/src/layout/measure.rs b/crates/typst/src/layout/measure.rs index cb08e5913..92d4c5c3c 100644 --- a/crates/typst/src/layout/measure.rs +++ b/crates/typst/src/layout/measure.rs @@ -6,7 +6,7 @@ use crate::foundations::{ dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles, }; use crate::introspection::{Locator, LocatorLink}; -use crate::layout::{Abs, Axes, Length, Regions, Size}; +use crate::layout::{layout_frame, Abs, Axes, Length, Region, Size}; use crate::syntax::Span; /// Measures the layouted size of content. @@ -93,7 +93,7 @@ pub fn measure( }; // Create a pod region with the available space. - let pod = Regions::one( + let pod = Region::new( Axes::new( width.resolve(styles).unwrap_or(Abs::inf()), height.resolve(styles).unwrap_or(Abs::inf()), @@ -109,7 +109,7 @@ pub fn measure( let link = LocatorLink::measure(here); let locator = Locator::link(&link); - let frame = content.layout(engine, locator, styles, pod)?.into_frame(); + let frame = layout_frame(engine, &content, locator, styles, pod)?; let Size { x, y } = frame.size(); Ok(dict! { "width" => x, "height" => y }) } diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs index f7f5c5b3b..8f3dc7c56 100644 --- a/crates/typst/src/layout/mod.rs +++ b/crates/typst/src/layout/mod.rs @@ -67,17 +67,9 @@ pub use self::spacing::*; pub use self::stack::*; pub use self::transform::*; -pub(crate) use self::inline::*; +pub(crate) use self::inline::layout_inline; -use comemo::{Track, Tracked, TrackedMut}; - -use crate::diag::{bail, SourceResult}; -use crate::engine::{Engine, Route, Sink, Traced}; -use crate::foundations::{category, Category, Content, Scope, StyleChain}; -use crate::introspection::{Introspector, Locator, LocatorLink}; -use crate::model::Document; -use crate::realize::{realize_doc, realize_flow, Arenas}; -use crate::World; +use crate::foundations::{category, Category, Scope}; /// Arranging elements on the page in different ways. /// @@ -116,117 +108,3 @@ pub fn define(global: &mut Scope) { global.define_func::(); global.define_func::(); } - -impl Content { - /// Layout the content into a document. - /// - /// 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, - ) -> SourceResult { - #[comemo::memoize] - fn cached( - content: &Content, - world: Tracked, - introspector: Tracked, - traced: Tracked, - sink: TrackedMut, - route: Tracked, - styles: StyleChain, - ) -> SourceResult { - let mut locator = Locator::root().split(); - let mut engine = Engine { - world, - introspector, - traced, - sink, - route: Route::extend(route).unnested(), - }; - let arenas = Arenas::default(); - let (document, styles, info) = - realize_doc(&mut engine, locator.next(&()), &arenas, content, styles)?; - document.layout(&mut engine, locator.next(&()), styles, info) - } - - cached( - self, - engine.world, - engine.introspector, - engine.traced, - TrackedMut::reborrow_mut(&mut engine.sink), - engine.route.track(), - styles, - ) - } - - /// Layout the content into the given regions. - pub fn layout( - &self, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - #[allow(clippy::too_many_arguments)] - #[comemo::memoize] - fn cached( - content: &Content, - world: Tracked, - introspector: Tracked, - traced: Tracked, - sink: TrackedMut, - route: Tracked, - locator: Tracked, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let link = LocatorLink::new(locator); - let locator = Locator::link(&link); - let mut engine = Engine { - world, - introspector, - traced, - sink, - route: Route::extend(route), - }; - - if !engine.route.within(Route::MAX_LAYOUT_DEPTH) { - bail!( - content.span(), "maximum layout depth exceeded"; - hint: "try to reduce the amount of nesting in your layout", - ); - } - - // If we are in a `PageElem`, this might already be a realized flow. - if let Some(flow) = content.to_packed::() { - return flow.layout(&mut engine, locator, styles, regions); - } - - // Layout the content by first turning it into a `FlowElem` and then - // layouting that. - let mut locator = locator.split(); - let arenas = Arenas::default(); - let (flow, styles) = - realize_flow(&mut engine, locator.next(&()), &arenas, content, styles)?; - flow.layout(&mut engine, locator.next(&()), styles, regions) - } - - cached( - self, - engine.world, - engine.introspector, - engine.traced, - TrackedMut::reborrow_mut(&mut engine.sink), - engine.route.track(), - locator.track(), - styles, - regions, - ) - } -} diff --git a/crates/typst/src/layout/pad.rs b/crates/typst/src/layout/pad.rs index 814ccdc5f..f1b69a5c1 100644 --- a/crates/typst/src/layout/pad.rs +++ b/crates/typst/src/layout/pad.rs @@ -5,7 +5,8 @@ use crate::foundations::{ }; use crate::introspection::Locator; use crate::layout::{ - Abs, BlockElem, Fragment, Frame, Length, Point, Regions, Rel, Sides, Size, + layout_fragment, Abs, BlockElem, Fragment, Frame, Length, Point, Regions, Rel, Sides, + Size, }; /// Adds spacing around content. @@ -91,7 +92,7 @@ fn layout_pad( let pod = regions.map(&mut backlog, |size| shrink(size, &padding)); // Layout child into padded regions. - let mut fragment = elem.body().layout(engine, locator, styles, pod)?; + let mut fragment = layout_fragment(engine, &elem.body, locator, styles, pod)?; for frame in &mut fragment { grow(frame, &padding); diff --git a/crates/typst/src/layout/page.rs b/crates/typst/src/layout/page.rs index ca2a0ce91..f52f59e94 100644 --- a/crates/typst/src/layout/page.rs +++ b/crates/typst/src/layout/page.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::num::NonZeroUsize; use std::ops::RangeInclusive; -use std::ptr; use std::str::FromStr; use comemo::Track; @@ -9,21 +8,15 @@ use comemo::Track; use crate::diag::{bail, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - cast, elem, AutoValue, Cast, Content, Context, Dict, Fold, Func, NativeElement, - Packed, Resolve, Smart, StyleChain, Value, -}; -use crate::introspection::{ - Counter, CounterDisplayElem, CounterKey, Locator, ManualPageCounter, SplitLocator, + cast, elem, AutoValue, Cast, Content, Context, Dict, Fold, Func, Smart, StyleChain, + Value, }; use crate::layout::{ - Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length, - OuterVAlignment, Point, Ratio, Regions, Rel, Sides, Size, SpecificAlignment, - VAlignment, + Abs, Alignment, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel, Sides, + SpecificAlignment, }; - use crate::model::Numbering; -use crate::text::TextElem; -use crate::utils::{NonZeroExt, Numeric, Scalar}; +use crate::utils::{NonZeroExt, Scalar}; use crate::visualize::{Color, Paint}; /// Layouts its child onto one or multiple pages. @@ -348,236 +341,6 @@ pub struct PageElem { pub clear_to: Option, } -impl Packed { - /// A document can consist of multiple `PageElem`s, one per run of pages - /// with equal properties (not one per actual output page!). The `number` is - /// the physical page number of the first page of this run. It is mutated - /// while we post-process the pages in this function. This function returns - /// a fragment consisting of multiple frames, one per output page of this - /// page run. - #[typst_macros::time(name = "page", span = self.span())] - pub fn layout<'a>( - &'a self, - engine: &mut Engine, - locator: Locator<'a>, - styles: StyleChain<'a>, - extend_to: Option, - ) -> SourceResult> { - let mut locator = locator.split(); - - // When one of the lengths is infinite the page fits its content along - // that axis. - let width = self.width(styles).unwrap_or(Abs::inf()); - let height = self.height(styles).unwrap_or(Abs::inf()); - let mut size = Size::new(width, height); - if self.flipped(styles) { - std::mem::swap(&mut size.x, &mut size.y); - } - - let mut min = width.min(height); - if !min.is_finite() { - min = Paper::A4.width(); - } - - // Determine the margins. - let default = Rel::::from((2.5 / 21.0) * min); - let margin = self.margin(styles); - let two_sided = margin.two_sided.unwrap_or(false); - let margin = margin - .sides - .map(|side| side.and_then(Smart::custom).unwrap_or(default)) - .resolve(styles) - .relative_to(size); - - // Realize columns. - let mut child = self.body().clone(); - let columns = self.columns(styles); - if columns.get() > 1 { - child = ColumnsElem::new(child) - .with_count(columns) - .pack() - .spanned(self.span()); - } - - let area = size - margin.sum_by_axis(); - let mut regions = Regions::repeat(area, area.map(Abs::is_finite)); - regions.root = true; - - // Layout the child. - let frames = child - .layout(engine, locator.next(&self.span()), styles, regions)? - .into_frames(); - - Ok(PageLayout { - page: self, - locator, - styles, - extend_to, - area, - margin, - two_sided, - frames, - }) - } -} - -/// A prepared layout of a page run that can be finalized with access to the -/// page counter. -pub struct PageLayout<'a> { - page: &'a Packed, - locator: SplitLocator<'a>, - styles: StyleChain<'a>, - extend_to: Option, - area: Size, - margin: Sides, - two_sided: bool, - frames: Vec, -} - -impl PageLayout<'_> { - /// Finalize the layout with access to the next page counter. - #[typst_macros::time(name = "finalize page", span = self.page.span())] - pub fn finalize( - mut self, - engine: &mut Engine, - page_counter: &mut ManualPageCounter, - ) -> SourceResult> { - let styles = self.styles; - - // Align the child to the pagebreak's parity. - // Check for page count after adding the pending frames - if self.extend_to.is_some_and(|p| { - !p.matches(page_counter.physical().get() + self.frames.len()) - }) { - // Insert empty page after the current pages. - let size = self.area.map(Abs::is_finite).select(self.area, Size::zero()); - self.frames.push(Frame::hard(size)); - } - - let fill = self.page.fill(styles); - let foreground = self.page.foreground(styles); - let background = self.page.background(styles); - let header_ascent = self.page.header_ascent(styles); - let footer_descent = self.page.footer_descent(styles); - let numbering = self.page.numbering(styles); - let number_align = self.page.number_align(styles); - let binding = - self.page - .binding(styles) - .unwrap_or_else(|| match TextElem::dir_in(styles) { - Dir::LTR => Binding::Left, - _ => Binding::Right, - }); - - // Construct the numbering (for header or footer). - let numbering_marginal = numbering.as_ref().map(|numbering| { - let both = match numbering { - Numbering::Pattern(pattern) => pattern.pieces() >= 2, - Numbering::Func(_) => true, - }; - - let mut counter = CounterDisplayElem::new( - Counter::new(CounterKey::Page), - Smart::Custom(numbering.clone()), - both, - ) - .pack() - .spanned(self.page.span()); - - // We interpret the Y alignment as selecting header or footer - // and then ignore it for aligning the actual number. - if let Some(x) = number_align.x() { - counter = counter.aligned(x.into()); - } - - counter - }); - - let header = self.page.header(styles); - let footer = self.page.footer(styles); - let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) { - ( - header.as_ref().unwrap_or(&numbering_marginal), - footer.as_ref().unwrap_or(&None), - ) - } else { - ( - header.as_ref().unwrap_or(&None), - footer.as_ref().unwrap_or(&numbering_marginal), - ) - }; - - // Post-process pages. - let mut pages = Vec::with_capacity(self.frames.len()); - for mut frame in self.frames { - // The padded width of the page's content without margins. - let pw = frame.width(); - - // If two sided, left becomes inside and right becomes outside. - // Thus, for left-bound pages, we want to swap on even pages and - // for right-bound pages, we want to swap on odd pages. - let mut margin = self.margin; - if self.two_sided && binding.swap(page_counter.physical()) { - std::mem::swap(&mut margin.left, &mut margin.right); - } - - // Realize margins. - frame.set_size(frame.size() + margin.sum_by_axis()); - frame.translate(Point::new(margin.left, margin.top)); - - // The page size with margins. - let size = frame.size(); - - // Realize overlays. - for marginal in [header, footer, background, foreground] { - let Some(content) = marginal.as_ref() else { continue }; - - let (pos, area, align); - if ptr::eq(marginal, header) { - let ascent = header_ascent.relative_to(margin.top); - pos = Point::with_x(margin.left); - area = Size::new(pw, margin.top - ascent); - align = Alignment::BOTTOM; - } else if ptr::eq(marginal, footer) { - let descent = footer_descent.relative_to(margin.bottom); - pos = Point::new(margin.left, size.y - margin.bottom + descent); - area = Size::new(pw, margin.bottom - descent); - align = Alignment::TOP; - } else { - pos = Point::zero(); - area = size; - align = HAlignment::Center + VAlignment::Horizon; - }; - - let pod = Regions::one(area, Axes::splat(true)); - let sub = content - .clone() - .styled(AlignElem::set_alignment(align)) - .layout(engine, self.locator.next(&content.span()), styles, pod)? - .into_frame(); - - if ptr::eq(marginal, header) || ptr::eq(marginal, background) { - frame.prepend_frame(pos, sub); - } else { - frame.push_frame(pos, sub); - } - } - - page_counter.visit(engine, &frame)?; - pages.push(Page { - frame, - fill: fill.clone(), - numbering: numbering.clone(), - number: page_counter.logical(), - }); - - page_counter.step(); - } - - Ok(pages) - } -} - /// A finished page. #[derive(Debug, Clone)] pub struct Page { @@ -736,7 +499,7 @@ pub enum Binding { impl Binding { /// Whether to swap left and right margin for the page with this number. - fn swap(self, number: NonZeroUsize) -> bool { + pub fn swap(self, number: NonZeroUsize) -> bool { match self { // Left-bound must swap on even pages // (because it is correct on the first page). @@ -798,14 +561,18 @@ cast! { v: Func => Self::Func(v), } -/// A list of page ranges to be exported. The ranges are one-indexed. -/// For example, `1..=3` indicates the first, second and third pages should be -/// exported. +/// A list of page ranges to be exported. + pub struct PageRanges(Vec); +/// A range of pages to export. +/// +/// The range is one-indexed. For example, `1..=3` indicates the first, second +/// and third pages should be exported. pub type PageRange = RangeInclusive>; impl PageRanges { + /// Create new page ranges. pub fn new(ranges: Vec) -> Self { Self(ranges) } @@ -875,7 +642,7 @@ pub enum Parity { impl Parity { /// Whether the given number matches the parity. - fn matches(self, number: usize) -> bool { + pub fn matches(self, number: usize) -> bool { match self { Self::Even => number % 2 == 0, Self::Odd => number % 2 == 1, diff --git a/crates/typst/src/layout/place.rs b/crates/typst/src/layout/place.rs index be211c150..51a1f5bf9 100644 --- a/crates/typst/src/layout/place.rs +++ b/crates/typst/src/layout/place.rs @@ -3,7 +3,7 @@ use crate::engine::Engine; use crate::foundations::{elem, scope, Content, Packed, Smart, StyleChain, Unlabellable}; use crate::introspection::Locator; use crate::layout::{ - Alignment, Axes, Em, Fragment, Length, Regions, Rel, Size, VAlignment, + layout_frame, Alignment, Axes, Em, Frame, Length, Region, Rel, Size, VAlignment, }; use crate::realize::{Behave, Behaviour}; @@ -112,7 +112,7 @@ impl Packed { locator: Locator, styles: StyleChain, base: Size, - ) -> SourceResult { + ) -> SourceResult { // The pod is the base area of the region because for absolute // placement we don't really care about the already used area. let float = self.float(styles); @@ -135,9 +135,8 @@ impl Packed { .clone() .aligned(alignment.unwrap_or_else(|| Alignment::CENTER)); - let pod = Regions::one(base, Axes::splat(false)); - let frame = child.layout(engine, locator, styles, pod)?.into_frame(); - Ok(Fragment::frame(frame)) + let pod = Region::new(base, Axes::splat(false)); + layout_frame(engine, &child, locator, styles, pod) } } diff --git a/crates/typst/src/layout/regions.rs b/crates/typst/src/layout/regions.rs index 6280632dd..7ff2e1c42 100644 --- a/crates/typst/src/layout/regions.rs +++ b/crates/typst/src/layout/regions.rs @@ -17,10 +17,18 @@ impl Region { pub fn new(size: Size, expand: Axes) -> Self { Self { size, expand } } +} - /// Turns this into a region sequence. - pub fn into_regions(self) -> Regions<'static> { - Regions::one(self.size, self.expand) +impl From for Regions<'_> { + fn from(region: Region) -> Self { + Regions { + size: region.size, + expand: region.expand, + full: region.size.y, + backlog: &[], + last: None, + root: false, + } } } @@ -35,6 +43,9 @@ impl Region { pub struct Regions<'a> { /// The remaining size of the first region. pub size: Size, + /// Whether elements should expand to fill the regions instead of shrinking + /// to fit the content. + pub expand: Axes, /// The full height of the region for relative sizing. pub full: Abs, /// The height of followup regions. The width is the same for all regions. @@ -42,9 +53,6 @@ pub struct Regions<'a> { /// The height of the final region that is repeated once the backlog is /// drained. The width is the same for all regions. pub last: Option, - /// Whether elements should expand to fill the regions instead of shrinking - /// to fit the content. - pub expand: Axes, /// Whether these are the root regions or direct descendants. /// /// True for the padded page regions and columns directly in the page, @@ -53,18 +61,6 @@ pub struct Regions<'a> { } impl Regions<'_> { - /// Create a new region sequence with exactly one region. - pub fn one(size: Size, expand: Axes) -> Self { - Self { - size, - full: size.y, - backlog: &[], - last: None, - expand, - root: false, - } - } - /// Create a new sequence of same-size regions that repeats indefinitely. pub fn repeat(size: Size, expand: Axes) -> Self { Self { diff --git a/crates/typst/src/layout/repeat.rs b/crates/typst/src/layout/repeat.rs index 60a3e4c7e..66a6f923c 100644 --- a/crates/typst/src/layout/repeat.rs +++ b/crates/typst/src/layout/repeat.rs @@ -5,7 +5,7 @@ use crate::foundations::{ }; use crate::introspection::Locator; use crate::layout::{ - Abs, AlignElem, Axes, BlockElem, Frame, Length, Point, Region, Regions, Size, + layout_frame, Abs, AlignElem, Axes, BlockElem, Frame, Length, Point, Region, Size, }; use crate::utils::Numeric; @@ -63,8 +63,8 @@ fn layout_repeat( styles: StyleChain, region: Region, ) -> SourceResult { - let pod = Regions::one(region.size, Axes::new(false, false)); - let piece = elem.body().layout(engine, locator, styles, pod)?.into_frame(); + let pod = Region::new(region.size, Axes::new(false, false)); + let piece = layout_frame(engine, &elem.body, locator, styles, pod)?; let size = Size::new(region.size.x, piece.height()); if !size.is_finite() { diff --git a/crates/typst/src/layout/stack.rs b/crates/typst/src/layout/stack.rs index 3d8002ab2..6f1aa5340 100644 --- a/crates/typst/src/layout/stack.rs +++ b/crates/typst/src/layout/stack.rs @@ -8,8 +8,8 @@ use crate::foundations::{ }; use crate::introspection::{Locator, SplitLocator}; use crate::layout::{ - Abs, AlignElem, Axes, Axis, BlockElem, Dir, FixedAlignment, Fr, Fragment, Frame, - HElem, Point, Regions, Size, Spacing, VElem, + layout_fragment, Abs, AlignElem, Axes, Axis, BlockElem, Dir, FixedAlignment, Fr, + Fragment, Frame, HElem, Point, Regions, Size, Spacing, VElem, }; use crate::utils::{Get, Numeric}; @@ -257,8 +257,9 @@ impl<'a> StackLayouter<'a> { } .resolve(styles); - let fragment = block.layout( + let fragment = layout_fragment( engine, + block, self.locator.next(&block.span()), styles, self.regions, diff --git a/crates/typst/src/layout/transform.rs b/crates/typst/src/layout/transform.rs index dcb178fdb..2c718193f 100644 --- a/crates/typst/src/layout/transform.rs +++ b/crates/typst/src/layout/transform.rs @@ -9,8 +9,8 @@ use crate::foundations::{ }; use crate::introspection::Locator; use crate::layout::{ - Abs, Alignment, Angle, Axes, BlockElem, FixedAlignment, Frame, HAlignment, Length, - Point, Ratio, Region, Regions, Rel, Size, VAlignment, + layout_frame, Abs, Alignment, Angle, Axes, BlockElem, FixedAlignment, Frame, + HAlignment, Length, Point, Ratio, Region, Rel, Size, VAlignment, }; use crate::utils::Numeric; @@ -62,10 +62,7 @@ fn layout_move( styles: StyleChain, region: Region, ) -> SourceResult { - let mut frame = elem - .body() - .layout(engine, locator, styles, region.into_regions())? - .into_frame(); + let mut frame = layout_frame(engine, &elem.body, locator, styles, region)?; 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()); @@ -299,8 +296,8 @@ impl Packed { } let size = Lazy::new(|| { - let pod = Regions::one(container, Axes::splat(false)); - let frame = self.body().layout(engine, locator, styles, pod)?.into_frame(); + let pod = Region::new(container, Axes::splat(false)); + let frame = layout_frame(engine, &self.body, locator, styles, pod)?; SourceResult::Ok(frame.size()) }); @@ -478,12 +475,12 @@ fn measure_and_layout( ) -> SourceResult { if reflow { // Measure the size of the body. - let pod = Regions::one(size, Axes::splat(false)); - let frame = body.layout(engine, locator.relayout(), styles, pod)?.into_frame(); + let pod = Region::new(size, Axes::splat(false)); + let frame = layout_frame(engine, body, locator.relayout(), styles, pod)?; // Actually perform the layout. - let pod = Regions::one(frame.size(), Axes::splat(true)); - let mut frame = body.layout(engine, locator, styles, pod)?.into_frame(); + let pod = Region::new(frame.size(), Axes::splat(true)); + let mut frame = layout_frame(engine, body, locator, styles, pod)?; let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position); // Compute the transform. @@ -499,9 +496,7 @@ fn measure_and_layout( Ok(frame) } else { // Layout the body. - let mut frame = body - .layout(engine, locator, styles, region.into_regions())? - .into_frame(); + let mut frame = layout_frame(engine, body, locator, styles, region)?; let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position); // Compute the transform. diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index cfcfd757a..69c8dad8e 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]: foundations::Content::layout_document +//! [layouted]: crate::layout::layout_document //! [document]: model::Document //! [frame]: layout::Frame @@ -86,7 +86,7 @@ use crate::visualize::Color; #[typst_macros::time] pub fn compile(world: &dyn World) -> Warned> { let mut sink = Sink::new(); - let output = compile_inner(world.track(), Traced::default().track(), &mut sink) + let output = compile_impl(world.track(), Traced::default().track(), &mut sink) .map_err(deduplicate); Warned { output, warnings: sink.warnings() } } @@ -97,12 +97,13 @@ pub fn compile(world: &dyn World) -> Warned> { pub fn trace(world: &dyn World, span: Span) -> EcoVec<(Value, Option)> { let mut sink = Sink::new(); let traced = Traced::new(span); - compile_inner(world.track(), traced.track(), &mut sink).ok(); + compile_impl(world.track(), traced.track(), &mut sink).ok(); sink.values() } -/// Relayout until introspection converges. -fn compile_inner( +/// The internal implementation of `compile` with a bit lower-level interface +/// that is also used by `trace`. +fn compile_impl( world: Tracked, traced: Tracked, sink: &mut Sink, @@ -150,7 +151,7 @@ fn compile_inner( }; // Layout! - document = content.layout_document(&mut engine, styles)?; + document = crate::layout::layout_document(&mut engine, &content, 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 ac3fbca85..0eb97f9ce 100644 --- a/crates/typst/src/math/ctx.rs +++ b/crates/typst/src/math/ctx.rs @@ -13,12 +13,11 @@ use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{Content, Packed, StyleChain}; use crate::introspection::{Locator, SplitLocator}; -use crate::layout::{Abs, Axes, BoxElem, Em, Frame, Regions, Size}; +use crate::layout::{layout_frame, Abs, Axes, BoxElem, Em, Frame, Region, Size}; use crate::math::{ scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment, LayoutMath, MathFragment, MathRun, MathSize, THICK, }; -use crate::model::ParElem; use crate::realize::StyleVec; use crate::syntax::{is_newline, Span}; use crate::text::{ @@ -51,7 +50,7 @@ pub struct MathContext<'a, 'b, 'v> { // External. pub engine: &'v mut Engine<'b>, pub locator: SplitLocator<'v>, - pub regions: Regions<'static>, + pub region: Region, // Font-related. pub font: &'a Font, pub ttf: &'a ttf_parser::Face<'a>, @@ -107,7 +106,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { Self { engine, locator: locator.split(), - regions: Regions::one(base, Axes::splat(false)), + region: Region::new(base, Axes::splat(false)), font, ttf: font.ttf(), table: math_table, @@ -182,7 +181,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { self.engine, self.locator.next(&boxed.span()), styles.chain(&local), - self.regions.base(), + self.region.size, ) } @@ -194,14 +193,13 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { ) -> SourceResult { let local = TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap(); - Ok(content - .layout( - self.engine, - self.locator.next(&content.span()), - styles.chain(&local), - self.regions, - )? - .into_frame()) + layout_frame( + self.engine, + content, + self.locator.next(&content.span()), + styles.chain(&local), + self.region, + ) } /// Layout the given [`TextElem`] into a [`MathFragment`]. @@ -300,19 +298,17 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { // it will overflow. So emulate an `hbox` instead and allow the paragraph // to extend as far as needed. let spaced = text.graphemes(true).nth(1).is_some(); - let text = TextElem::packed(text).spanned(span); - let par = ParElem::new(StyleVec::wrap(eco_vec![text])); - let frame = Packed::new(par) - .spanned(span) - .layout( - self.engine, - self.locator.next(&span), - styles, - false, - Size::splat(Abs::inf()), - false, - )? - .into_frame(); + let elem = TextElem::packed(text).spanned(span); + let frame = crate::layout::layout_inline( + self.engine, + &StyleVec::wrap(eco_vec![elem]), + self.locator.next(&span), + styles, + false, + Size::splat(Abs::inf()), + false, + )? + .into_frame(); Ok(FrameFragment::new(self, styles, frame) .with_class(MathClass::Alphabetic) diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs index 054b28230..df7cc0216 100644 --- a/crates/typst/src/math/equation.rs +++ b/crates/typst/src/math/equation.rs @@ -10,9 +10,9 @@ use crate::foundations::{ }; use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Locator}; use crate::layout::{ - Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment, Fragment, Frame, - InlineElem, InlineItem, OuterHAlignment, Point, Regions, Size, SpecificAlignment, - VAlignment, + layout_frame, Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment, + Fragment, Frame, InlineElem, InlineItem, OuterHAlignment, Point, Region, Regions, + Size, SpecificAlignment, VAlignment, }; use crate::math::{ scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant, @@ -399,12 +399,11 @@ fn layout_equation_block( return Ok(Fragment::frames(frames)); }; - let pod = Regions::one(regions.base(), Axes::splat(false)); - let number = Counter::of(EquationElem::elem()) + let pod = Region::new(regions.base(), Axes::splat(false)); + let counter = Counter::of(EquationElem::elem()) .display_at_loc(engine, elem.location().unwrap(), styles, numbering)? - .spanned(span) - .layout(engine, locator.next(&()), styles, pod)? - .into_frame(); + .spanned(span); + let number = layout_frame(engine, &counter, locator.next(&()), styles, pod)?; static NUMBER_GUTTER: Em = Em::new(0.5); let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles); diff --git a/crates/typst/src/math/matrix.rs b/crates/typst/src/math/matrix.rs index 514490470..9f9b82bca 100644 --- a/crates/typst/src/math/matrix.rs +++ b/crates/typst/src/math/matrix.rs @@ -434,7 +434,7 @@ fn layout_vec_body( row_gap: Rel, alternator: LeftRightAlternator, ) -> SourceResult { - let gap = row_gap.relative_to(ctx.regions.base().y); + let gap = row_gap.relative_to(ctx.region.size.y); let denom_style = style_for_denominator(styles); let mut flat = vec![]; @@ -464,7 +464,7 @@ fn layout_mat_body( return Ok(Frame::soft(Size::zero())); } - let gap = gap.zip_map(ctx.regions.base(), Rel::relative_to); + let gap = gap.zip_map(ctx.region.size, Rel::relative_to); let half_gap = gap * 0.5; // We provide a default stroke thickness that scales diff --git a/crates/typst/src/model/document.rs b/crates/typst/src/model/document.rs index 71e9e7969..d37f6c8f8 100644 --- a/crates/typst/src/model/document.rs +++ b/crates/typst/src/model/document.rs @@ -3,12 +3,11 @@ use ecow::EcoString; use crate::diag::{bail, HintedStrResult, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - cast, elem, Args, Array, Construct, Content, Datetime, Fields, Packed, Smart, - StyleChain, Styles, Value, + cast, elem, Args, Array, Construct, Content, Datetime, Fields, Smart, StyleChain, + Styles, Value, }; -use crate::introspection::{Introspector, Locator, ManualPageCounter}; -use crate::layout::{Page, PageElem}; -use crate::realize::StyleVec; +use crate::introspection::Introspector; +use crate::layout::Page; /// The root element of a document and its metadata. /// @@ -57,11 +56,6 @@ pub struct DocumentElem { /// something other than `{auto}`. #[ghost] pub date: Smart>, - - /// The page runs. - #[internal] - #[variadic] - pub children: StyleVec, } impl Construct for DocumentElem { @@ -70,48 +64,6 @@ impl Construct for DocumentElem { } } -impl Packed { - /// Layout this document. - #[typst_macros::time(name = "document", span = self.span())] - pub fn layout( - &self, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - info: DocumentInfo, - ) -> SourceResult { - let children = self.children(); - let mut peekable = children.chain(&styles).peekable(); - let mut locator = locator.split(); - - let iter = std::iter::from_fn(|| { - let (child, styles) = peekable.next()?; - let extend_to = peekable - .peek() - .and_then(|(next, _)| *next.to_packed::()?.clear_to()?); - let locator = locator.next(&child.span()); - Some((child, styles, extend_to, locator)) - }); - - let layouts = - engine.parallelize(iter, |engine, (child, styles, extend_to, locator)| { - if let Some(page) = child.to_packed::() { - page.layout(engine, locator, styles, extend_to) - } else { - bail!(child.span(), "unexpected document child"); - } - }); - - let mut page_counter = ManualPageCounter::new(); - let mut pages = Vec::with_capacity(self.children().len()); - for result in layouts { - pages.extend(result?.finalize(engine, &mut page_counter)?); - } - - Ok(Document { pages, info, introspector: Introspector::default() }) - } -} - /// A list of authors. #[derive(Debug, Default, Clone, PartialEq, Hash)] pub struct Author(Vec); diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs index e065d3ac1..6f126ff53 100644 --- a/crates/typst/src/model/heading.rs +++ b/crates/typst/src/model/heading.rs @@ -9,7 +9,9 @@ use crate::foundations::{ use crate::introspection::{ Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink, }; -use crate::layout::{Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Regions}; +use crate::layout::{ + layout_frame, Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Region, +}; use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement}; use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize}; use crate::utils::NonZeroExt; @@ -233,15 +235,14 @@ impl Show for Packed { .spanned(span); if hanging_indent.is_auto() { - let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); + let pod = Region::new(Axes::splat(Abs::inf()), Axes::splat(false)); // We don't have a locator for the numbering here, so we just // use the measurement infrastructure for now. let link = LocatorLink::measure(location); - let size = numbering - .layout(engine, Locator::link(&link), styles, pod)? - .into_frame() - .size(); + let size = + layout_frame(engine, &numbering, Locator::link(&link), styles, pod)? + .size(); indent = size.x + SPACING_TO_NUMBERING.resolve(styles); } diff --git a/crates/typst/src/model/par.rs b/crates/typst/src/model/par.rs index 2110995f3..4d9f815e0 100644 --- a/crates/typst/src/model/par.rs +++ b/crates/typst/src/model/par.rs @@ -3,11 +3,9 @@ use std::fmt::{self, Debug, Formatter}; use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{ - elem, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, StyleChain, - Unlabellable, + elem, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, Unlabellable, }; -use crate::introspection::Locator; -use crate::layout::{Em, Fragment, Length, Size}; +use crate::layout::{Em, Length}; use crate::realize::StyleVec; /// Arranges text, spacing and inline-level elements into a paragraph. @@ -159,30 +157,6 @@ impl Construct for ParElem { } } -impl Packed { - /// Layout the paragraph into a collection of lines. - #[typst_macros::time(name = "par", span = self.span())] - pub fn layout( - &self, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - consecutive: bool, - region: Size, - expand: bool, - ) -> SourceResult { - crate::layout::layout_inline( - &self.children, - engine, - locator, - styles, - consecutive, - region, - expand, - ) - } -} - impl Debug for ParElem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "Par ")?; diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs index 721d4b4a6..eb968605b 100644 --- a/crates/typst/src/realize/mod.rs +++ b/crates/typst/src/realize/mod.rs @@ -23,7 +23,7 @@ use crate::engine::{Engine, Route}; use crate::foundations::{ Content, NativeElement, Packed, SequenceElem, Smart, StyleChain, StyledElem, Styles, }; -use crate::introspection::{Locator, SplitLocator, TagElem}; +use crate::introspection::{SplitLocator, TagElem}; use crate::layout::{ AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, FlushElem, HElem, InlineElem, PageElem, PagebreakElem, Parity, PlaceElem, VElem, @@ -36,16 +36,15 @@ use crate::model::{ use crate::syntax::Span; use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem}; -/// Realize into a `DocumentElem`, an element that is capable of root-level -/// layout. -#[typst_macros::time(name = "realize doc")] -pub fn realize_doc<'a>( - engine: &mut Engine, - locator: Locator, +/// Realize at the root-level. +#[typst_macros::time(name = "realize root")] +pub fn realize_root<'a>( + engine: &mut Engine<'a>, + locator: &mut SplitLocator<'a>, arenas: &'a Arenas<'a>, content: &'a Content, styles: StyleChain<'a>, -) -> SourceResult<(Packed, StyleChain<'a>, DocumentInfo)> { +) -> SourceResult<(StyleVec, StyleChain<'a>, DocumentInfo)> { let mut builder = Builder::new(engine, locator, arenas, true); builder.accept(content, styles)?; builder.interrupt_page(Some(styles), true)?; @@ -55,8 +54,8 @@ pub fn realize_doc<'a>( /// Realize into a `FlowElem`, an element that is capable of block-level layout. #[typst_macros::time(name = "realize flow")] pub fn realize_flow<'a>( - engine: &mut Engine, - locator: Locator, + engine: &mut Engine<'a>, + locator: &mut SplitLocator<'a>, arenas: &'a Arenas<'a>, content: &'a Content, styles: StyleChain<'a>, @@ -68,11 +67,11 @@ pub fn realize_flow<'a>( } /// Builds a document or a flow element from content. -struct Builder<'a, 'v, 't> { +struct Builder<'a, 'v> { /// The engine. - engine: &'v mut Engine<'t>, + engine: &'v mut Engine<'a>, /// Assigns unique locations to elements. - locator: SplitLocator<'v>, + locator: &'v mut SplitLocator<'a>, /// Scratch arenas for building. arenas: &'a Arenas<'a>, /// The current document building state. @@ -87,16 +86,16 @@ struct Builder<'a, 'v, 't> { cites: CiteGroupBuilder<'a>, } -impl<'a, 'v, 't> Builder<'a, 'v, 't> { +impl<'a, 'v> Builder<'a, 'v> { fn new( - engine: &'v mut Engine<'t>, - locator: Locator<'v>, + engine: &'v mut Engine<'a>, + locator: &'v mut SplitLocator<'a>, arenas: &'a Arenas<'a>, top: bool, ) -> Self { Self { engine, - locator: locator.split(), + locator, arenas, doc: top.then(DocBuilder::default), flow: FlowBuilder::default(), @@ -121,8 +120,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { // Styled elements and sequences can (at least currently) also have // labels, so this needs to happen before they are handled. - if let Some(realized) = process(self.engine, &mut self.locator, content, styles)? - { + if let Some(realized) = process(self.engine, self.locator, content, styles)? { self.engine.route.increase(); if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) { bail!( @@ -350,11 +348,11 @@ impl<'a> DocBuilder<'a> { false } - /// Turns this builder into the resulting document, along with + /// Turns this builder into the resulting page runs, along with /// its [style chain][StyleChain]. - fn finish(self) -> (Packed, StyleChain<'a>, DocumentInfo) { - let (children, trunk, span) = self.pages.finish(); - (Packed::new(DocumentElem::new(children)).spanned(span), trunk, self.info) + fn finish(self) -> (StyleVec, StyleChain<'a>, DocumentInfo) { + let (children, trunk, _) = self.pages.finish(); + (children, trunk, self.info) } } diff --git a/crates/typst/src/visualize/pattern.rs b/crates/typst/src/visualize/pattern.rs index 804c87df2..daff5fa0f 100644 --- a/crates/typst/src/visualize/pattern.rs +++ b/crates/typst/src/visualize/pattern.rs @@ -7,7 +7,7 @@ use crate::diag::{bail, SourceResult}; use crate::engine::Engine; use crate::foundations::{func, repr, scope, ty, Content, Smart, StyleChain}; use crate::introspection::Locator; -use crate::layout::{Abs, Axes, Frame, Length, Regions, Size}; +use crate::layout::{layout_frame, Abs, Axes, Frame, Length, Region, Size}; use crate::syntax::{Span, Spanned}; use crate::utils::{LazyHash, Numeric}; use crate::visualize::RelativeTo; @@ -192,8 +192,8 @@ impl Pattern { let library = world.library(); let locator = Locator::root(); let styles = StyleChain::new(&library.styles); - let pod = Regions::one(region, Axes::splat(false)); - let mut frame = body.layout(engine, locator, styles, pod)?.into_frame(); + let pod = Region::new(region, Axes::splat(false)); + let mut frame = layout_frame(engine, &body, locator, styles, pod)?; // Set the size of the frame if the size is enforced. if let Smart::Custom(size) = size { diff --git a/crates/typst/src/visualize/shape.rs b/crates/typst/src/visualize/shape.rs index e8a68fe38..b2125bf5b 100644 --- a/crates/typst/src/visualize/shape.rs +++ b/crates/typst/src/visualize/shape.rs @@ -7,8 +7,8 @@ use crate::foundations::{ }; use crate::introspection::Locator; use crate::layout::{ - Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point, Ratio, - Region, Regions, Rel, Sides, Size, + layout_frame, Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point, + Ratio, Region, Rel, Sides, Size, }; use crate::syntax::Span; use crate::utils::Get; @@ -495,16 +495,14 @@ fn layout_shape( } // Layout the child. - frame = child - .layout(engine, locator.relayout(), styles, pod.into_regions())? - .into_frame(); + frame = layout_frame(engine, child, locator.relayout(), styles, pod)?; // 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() { 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, locator, styles, quad_pod)?.into_frame(); + let quad_pod = Region::new(Size::splat(length), Axes::splat(true)); + frame = layout_frame(engine, child, locator, styles, quad_pod)?; } // Apply the inset.