diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-02 14:48:51 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-02 14:48:51 +0100 |
| commit | 56342bd972a13ffe21beaf2b87ab7eb1597704b4 (patch) | |
| tree | 78f9549141e753dde4a938670c54f3fe8695a058 /src/library/layout | |
| parent | 37ac5d966ebaf97ac79c507028cd5b742b510b89 (diff) | |
Move layout traits into library
Diffstat (limited to 'src/library/layout')
| -rw-r--r-- | src/library/layout/align.rs | 21 | ||||
| -rw-r--r-- | src/library/layout/columns.rs | 12 | ||||
| -rw-r--r-- | src/library/layout/container.rs | 26 | ||||
| -rw-r--r-- | src/library/layout/flow.rs | 45 | ||||
| -rw-r--r-- | src/library/layout/grid.rs | 32 | ||||
| -rw-r--r-- | src/library/layout/mod.rs | 789 | ||||
| -rw-r--r-- | src/library/layout/pad.rs | 14 | ||||
| -rw-r--r-- | src/library/layout/page.rs | 2 | ||||
| -rw-r--r-- | src/library/layout/place.rs | 12 | ||||
| -rw-r--r-- | src/library/layout/spacing.rs | 2 | ||||
| -rw-r--r-- | src/library/layout/stack.rs | 42 | ||||
| -rw-r--r-- | src/library/layout/transform.rs | 36 |
12 files changed, 888 insertions, 145 deletions
diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index f49763b5..2ee565cc 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -1,26 +1,23 @@ use crate::library::prelude::*; use crate::library::text::{HorizontalAlign, ParNode}; -/// Align a node along the layouting axes. +/// Align content along the layouting axes. #[derive(Debug, Hash)] pub struct AlignNode { - /// How to align the node horizontally and vertically. + /// How to align the content horizontally and vertically. pub aligns: Axes<Option<RawAlign>>, - /// The node to be aligned. + /// The content to be aligned. pub child: Content, } -#[node(Layout)] +#[node(LayoutBlock)] impl AlignNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; if let Axes { x: Some(x), y: None } = aligns { - if body - .to::<dyn Layout>() - .map_or(true, |node| node.level() == Level::Inline) - { + if !body.has::<dyn LayoutBlock>() { return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x))); } } @@ -29,8 +26,8 @@ impl AlignNode { } } -impl Layout for AlignNode { - fn layout( +impl LayoutBlock for AlignNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -62,8 +59,4 @@ impl Layout for AlignNode { Ok(frames) } - - fn level(&self) -> Level { - Level::Block - } } diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index 79d98e11..df259eab 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -11,7 +11,7 @@ pub struct ColumnsNode { pub child: Content, } -#[node(Layout)] +#[node(LayoutBlock)] impl ColumnsNode { /// The size of the gutter space between each column. #[property(resolve)] @@ -26,8 +26,8 @@ impl ColumnsNode { } } -impl Layout for ColumnsNode { - fn layout( +impl LayoutBlock for ColumnsNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -66,7 +66,7 @@ impl Layout for ColumnsNode { // Stitch together the columns for each region. for region in regions.iter().take(total_regions) { - // The height should be the parent height if the node shall expand. + // 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. @@ -100,10 +100,6 @@ impl Layout for ColumnsNode { Ok(finished) } - - fn level(&self) -> Level { - Level::Block - } } /// A column break. diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs index 60f9139b..023809d0 100644 --- a/src/library/layout/container.rs +++ b/src/library/layout/container.rs @@ -1,15 +1,15 @@ use crate::library::prelude::*; -/// An inline-level container that sizes content and places it into a paragraph. +/// An inline-level container that sizes content. #[derive(Debug, Clone, Hash)] pub struct BoxNode { - /// How to size the node horizontally and vertically. + /// How to size the content horizontally and vertically. pub sizing: Axes<Option<Rel<Length>>>, - /// The node to be sized. + /// The content to be sized. pub child: Content, } -#[node(Layout)] +#[node(LayoutInline)] impl BoxNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let width = args.named("width")?; @@ -19,8 +19,8 @@ impl BoxNode { } } -impl Layout for BoxNode { - fn layout( +impl LayoutInline for BoxNode { + fn layout_inline( &self, world: Tracked<dyn World>, regions: &Regions, @@ -55,25 +55,21 @@ impl Layout for BoxNode { Ok(frames) } - - fn level(&self) -> Level { - Level::Inline - } } /// A block-level container that places content into a separate flow. #[derive(Debug, Clone, Hash)] pub struct BlockNode(pub Content); -#[node(Layout)] +#[node(LayoutBlock)] impl BlockNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self(args.eat()?.unwrap_or_default()).pack()) } } -impl Layout for BlockNode { - fn layout( +impl LayoutBlock for BlockNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -81,8 +77,4 @@ impl Layout for BlockNode { ) -> SourceResult<Vec<Frame>> { self.0.layout_block(world, regions, styles) } - - fn level(&self) -> Level { - Level::Block - } } diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index 01ee9dc9..f4d18699 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -4,7 +4,7 @@ use super::{AlignNode, PlaceNode, Spacing}; use crate::library::prelude::*; use crate::library::text::ParNode; -/// Arrange spacing, paragraphs and other block-level nodes into a flow. +/// Arrange spacing, paragraphs and block-level nodes into a flow. /// /// This node is reponsible for layouting both the top-level content flow and /// the contents of boxes. @@ -16,17 +16,17 @@ pub struct FlowNode(pub StyleVec<FlowChild>); pub enum FlowChild { /// Vertical spacing between other children. Spacing(Spacing), - /// An arbitrary block-level node. - Node(Content), + /// Arbitrary block-level content. + Block(Content), /// A column / region break. Colbreak, } -#[node(Layout)] +#[node(LayoutBlock)] impl FlowNode {} -impl Layout for FlowNode { - fn layout( +impl LayoutBlock for FlowNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -40,8 +40,8 @@ impl Layout for FlowNode { FlowChild::Spacing(kind) => { layouter.layout_spacing(*kind, styles); } - FlowChild::Node(ref node) => { - layouter.layout_node(world, node, styles)?; + FlowChild::Block(block) => { + layouter.layout_block(world, block, styles)?; } FlowChild::Colbreak => { layouter.finish_region(); @@ -51,10 +51,6 @@ impl Layout for FlowNode { Ok(layouter.finish()) } - - fn level(&self) -> Level { - Level::Block - } } impl Debug for FlowNode { @@ -68,7 +64,7 @@ impl Debug for FlowChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(kind) => write!(f, "{:?}", kind), - Self::Node(node) => node.fmt(f), + Self::Block(block) => block.fmt(f), Self::Colbreak => f.pad("Colbreak"), } } @@ -96,7 +92,7 @@ pub struct FlowLayouter { used: Size, /// The sum of fractions in the current region. fr: Fr, - /// Spacing and layouted nodes. + /// Spacing and layouted blocks. items: Vec<FlowItem>, /// Finished frames for previous regions. finished: Vec<Frame>, @@ -108,7 +104,7 @@ enum FlowItem { Absolute(Abs), /// Fractional spacing between other items. Fractional(Fr), - /// A frame for a layouted child node and how to align it. + /// A frame for a layouted block and how to align it. Frame(Frame, Axes<Align>), /// An absolutely placed frame. Placed(Frame), @@ -153,11 +149,11 @@ impl FlowLayouter { } } - /// Layout a node. - pub fn layout_node( + /// Layout a block. + pub fn layout_block( &mut self, world: Tracked<dyn World>, - node: &Content, + block: &Content, styles: StyleChain, ) -> SourceResult<()> { // Don't even try layouting into a full region. @@ -167,27 +163,28 @@ impl FlowLayouter { // Placed nodes that are out of flow produce placed items which aren't // aligned later. - if let Some(placed) = node.downcast::<PlaceNode>() { + if let Some(placed) = block.downcast::<PlaceNode>() { if placed.out_of_flow() { - let frame = node.layout_block(world, &self.regions, styles)?.remove(0); + let frame = block.layout_block(world, &self.regions, styles)?.remove(0); self.items.push(FlowItem::Placed(frame)); return Ok(()); } } - // How to align the node. + // How to align the block. let aligns = Axes::new( // For non-expanding paragraphs it is crucial that we align the // whole paragraph as it is itself aligned. styles.get(ParNode::ALIGN), - // Vertical align node alignment is respected by the flow node. - node.downcast::<AlignNode>() + // Vertical align node alignment is respected by the flow. + block + .downcast::<AlignNode>() .and_then(|aligned| aligned.aligns.y) .map(|align| align.resolve(styles)) .unwrap_or(Align::Top), ); - let frames = node.layout_block(world, &self.regions, styles)?; + let frames = block.layout_block(world, &self.regions, styles)?; let len = frames.len(); for (i, mut frame) in frames.into_iter().enumerate() { // Set the generic block role. diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 7e5cbbd5..1bb67691 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -1,17 +1,17 @@ use crate::library::prelude::*; -/// Arrange nodes in a grid. +/// Arrange content in a grid. #[derive(Debug, Hash)] pub struct GridNode { /// Defines sizing for content rows and columns. pub tracks: Axes<Vec<TrackSizing>>, /// Defines sizing of gutter rows and columns between content. pub gutter: Axes<Vec<TrackSizing>>, - /// The nodes to be arranged in a grid. + /// The content to be arranged in a grid. pub cells: Vec<Content>, } -#[node(Layout)] +#[node(LayoutBlock)] impl GridNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let columns = args.named("columns")?.unwrap_or_default(); @@ -31,8 +31,8 @@ impl GridNode { } } -impl Layout for GridNode { - fn layout( +impl LayoutBlock for GridNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -51,10 +51,6 @@ impl Layout for GridNode { // Measure the columns and layout the grid row-by-row. layouter.layout() } - - fn level(&self) -> Level { - Level::Block - } } /// Defines how to size a grid cell along an axis. @@ -293,7 +289,7 @@ impl<'a> GridLayouter<'a> { let mut resolved = Abs::zero(); for y in 0 .. self.rows.len() { - if let Some(node) = self.cell(x, y) { + if let Some(cell) = self.cell(x, y) { let size = Size::new(available, self.regions.base.y); let mut pod = Regions::one(size, self.regions.base, Axes::splat(false)); @@ -307,7 +303,7 @@ impl<'a> GridLayouter<'a> { } let frame = - node.layout_block(self.world, &pod, self.styles)?.remove(0); + cell.layout_block(self.world, &pod, self.styles)?.remove(0); resolved.set_max(frame.width()); } } @@ -366,7 +362,7 @@ impl<'a> GridLayouter<'a> { // Determine the size for each region of the row. for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { + if let Some(cell) = self.cell(x, y) { let mut pod = self.regions.clone(); pod.first.x = rcol; pod.base.x = rcol; @@ -376,7 +372,7 @@ impl<'a> GridLayouter<'a> { pod.base.x = self.regions.base.x; } - let mut sizes = node + let mut sizes = cell .layout_block(self.world, &pod, self.styles)? .into_iter() .map(|frame| frame.height()); @@ -456,7 +452,7 @@ impl<'a> GridLayouter<'a> { let mut pos = Point::zero(); for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { + if let Some(cell) = self.cell(x, y) { let size = Size::new(rcol, height); // Set the base to the region's base for auto rows and to the @@ -466,7 +462,7 @@ impl<'a> GridLayouter<'a> { .select(self.regions.base, size); let pod = Regions::one(size, base, Axes::splat(true)); - let frame = node.layout_block(self.world, &pod, self.styles)?.remove(0); + let frame = cell.layout_block(self.world, &pod, self.styles)?.remove(0); match frame.role() { Some(Role::ListLabel | Role::ListItemBody) => { output.apply_role(Role::ListItem) @@ -504,7 +500,7 @@ impl<'a> GridLayouter<'a> { // Layout the row. let mut pos = Point::zero(); for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { + if let Some(cell) = self.cell(x, y) { pod.first.x = rcol; pod.base.x = rcol; @@ -514,7 +510,7 @@ impl<'a> GridLayouter<'a> { } // Push the layouted frames into the individual output frames. - let frames = node.layout_block(self.world, &pod, self.styles)?; + let frames = cell.layout_block(self.world, &pod, self.styles)?; for (output, frame) in outputs.iter_mut().zip(frames) { match frame.role() { Some(Role::ListLabel | Role::ListItemBody) => { @@ -578,7 +574,7 @@ impl<'a> GridLayouter<'a> { Ok(()) } - /// Get the node in the cell in column `x` and row `y`. + /// Get the content of the cell in column `x` and row `y`. /// /// Returns `None` if it's a gutter cell. #[track_caller] diff --git a/src/library/layout/mod.rs b/src/library/layout/mod.rs index 02276f22..000cb212 100644 --- a/src/library/layout/mod.rs +++ b/src/library/layout/mod.rs @@ -23,3 +23,792 @@ pub use place::*; pub use spacing::*; pub use stack::*; pub use transform::*; + +use std::mem; + +use comemo::Tracked; +use typed_arena::Arena; + +use crate::diag::SourceResult; +use crate::frame::Frame; +use crate::geom::*; +use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST}; +use crate::library::text::{ + LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, +}; +use crate::model::{ + capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain, + StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target, +}; +use crate::World; + +/// The root-level layout. +#[capability] +pub trait Layout: 'static + Sync + Send { + /// Layout into one frame per page. + fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>>; +} + +impl Layout for Content { + #[comemo::memoize] + fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>> { + let styles = StyleChain::with_root(&world.config().styles); + let scratch = Scratch::default(); + + let mut builder = Builder::new(world, &scratch, true); + builder.accept(self, styles)?; + + let (doc, shared) = builder.into_doc(styles)?; + doc.layout(world, shared) + } +} + +/// Block-level layout. +#[capability] +pub trait LayoutBlock: 'static + Sync + Send { + /// Layout into one frame per region. + fn layout_block( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>>; +} + +impl LayoutBlock for Content { + #[comemo::memoize] + fn layout_block( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + if let Some(node) = self.to::<dyn LayoutBlock>() { + let barrier = StyleEntry::Barrier(Barrier::new(self.id())); + let styles = barrier.chain(&styles); + return node.layout_block(world, regions, styles); + } + + let scratch = Scratch::default(); + let mut builder = Builder::new(world, &scratch, false); + builder.accept(self, styles)?; + let (flow, shared) = builder.into_flow(styles)?; + flow.layout_block(world, regions, shared) + } +} + +/// Inline-level layout. +#[capability] +pub trait LayoutInline: 'static + Sync + Send { + /// Layout into a single frame. + fn layout_inline( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>>; +} + +impl LayoutInline for Content { + #[comemo::memoize] + fn layout_inline( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + if let Some(node) = self.to::<dyn LayoutInline>() { + let barrier = StyleEntry::Barrier(Barrier::new(self.id())); + let styles = barrier.chain(&styles); + return node.layout_inline(world, regions, styles); + } + + if let Some(node) = self.to::<dyn LayoutBlock>() { + let barrier = StyleEntry::Barrier(Barrier::new(self.id())); + let styles = barrier.chain(&styles); + return node.layout_block(world, regions, styles); + } + + let scratch = Scratch::default(); + let mut builder = Builder::new(world, &scratch, false); + builder.accept(self, styles)?; + let (flow, shared) = builder.into_flow(styles)?; + flow.layout_block(world, regions, shared) + } +} + +/// A sequence of regions to layout into. +#[derive(Debug, Clone, Hash)] +pub struct Regions { + /// The (remaining) size of the first region. + pub first: Size, + /// The base size for relative sizing. + pub base: Size, + /// The height of followup regions. The width is the same for all regions. + pub backlog: Vec<Abs>, + /// 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<Abs>, + /// Whether nodes should expand to fill the regions instead of shrinking to + /// fit the content. + pub expand: Axes<bool>, +} + +impl Regions { + /// Create a new region sequence with exactly one region. + pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self { + Self { + first: size, + base, + backlog: vec![], + last: None, + expand, + } + } + + /// Create a new sequence of same-size regions that repeats indefinitely. + pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self { + Self { + first: size, + base, + backlog: vec![], + last: Some(size.y), + expand, + } + } + + /// Create new regions where all sizes are mapped with `f`. + /// + /// Note that since all regions must have the same width, the width returned + /// by `f` is ignored for the backlog and the final region. + pub fn map<F>(&self, mut f: F) -> Self + where + F: FnMut(Size) -> Size, + { + let x = self.first.x; + Self { + first: f(self.first), + base: f(self.base), + backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(), + last: self.last.map(|y| f(Size::new(x, y)).y), + expand: self.expand, + } + } + + /// Whether the first region is full and a region break is called for. + pub fn is_full(&self) -> bool { + Abs::zero().fits(self.first.y) && !self.in_last() + } + + /// Whether the first region is the last usable region. + /// + /// If this is true, calling `next()` will have no effect. + pub fn in_last(&self) -> bool { + self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height) + } + + /// Advance to the next region if there is any. + pub fn next(&mut self) { + if let Some(height) = (!self.backlog.is_empty()) + .then(|| self.backlog.remove(0)) + .or(self.last) + { + self.first.y = height; + self.base.y = height; + } + } + + /// An iterator that returns the sizes of the first and all following + /// regions, equivalently to what would be produced by calling + /// [`next()`](Self::next) repeatedly until all regions are exhausted. + /// This iterater may be infinite. + pub fn iter(&self) -> impl Iterator<Item = Size> + '_ { + let first = std::iter::once(self.first); + let backlog = self.backlog.iter(); + let last = self.last.iter().cycle(); + first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) + } +} + +/// Builds a document or a flow node from content. +struct Builder<'a> { + /// The core context. + world: Tracked<'a, dyn World>, + /// Scratch arenas for building. + scratch: &'a Scratch<'a>, + /// The current document building state. + doc: Option<DocBuilder<'a>>, + /// The current flow building state. + flow: FlowBuilder<'a>, + /// The current paragraph building state. + par: ParBuilder<'a>, + /// The current list building state. + list: ListBuilder<'a>, +} + +/// Temporary storage arenas for building. +#[derive(Default)] +struct Scratch<'a> { + /// An arena where intermediate style chains are stored. + styles: Arena<StyleChain<'a>>, + /// An arena where intermediate content resulting from show rules is stored. + templates: Arena<Content>, +} + +impl<'a> Builder<'a> { + pub fn new( + world: Tracked<'a, dyn World>, + scratch: &'a Scratch<'a>, + top: bool, + ) -> Self { + Self { + world, + scratch, + doc: top.then(|| DocBuilder::default()), + flow: FlowBuilder::default(), + par: ParBuilder::default(), + list: ListBuilder::default(), + } + } + + pub fn into_doc( + mut self, + styles: StyleChain<'a>, + ) -> SourceResult<(DocNode, StyleChain<'a>)> { + self.interrupt(Interruption::Page, styles, true)?; + let (pages, shared) = self.doc.unwrap().pages.finish(); + Ok((DocNode(pages), shared)) + } + + pub fn into_flow( + mut self, + styles: StyleChain<'a>, + ) -> SourceResult<(FlowNode, StyleChain<'a>)> { + self.interrupt(Interruption::Par, styles, false)?; + let (children, shared) = self.flow.0.finish(); + Ok((FlowNode(children), shared)) + } + + pub fn accept( + &mut self, + content: &'a Content, + styles: StyleChain<'a>, + ) -> SourceResult<()> { + if let Some(text) = content.downcast::<TextNode>() { + if let Some(realized) = styles.apply(self.world, Target::Text(&text.0))? { + let stored = self.scratch.templates.alloc(realized); + return self.accept(stored, styles); + } + } else if let Some(styled) = content.downcast::<StyledNode>() { + return self.styled(styled, styles); + } else if let Some(seq) = content.downcast::<SequenceNode>() { + return self.sequence(seq, styles); + } else if content.has::<dyn Show>() { + if self.show(&content, styles)? { + return Ok(()); + } + } + + if self.list.accept(content, styles) { + return Ok(()); + } + + self.interrupt(Interruption::List, styles, false)?; + + if content.is::<ListItem>() { + self.list.accept(content, styles); + return Ok(()); + } + + if self.par.accept(content, styles) { + return Ok(()); + } + + self.interrupt(Interruption::Par, styles, false)?; + + if self.flow.accept(content, styles) { + return Ok(()); + } + + let keep = content + .downcast::<PagebreakNode>() + .map_or(false, |pagebreak| !pagebreak.weak); + self.interrupt(Interruption::Page, styles, keep)?; + + if let Some(doc) = &mut self.doc { + doc.accept(content, styles); + } + + // We might want to issue a warning or error for content that wasn't + // handled (e.g. a pagebreak in a flow building process). However, we + // don't have the spans here at the moment. + Ok(()) + } + + fn show( + &mut self, + content: &'a Content, + styles: StyleChain<'a>, + ) -> SourceResult<bool> { + if let Some(mut realized) = styles.apply(self.world, Target::Node(content))? { + let mut map = StyleMap::new(); + let barrier = Barrier::new(content.id()); + map.push(StyleEntry::Barrier(barrier)); + map.push(StyleEntry::Barrier(barrier)); + realized = realized.styled_with_map(map); + let stored = self.scratch.templates.alloc(realized); + self.accept(stored, styles)?; + Ok(true) + } else { + Ok(false) + } + } + + fn styled( + &mut self, + styled: &'a StyledNode, + styles: StyleChain<'a>, + ) -> SourceResult<()> { + let stored = self.scratch.styles.alloc(styles); + let styles = styled.map.chain(stored); + let intr = styled.map.interruption(); + + if let Some(intr) = intr { + self.interrupt(intr, styles, false)?; + } + + self.accept(&styled.sub, styles)?; + + if let Some(intr) = intr { + self.interrupt(intr, styles, true)?; + } + + Ok(()) + } + + fn interrupt( + &mut self, + intr: Interruption, + styles: StyleChain<'a>, + keep: bool, + ) -> SourceResult<()> { + if intr >= Interruption::List && !self.list.is_empty() { + mem::take(&mut self.list).finish(self)?; + } + + if intr >= Interruption::Par { + if !self.par.is_empty() { + mem::take(&mut self.par).finish(self); + } + } + + if intr >= Interruption::Page { + if let Some(doc) = &mut self.doc { + if !self.flow.is_empty() || (doc.keep_next && keep) { + mem::take(&mut self.flow).finish(doc, styles); + } + doc.keep_next = !keep; + } + } + + Ok(()) + } + + fn sequence( + &mut self, + seq: &'a SequenceNode, + styles: StyleChain<'a>, + ) -> SourceResult<()> { + for content in &seq.0 { + self.accept(content, styles)?; + } + Ok(()) + } +} + +/// Accepts pagebreaks and pages. +struct DocBuilder<'a> { + /// The page runs built so far. + pages: StyleVecBuilder<'a, PageNode>, + /// Whether to keep a following page even if it is empty. + keep_next: bool, +} + +impl<'a> DocBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) { + if let Some(pagebreak) = content.downcast::<PagebreakNode>() { + self.keep_next = !pagebreak.weak; + } + + if let Some(page) = content.downcast::<PageNode>() { + self.pages.push(page.clone(), styles); + self.keep_next = false; + } + } +} + +impl Default for DocBuilder<'_> { + fn default() -> Self { + Self { + pages: StyleVecBuilder::new(), + keep_next: true, + } + } +} + +/// Accepts flow content. +#[derive(Default)] +struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>); + +impl<'a> FlowBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + // Weak flow elements: + // Weakness | Element + // 0 | weak colbreak + // 1 | weak fractional spacing + // 2 | weak spacing + // 3 | generated weak spacing + // 4 | generated weak fractional spacing + // 5 | par spacing + + if let Some(_) = content.downcast::<ParbreakNode>() { + /* Nothing to do */ + } else if let Some(colbreak) = content.downcast::<ColbreakNode>() { + if colbreak.weak { + self.0.weak(FlowChild::Colbreak, styles, 0); + } else { + self.0.destructive(FlowChild::Colbreak, styles); + } + } else if let Some(vertical) = content.downcast::<VNode>() { + let child = FlowChild::Spacing(vertical.amount); + let frac = vertical.amount.is_fractional(); + if vertical.weak { + let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated); + self.0.weak(child, styles, weakness); + } else if frac { + self.0.destructive(child, styles); + } else { + self.0.ignorant(child, styles); + } + } else if content.has::<dyn LayoutBlock>() { + let child = FlowChild::Block(content.clone()); + if content.is::<PlaceNode>() { + self.0.ignorant(child, styles); + } else { + self.0.supportive(child, styles); + } + } else { + return false; + } + + true + } + + fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) { + let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) { + styles.get(ParNode::LEADING).into() + } else { + styles.get(ParNode::SPACING).into() + }; + + self.0.weak(FlowChild::Spacing(amount), styles, 5); + self.0.supportive(FlowChild::Block(par.pack()), styles); + self.0.weak(FlowChild::Spacing(amount), styles, 5); + } + + fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) { + let (flow, shared) = self.0.finish(); + let styles = if flow.is_empty() { styles } else { shared }; + let node = PageNode(FlowNode(flow).pack()); + doc.pages.push(node, styles); + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +/// Accepts paragraph content. +#[derive(Default)] +struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>); + +impl<'a> ParBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + // Weak par elements: + // Weakness | Element + // 0 | weak fractional spacing + // 1 | weak spacing + // 2 | space + + if content.is::<SpaceNode>() { + self.0.weak(ParChild::Text(' '.into()), styles, 2); + } else if let Some(linebreak) = content.downcast::<LinebreakNode>() { + let c = if linebreak.justify { '\u{2028}' } else { '\n' }; + self.0.destructive(ParChild::Text(c.into()), styles); + } else if let Some(horizontal) = content.downcast::<HNode>() { + let child = ParChild::Spacing(horizontal.amount); + let frac = horizontal.amount.is_fractional(); + if horizontal.weak { + let weakness = u8::from(!frac); + self.0.weak(child, styles, weakness); + } else if frac { + self.0.destructive(child, styles); + } else { + self.0.ignorant(child, styles); + } + } else if let Some(quote) = content.downcast::<SmartQuoteNode>() { + self.0.supportive(ParChild::Quote { double: quote.double }, styles); + } else if let Some(text) = content.downcast::<TextNode>() { + self.0.supportive(ParChild::Text(text.0.clone()), styles); + } else if content.has::<dyn LayoutInline>() { + self.0.supportive(ParChild::Inline(content.clone()), styles); + } else { + return false; + } + + true + } + + fn finish(self, parent: &mut Builder<'a>) { + let (mut children, shared) = self.0.finish(); + if children.is_empty() { + return; + } + + // Paragraph indent should only apply if the paragraph starts with + // text and follows directly after another paragraph. + let indent = shared.get(ParNode::INDENT); + if !indent.is_zero() + && children + .items() + .find_map(|child| match child { + ParChild::Spacing(_) => None, + ParChild::Text(_) | ParChild::Quote { .. } => Some(true), + ParChild::Inline(_) => Some(false), + }) + .unwrap_or_default() + && parent + .flow + .0 + .items() + .rev() + .find_map(|child| match child { + FlowChild::Spacing(_) => None, + FlowChild::Block(content) => Some(content.is::<ParNode>()), + FlowChild::Colbreak => Some(false), + }) + .unwrap_or_default() + { + children.push_front(ParChild::Spacing(indent.into())); + } + + parent.flow.par(ParNode(children), shared, !indent.is_zero()); + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +/// Accepts list / enum items, spaces, paragraph breaks. +struct ListBuilder<'a> { + /// The list items collected so far. + items: StyleVecBuilder<'a, ListItem>, + /// Whether the list contains no paragraph breaks. + tight: bool, + /// Whether the list can be attached. + attachable: bool, + /// Trailing content for which it is unclear whether it is part of the list. + staged: Vec<(&'a Content, StyleChain<'a>)>, +} + +impl<'a> ListBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + if self.items.is_empty() { + if content.is::<ParbreakNode>() { + self.attachable = false; + } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() { + self.attachable = true; + } + } + + if let Some(item) = content.downcast::<ListItem>() { + if self + .items + .items() + .next() + .map_or(true, |first| item.kind() == first.kind()) + { + self.items.push(item.clone(), styles); + self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>()); + } else { + return false; + } + } else if !self.items.is_empty() + && (content.is::<SpaceNode>() || content.is::<ParbreakNode>()) + { + self.staged.push((content, styles)); + } else { + return false; + } + + true + } + + fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> { + let (items, shared) = self.items.finish(); + let kind = match items.items().next() { + Some(item) => item.kind(), + None => return Ok(()), + }; + + let tight = self.tight; + let attached = tight && self.attachable; + let content = match kind { + LIST => ListNode::<LIST> { tight, attached, items }.pack(), + ENUM => ListNode::<ENUM> { tight, attached, items }.pack(), + DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(), + }; + + let stored = parent.scratch.templates.alloc(content); + parent.accept(stored, shared)?; + + for (content, styles) in self.staged { + parent.accept(content, styles)?; + } + + parent.list.attachable = true; + + Ok(()) + } + + fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +impl Default for ListBuilder<'_> { + fn default() -> Self { + Self { + items: StyleVecBuilder::default(), + tight: true, + attachable: true, + staged: vec![], + } + } +} + +/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. +struct CollapsingBuilder<'a, T> { + /// The internal builder. + builder: StyleVecBuilder<'a, T>, + /// Staged weak and ignorant items that we can't yet commit to the builder. + /// The option is `Some(_)` for weak items and `None` for ignorant items. + staged: Vec<(T, StyleChain<'a>, Option<u8>)>, + /// What the last non-ignorant item was. + last: Last, +} + +/// What the last non-ignorant item was. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Last { + Weak, + Destructive, + Supportive, +} + +impl<'a, T> CollapsingBuilder<'a, T> { + /// Create a new style-vec builder. + pub fn new() -> Self { + Self { + builder: StyleVecBuilder::new(), + staged: vec![], + last: Last::Destructive, + } + } + + /// Whether the builder is empty. + pub fn is_empty(&self) -> bool { + self.builder.is_empty() && self.staged.is_empty() + } + + /// Can only exist when there is at least one supportive item to its left + /// and to its right, with no destructive items in between. There may be + /// ignorant items in between in both directions. + /// + /// Between weak items, there may be at least one per layer and among the + /// candidates the strongest one (smallest `weakness`) wins. When tied, + /// the one that compares larger through `PartialOrd` wins. + pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8) + where + T: PartialOrd, + { + if self.last == Last::Destructive { + return; + } + + if self.last == Last::Weak { + if let Some(i) = + self.staged.iter().position(|(prev_item, _, prev_weakness)| { + prev_weakness.map_or(false, |prev_weakness| { + weakness < prev_weakness + || (weakness == prev_weakness && item > *prev_item) + }) + }) + { + self.staged.remove(i); + } else { + return; + } + } + + self.staged.push((item, styles, Some(weakness))); + self.last = Last::Weak; + } + + /// Forces nearby weak items to collapse. + pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) { + self.flush(false); + self.builder.push(item, styles); + self.last = Last::Destructive; + } + + /// Allows nearby weak items to exist. + pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) { + self.flush(true); + self.builder.push(item, styles); + self.last = Last::Supportive; + } + + /// Has no influence on other items. + pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { + self.staged.push((item, styles, None)); + } + + /// Iterate over the contained items. + pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> { + self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) + } + + /// Return the finish style vec and the common prefix chain. + pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) { + self.flush(false); + self.builder.finish() + } + + /// Push the staged items, filtering out weak items if `supportive` is + /// false. + fn flush(&mut self, supportive: bool) { + for (item, styles, meta) in self.staged.drain(..) { + if supportive || meta.is_none() { + self.builder.push(item, styles); + } + } + } +} + +impl<'a, T> Default for CollapsingBuilder<'a, T> { + fn default() -> Self { + Self::new() + } +} diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index effdd5f8..920660d6 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -1,15 +1,15 @@ use crate::library::prelude::*; -/// Pad a node at the sides. +/// Pad content at the sides. #[derive(Debug, Hash)] pub struct PadNode { /// The amount of padding. pub padding: Sides<Rel<Length>>, - /// The child node whose sides to pad. + /// The content whose sides to pad. pub child: Content, } -#[node(Layout)] +#[node(LayoutBlock)] impl PadNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let all = args.named("rest")?.or(args.find()?); @@ -25,8 +25,8 @@ impl PadNode { } } -impl Layout for PadNode { - fn layout( +impl LayoutBlock for PadNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -51,10 +51,6 @@ impl Layout for PadNode { Ok(frames) } - - fn level(&self) -> Level { - Level::Block - } } /// Shrink a size by padding relative to the size itself. diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index f5821ae6..8d081749 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -80,7 +80,7 @@ impl PageNode { let mut child = self.0.clone(); - // Realize columns with columns node. + // Realize columns. let columns = styles.get(Self::COLUMNS); if columns.get() > 1 { child = ColumnsNode { columns, child: self.0.clone() }.pack(); diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs index 42ab0fba..ee38ebe6 100644 --- a/src/library/layout/place.rs +++ b/src/library/layout/place.rs @@ -1,11 +1,11 @@ use super::AlignNode; use crate::library::prelude::*; -/// Place a node at an absolute position. +/// Place content at an absolute position. #[derive(Debug, Hash)] pub struct PlaceNode(pub Content); -#[node(Layout)] +#[node(LayoutBlock)] impl PlaceNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start))); @@ -16,8 +16,8 @@ impl PlaceNode { } } -impl Layout for PlaceNode { - fn layout( +impl LayoutBlock for PlaceNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -42,10 +42,6 @@ impl Layout for PlaceNode { Ok(frames) } - - fn level(&self) -> Level { - Level::Block - } } impl PlaceNode { diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs index 6df5cde5..c410eee7 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -78,7 +78,7 @@ castable! { Value::Fraction(v) => Self::Fractional(v), } -/// Spacing around and between block-level nodes, relative to paragraph spacing. +/// Spacing around and between blocks, relative to paragraph spacing. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct BlockSpacing(Rel<Length>); diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index 9ea7965d..e1e70de9 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -3,7 +3,7 @@ use crate::library::prelude::*; use crate::library::text::ParNode; use crate::model::StyledNode; -/// Arrange nodes and spacing along an axis. +/// Arrange content and spacing along an axis. #[derive(Debug, Hash)] pub struct StackNode { /// The stacking direction. @@ -14,7 +14,7 @@ pub struct StackNode { pub children: Vec<StackChild>, } -#[node(Layout)] +#[node(LayoutBlock)] impl StackNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self { @@ -26,8 +26,8 @@ impl StackNode { } } -impl Layout for StackNode { - fn layout( +impl LayoutBlock for StackNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -35,7 +35,7 @@ impl Layout for StackNode { ) -> SourceResult<Vec<Frame>> { let mut layouter = StackLayouter::new(self.dir, regions, styles); - // Spacing to insert before the next node. + // Spacing to insert before the next block. let mut deferred = None; for child in &self.children { @@ -44,12 +44,12 @@ impl Layout for StackNode { layouter.layout_spacing(*kind); deferred = None; } - StackChild::Node(node) => { + StackChild::Block(block) => { if let Some(kind) = deferred { layouter.layout_spacing(kind); } - layouter.layout_node(world, node, styles)?; + layouter.layout_block(world, block, styles)?; deferred = self.spacing; } } @@ -57,26 +57,22 @@ impl Layout for StackNode { Ok(layouter.finish()) } - - fn level(&self) -> Level { - Level::Block - } } /// A child of a stack node. #[derive(Hash)] pub enum StackChild { - /// Spacing between other nodes. + /// Spacing between other children. Spacing(Spacing), - /// An arbitrary node. - Node(Content), + /// Arbitrary block-level content. + Block(Content), } impl Debug for StackChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(kind) => kind.fmt(f), - Self::Node(node) => node.fmt(f), + Self::Block(block) => block.fmt(f), } } } @@ -88,7 +84,7 @@ castable! { Value::Ratio(v) => Self::Spacing(Spacing::Relative(v.into())), Value::Relative(v) => Self::Spacing(Spacing::Relative(v)), Value::Fraction(v) => Self::Spacing(Spacing::Fractional(v)), - Value::Content(v) => Self::Node(v), + Value::Content(v) => Self::Block(v), } /// Performs stack layout. @@ -122,7 +118,7 @@ enum StackItem { Absolute(Abs), /// Fractional spacing between other items. Fractional(Fr), - /// A frame for a layouted child node. + /// A frame for a layouted block. Frame(Frame, Align), } @@ -171,11 +167,11 @@ impl<'a> StackLayouter<'a> { } } - /// Layout an arbitrary node. - pub fn layout_node( + /// Layout an arbitrary block. + pub fn layout_block( &mut self, world: Tracked<dyn World>, - node: &Content, + block: &Content, styles: StyleChain, ) -> SourceResult<()> { if self.regions.is_full() { @@ -184,12 +180,12 @@ impl<'a> StackLayouter<'a> { // Block-axis alignment of the `AlignNode` is respected // by the stack node. - let align = node + let align = block .downcast::<AlignNode>() .and_then(|node| node.aligns.get(self.axis)) .map(|align| align.resolve(styles)) .unwrap_or_else(|| { - if let Some(styled) = node.downcast::<StyledNode>() { + if let Some(styled) = block.downcast::<StyledNode>() { let map = &styled.map; if map.contains(ParNode::ALIGN) { return StyleChain::with_root(map).get(ParNode::ALIGN); @@ -199,7 +195,7 @@ impl<'a> StackLayouter<'a> { self.dir.start().into() }); - let frames = node.layout_block(world, &self.regions, styles)?; + let frames = block.layout_block(world, &self.regions, styles)?; let len = frames.len(); for (i, mut frame) in frames.into_iter().enumerate() { // Set the generic block role. diff --git a/src/library/layout/transform.rs b/src/library/layout/transform.rs index 061efa6b..a73a1827 100644 --- a/src/library/layout/transform.rs +++ b/src/library/layout/transform.rs @@ -1,16 +1,16 @@ use crate::geom::Transform; use crate::library::prelude::*; -/// Move a node without affecting layout. +/// Move content without affecting layout. #[derive(Debug, Hash)] pub struct MoveNode { - /// The offset by which to move the node. + /// The offset by which to move the content. pub delta: Axes<Rel<Length>>, - /// The node whose contents should be moved. + /// The content that should be moved. pub child: Content, } -#[node(Layout)] +#[node(LayoutInline)] impl MoveNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let dx = args.named("dx")?.unwrap_or_default(); @@ -23,8 +23,8 @@ impl MoveNode { } } -impl Layout for MoveNode { - fn layout( +impl LayoutInline for MoveNode { + fn layout_inline( &self, world: Tracked<dyn World>, regions: &Regions, @@ -40,28 +40,24 @@ impl Layout for MoveNode { Ok(frames) } - - fn level(&self) -> Level { - Level::Inline - } } -/// Transform a node without affecting layout. +/// Transform content without affecting layout. #[derive(Debug, Hash)] pub struct TransformNode<const T: TransformKind> { - /// Transformation to apply to the contents. + /// Transformation to apply to the content. pub transform: Transform, - /// The node whose contents should be transformed. + /// The content that should be transformed. pub child: Content, } -/// Rotate a node without affecting layout. +/// Rotate content without affecting layout. pub type RotateNode = TransformNode<ROTATE>; -/// Scale a node without affecting layout. +/// Scale content without affecting layout. pub type ScaleNode = TransformNode<SCALE>; -#[node(Layout)] +#[node(LayoutInline)] impl<const T: TransformKind> TransformNode<T> { /// The origin of the transformation. #[property(resolve)] @@ -85,8 +81,8 @@ impl<const T: TransformKind> TransformNode<T> { } } -impl<const T: TransformKind> Layout for TransformNode<T> { - fn layout( +impl<const T: TransformKind> LayoutInline for TransformNode<T> { + fn layout_inline( &self, world: Tracked<dyn World>, regions: &Regions, @@ -106,10 +102,6 @@ impl<const T: TransformKind> Layout for TransformNode<T> { Ok(frames) } - - fn level(&self) -> Level { - Level::Inline - } } /// Kinds of transformations. |
