diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-09 18:16:59 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-09 18:20:02 +0100 |
| commit | 010cc2effc2fd0e1c4e52d5c914cb4d74506bc0a (patch) | |
| tree | e50060d271f076b00945e5569e7f8ffef2c28e9f /library/src/layout | |
| parent | 12a59963b08b68cc39dcded4d3d3e6a6631c2732 (diff) | |
New block spacing model
Diffstat (limited to 'library/src/layout')
| -rw-r--r-- | library/src/layout/container.rs | 14 | ||||
| -rw-r--r-- | library/src/layout/flow.rs | 46 | ||||
| -rw-r--r-- | library/src/layout/grid.rs | 11 | ||||
| -rw-r--r-- | library/src/layout/mod.rs | 156 | ||||
| -rw-r--r-- | library/src/layout/spacing.rs | 47 |
5 files changed, 155 insertions, 119 deletions
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index d65b78b6..22a9e02e 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -1,3 +1,4 @@ +use super::VNode; use crate::prelude::*; /// An inline-level container that sizes content. @@ -63,9 +64,22 @@ pub struct BlockNode(pub Content); #[node(LayoutBlock)] impl BlockNode { + /// The spacing between the previous and this block. + #[property(skip)] + pub const ABOVE: VNode = VNode::weak(Em::new(1.2).into()); + /// The spacing between this and the following block. + #[property(skip)] + pub const BELOW: VNode = VNode::weak(Em::new(1.2).into()); + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self(args.eat()?.unwrap_or_default()).pack()) } + + fn set(...) { + let spacing = args.named("spacing")?.map(VNode::weak); + styles.set_opt(Self::ABOVE, args.named("above")?.map(VNode::strong).or(spacing)); + styles.set_opt(Self::BELOW, args.named("below")?.map(VNode::strong).or(spacing)); + } } impl LayoutBlock for BlockNode { diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 347c1dd8..cc5dcd50 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -1,6 +1,7 @@ use std::cmp::Ordering; -use super::{AlignNode, PlaceNode, Spacing}; +use super::{AlignNode, PlaceNode, Spacing, VNode}; +use crate::layout::BlockNode; use crate::prelude::*; use crate::text::ParNode; @@ -15,7 +16,7 @@ pub struct FlowNode(pub StyleVec<FlowChild>); #[derive(Hash, PartialEq)] pub enum FlowChild { /// Vertical spacing between other children. - Spacing(Spacing), + Spacing(VNode), /// Arbitrary block-level content. Block(Content), /// A column / region break. @@ -32,13 +33,13 @@ impl LayoutBlock for FlowNode { regions: &Regions, styles: StyleChain, ) -> SourceResult<Vec<Frame>> { - let mut layouter = FlowLayouter::new(regions); + let mut layouter = FlowLayouter::new(regions, styles); for (child, map) in self.0.iter() { let styles = map.chain(&styles); match child { - FlowChild::Spacing(kind) => { - layouter.layout_spacing(*kind, styles); + FlowChild::Spacing(node) => { + layouter.layout_spacing(node, styles); } FlowChild::Block(block) => { layouter.layout_block(world, block, styles)?; @@ -80,9 +81,11 @@ impl PartialOrd for FlowChild { } /// Performs flow layout. -struct FlowLayouter { +struct FlowLayouter<'a> { /// The regions to layout children into. regions: Regions, + /// The shared styles. + shared: StyleChain<'a>, /// Whether the flow should expand to fill the region. expand: Axes<bool>, /// The full size of `regions.size` that was available before we started @@ -92,6 +95,8 @@ struct FlowLayouter { used: Size, /// The sum of fractions in the current region. fr: Fr, + /// The spacing below the last block. + below: Option<VNode>, /// Spacing and layouted blocks. items: Vec<FlowItem>, /// Finished frames for previous regions. @@ -110,9 +115,9 @@ enum FlowItem { Placed(Frame), } -impl FlowLayouter { +impl<'a> FlowLayouter<'a> { /// Create a new flow layouter. - fn new(regions: &Regions) -> Self { + fn new(regions: &Regions, shared: StyleChain<'a>) -> Self { let expand = regions.expand; let full = regions.first; @@ -122,18 +127,20 @@ impl FlowLayouter { Self { regions, + shared, expand, full, used: Size::zero(), fr: Fr::zero(), + below: None, items: vec![], finished: vec![], } } /// Layout spacing. - fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) { - match spacing { + fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) { + match node.amount { Spacing::Relative(v) => { // Resolve the spacing and limit it to the remaining space. let resolved = v.resolve(styles).relative_to(self.full.y); @@ -147,6 +154,10 @@ impl FlowLayouter { self.fr += v; } } + + if node.weak || node.amount.is_fractional() { + self.below = None; + } } /// Layout a block. @@ -161,9 +172,19 @@ impl FlowLayouter { self.finish_region(); } + // Add spacing between the last block and this one. + if let Some(below) = self.below.take() { + let above = styles.get(BlockNode::ABOVE); + let pick_below = (above.weak && !below.weak) || (below.amount > above.amount); + let spacing = if pick_below { below } else { above }; + self.layout_spacing(&spacing, self.shared); + } + // Placed nodes that are out of flow produce placed items which aren't // aligned later. + let mut is_placed = false; if let Some(placed) = block.downcast::<PlaceNode>() { + is_placed = true; if placed.out_of_flow() { let frame = block.layout_block(world, &self.regions, styles)?.remove(0); self.items.push(FlowItem::Placed(frame)); @@ -202,6 +223,10 @@ impl FlowLayouter { } } + if !is_placed { + self.below = Some(styles.get(BlockNode::BELOW)); + } + Ok(()) } @@ -250,6 +275,7 @@ impl FlowLayouter { self.full = self.regions.first; self.used = Size::zero(); self.fr = Fr::zero(); + self.below = None; self.finished.push(output); } diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 8e1cb7a2..8af69b9a 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -1,5 +1,7 @@ use crate::prelude::*; +use super::Spacing; + /// Arrange content in a grid. #[derive(Debug, Hash)] pub struct GridNode { @@ -66,6 +68,15 @@ pub enum TrackSizing { Fractional(Fr), } +impl From<Spacing> for TrackSizing { + fn from(spacing: Spacing) -> Self { + match spacing { + Spacing::Relative(rel) => Self::Relative(rel), + Spacing::Fractional(fr) => Self::Fractional(fr), + } + } +} + /// Track sizing definitions. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] pub struct TrackSizings(pub Vec<TrackSizing>); diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 1032ba54..e0eb431c 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -32,8 +32,8 @@ use typst::diag::SourceResult; use typst::frame::Frame; use typst::geom::*; use typst::model::{ - capability, Barrier, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, - StyleVec, StyleVecBuilder, StyledNode, Target, + capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVec, + StyleVecBuilder, StyledNode, Target, }; use typst::World; @@ -85,10 +85,12 @@ impl LayoutBlock for Content { 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); + if !self.has::<dyn Show>() || !styles.applicable(self) { + if let Some(node) = self.to::<dyn LayoutBlock>() { + let barrier = StyleEntry::Barrier(self.id()); + let styles = barrier.chain(&styles); + return node.layout_block(world, regions, styles); + } } let scratch = Scratch::default(); @@ -119,16 +121,18 @@ impl LayoutInline for Content { 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 !self.has::<dyn Show>() || !styles.applicable(self) { + if let Some(node) = self.to::<dyn LayoutInline>() { + let barrier = StyleEntry::Barrier(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); + if let Some(node) = self.to::<dyn LayoutBlock>() { + let barrier = StyleEntry::Barrier(self.id()); + let styles = barrier.chain(&styles); + return node.layout_block(world, regions, styles); + } } let scratch = Scratch::default(); @@ -253,8 +257,6 @@ struct Builder<'a> { struct Scratch<'a> { /// An arena where intermediate style chains are stored. styles: Arena<StyleChain<'a>>, - /// An arena for individual intermediate style entries. - entries: Arena<StyleEntry>, /// An arena where intermediate content resulting from show rules is stored. content: Arena<Content>, } @@ -354,18 +356,7 @@ impl<'a> Builder<'a> { Ok(()) } - fn show( - &mut self, - content: &'a Content, - styles: StyleChain<'a>, - ) -> SourceResult<bool> { - let barrier = StyleEntry::Barrier(Barrier::new(content.id())); - let styles = self - .scratch - .entries - .alloc(barrier) - .chain(self.scratch.styles.alloc(styles)); - + fn show(&mut self, content: &Content, styles: StyleChain<'a>) -> SourceResult<bool> { let Some(realized) = styles.apply(self.world, Target::Node(content))? else { return Ok(false); }; @@ -457,7 +448,7 @@ struct DocBuilder<'a> { } impl<'a> DocBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) { + fn accept(&mut self, content: &Content, styles: StyleChain<'a>) { if let Some(pagebreak) = content.downcast::<PagebreakNode>() { self.keep_next = !pagebreak.weak; } @@ -477,10 +468,10 @@ impl Default for DocBuilder<'_> { /// Accepts flow content. #[derive(Default)] -struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>); +struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>, bool); impl<'a> FlowBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { // Weak flow elements: // Weakness | Element // 0 | weak colbreak @@ -490,8 +481,11 @@ impl<'a> FlowBuilder<'a> { // 4 | generated weak fractional spacing // 5 | par spacing + let last_was_parbreak = self.1; + self.1 = false; + if content.is::<ParbreakNode>() { - /* Nothing to do */ + self.1 = true; } else if let Some(colbreak) = content.downcast::<ColbreakNode>() { if colbreak.weak { self.0.weak(FlowChild::Colbreak, styles, 0); @@ -499,10 +493,10 @@ impl<'a> FlowBuilder<'a> { self.0.destructive(FlowChild::Colbreak, styles); } } else if let Some(vertical) = content.downcast::<VNode>() { - let child = FlowChild::Spacing(vertical.amount); + let child = FlowChild::Spacing(*vertical); let frac = vertical.amount.is_fractional(); if vertical.weak { - let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated); + let weakness = 1 + u8::from(frac); self.0.weak(child, styles, weakness); } else if frac { self.0.destructive(child, styles); @@ -510,6 +504,24 @@ impl<'a> FlowBuilder<'a> { self.0.ignorant(child, styles); } } else if content.has::<dyn LayoutBlock>() { + if !last_was_parbreak { + let tight = if let Some(node) = content.downcast::<ListNode>() { + node.tight + } else if let Some(node) = content.downcast::<EnumNode>() { + node.tight + } else if let Some(node) = content.downcast::<DescNode>() { + node.tight + } else { + false + }; + + if tight { + let leading = styles.get(ParNode::LEADING); + let spacing = VNode::weak(leading.into()); + self.0.weak(FlowChild::Spacing(spacing), styles, 1); + } + } + let child = FlowChild::Block(content.clone()); if content.is::<PlaceNode>() { self.0.ignorant(child, styles); @@ -523,18 +535,6 @@ impl<'a> FlowBuilder<'a> { 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 }; @@ -552,7 +552,7 @@ impl<'a> FlowBuilder<'a> { struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>); impl<'a> ParBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { // Weak par elements: // Weakness | Element // 0 | weak fractional spacing @@ -621,7 +621,7 @@ impl<'a> ParBuilder<'a> { children.push_front(ParChild::Spacing(indent.into())); } - parent.flow.par(ParNode(children), shared, !indent.is_zero()); + parent.flow.accept(&ParNode(children).pack(), shared); } fn is_empty(&self) -> bool { @@ -635,63 +635,54 @@ struct ListBuilder<'a> { 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.is_empty() + && (content.is::<SpaceNode>() || content.is::<ParbreakNode>()) + { + self.staged.push((content, styles)); + } else if let Some(item) = content.downcast::<ListItem>() { if self .items .items() .next() - .map_or(true, |first| item.kind() == first.kind()) + .map_or(false, |first| item.kind() != first.kind()) { - self.items.push(item.clone(), styles); - self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>()); - return true; + return false; } - } else if !self.items.is_empty() - && (content.is::<SpaceNode>() || content.is::<ParbreakNode>()) - { - self.staged.push((content, styles)); - return true; + + self.items.push(item.clone(), styles); + self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>()); + } else { + return false; } - false + true } fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> { let (items, shared) = self.items.finish(); + if let Some(item) = items.items().next() { + let tight = self.tight; + let content = match item.kind() { + LIST => ListNode::<LIST> { tight, items }.pack(), + ENUM => ListNode::<ENUM> { tight, items }.pack(), + DESC | _ => ListNode::<DESC> { tight, items }.pack(), + }; - let Some(item) = items.items().next() else { return Ok(()) }; - let tight = self.tight; - let attached = tight && self.attachable; - let content = match item.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.content.alloc(content); - parent.accept(stored, shared)?; + let stored = parent.scratch.content.alloc(content); + parent.accept(stored, shared)?; + } for (content, styles) in self.staged { parent.accept(content, styles)?; } - parent.list.attachable = true; + parent.list.tight = true; Ok(()) } @@ -706,7 +697,6 @@ impl Default for ListBuilder<'_> { Self { items: StyleVecBuilder::default(), tight: true, - attachable: true, staged: vec![], } } diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index 67fff5db..66d71ed1 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -1,10 +1,9 @@ use std::cmp::Ordering; use crate::prelude::*; -use crate::text::ParNode; /// Horizontal spacing. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Copy, Clone, Hash)] pub struct HNode { pub amount: Spacing, pub weak: bool, @@ -20,11 +19,22 @@ impl HNode { } /// Vertical spacing. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)] pub struct VNode { pub amount: Spacing, pub weak: bool, - pub generated: bool, +} + +impl VNode { + /// Create weak vertical spacing. + pub fn weak(amount: Spacing) -> Self { + Self { amount, weak: true } + } + + /// Create strong vertical spacing. + pub fn strong(amount: Spacing) -> Self { + Self { amount, weak: false } + } } #[node] @@ -32,7 +42,7 @@ impl VNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); - Ok(Self { amount, weak, generated: false }.pack()) + Ok(Self { amount, weak }.pack()) } } @@ -59,6 +69,12 @@ impl From<Abs> for Spacing { } } +impl From<Em> for Spacing { + fn from(em: Em) -> Self { + Self::Relative(Rel::new(Ratio::zero(), em.into())) + } +} + impl PartialOrd for Spacing { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { match (self, other) { @@ -77,24 +93,3 @@ castable! { Value::Relative(v) => Self::Relative(v), Value::Fraction(v) => Self::Fractional(v), } - -/// Spacing around and between blocks, relative to paragraph spacing. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct BlockSpacing(Rel<Length>); - -castable!(BlockSpacing: Rel<Length>); - -impl Resolve for BlockSpacing { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - let whole = styles.get(ParNode::SPACING); - self.0.resolve(styles).relative_to(whole) - } -} - -impl From<Ratio> for BlockSpacing { - fn from(ratio: Ratio) -> Self { - Self(ratio.into()) - } -} |
