diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-12 23:25:54 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-12 23:46:26 +0100 |
| commit | bf59c08a0a601eeac4354c505cab15e65601c8e8 (patch) | |
| tree | 2ba3230c77960acf5d55ee34065a5028d1c45d5d /library/src/layout | |
| parent | d9ce194fe71076314955dd25896f64d48bccd6e5 (diff) | |
New interaction model
Diffstat (limited to 'library/src/layout')
| -rw-r--r-- | library/src/layout/columns.rs | 12 | ||||
| -rw-r--r-- | library/src/layout/container.rs | 16 | ||||
| -rw-r--r-- | library/src/layout/flow.rs | 95 | ||||
| -rw-r--r-- | library/src/layout/mod.rs | 228 | ||||
| -rw-r--r-- | library/src/layout/place.rs | 8 | ||||
| -rw-r--r-- | library/src/layout/spacing.rs | 95 |
6 files changed, 165 insertions, 289 deletions
diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 2faa6329..ec9510d8 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -104,10 +104,20 @@ pub struct ColbreakNode { pub weak: bool, } -#[node] +#[node(Behave)] impl ColbreakNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let weak = args.named("weak")?.unwrap_or(false); Ok(Self { weak }.pack()) } } + +impl Behave for ColbreakNode { + fn behaviour(&self) -> Behaviour { + if self.weak { + Behaviour::Weak(1) + } else { + Behaviour::Destructive + } + } +} diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 22a9e02e..20d80cba 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -66,19 +66,25 @@ pub struct BlockNode(pub Content); impl BlockNode { /// The spacing between the previous and this block. #[property(skip)] - pub const ABOVE: VNode = VNode::weak(Em::new(1.2).into()); + pub const ABOVE: VNode = VNode::block_spacing(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()); + pub const BELOW: VNode = VNode::block_spacing(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)); + let spacing = args.named("spacing")?.map(VNode::block_spacing); + styles.set_opt( + Self::ABOVE, + args.named("above")?.map(VNode::block_around).or(spacing), + ); + styles.set_opt( + Self::BELOW, + args.named("below")?.map(VNode::block_around).or(spacing), + ); } } diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 822d2c38..b05146c9 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -1,7 +1,4 @@ -use std::cmp::Ordering; - -use super::{AlignNode, PlaceNode, Spacing, VNode}; -use crate::layout::BlockNode; +use super::{AlignNode, ColbreakNode, PlaceNode, Spacing, VNode}; use crate::prelude::*; use crate::text::ParNode; @@ -10,18 +7,7 @@ use crate::text::ParNode; /// This node is reponsible for layouting both the top-level content flow and /// the contents of boxes. #[derive(Hash)] -pub struct FlowNode(pub StyleVec<FlowChild>); - -/// A child of a flow node. -#[derive(Hash, PartialEq)] -pub enum FlowChild { - /// Vertical spacing between other children. - Spacing(VNode), - /// Arbitrary block-level content. - Block(Content), - /// A column / region break. - Colbreak, -} +pub struct FlowNode(pub StyleVec<Content>); #[node(LayoutBlock)] impl FlowNode {} @@ -33,20 +19,18 @@ impl LayoutBlock for FlowNode { regions: &Regions, styles: StyleChain, ) -> SourceResult<Vec<Frame>> { - let mut layouter = FlowLayouter::new(regions, styles); + let mut layouter = FlowLayouter::new(regions); for (child, map) in self.0.iter() { let styles = map.chain(&styles); - match child { - FlowChild::Spacing(node) => { - layouter.layout_spacing(node, styles); - } - FlowChild::Block(block) => { - layouter.layout_block(world, block, styles)?; - } - FlowChild::Colbreak => { - layouter.finish_region(); - } + if let Some(&node) = child.downcast::<VNode>() { + layouter.layout_spacing(node.amount, styles); + } else if child.has::<dyn LayoutBlock>() { + layouter.layout_block(world, child, styles)?; + } else if child.is::<ColbreakNode>() { + layouter.finish_region(); + } else { + panic!("unexpected flow child: {child:?}"); } } @@ -61,31 +45,10 @@ impl Debug for FlowNode { } } -impl Debug for FlowChild { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Spacing(kind) => write!(f, "{:?}", kind), - Self::Block(block) => block.fmt(f), - Self::Colbreak => f.pad("Colbreak"), - } - } -} - -impl PartialOrd for FlowChild { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - match (self, other) { - (Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b), - _ => None, - } - } -} - /// Performs flow layout. -struct FlowLayouter<'a> { +struct FlowLayouter { /// 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 @@ -95,8 +58,6 @@ struct FlowLayouter<'a> { 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. @@ -115,9 +76,9 @@ enum FlowItem { Placed(Frame), } -impl<'a> FlowLayouter<'a> { +impl FlowLayouter { /// Create a new flow layouter. - fn new(regions: &Regions, shared: StyleChain<'a>) -> Self { + fn new(regions: &Regions) -> Self { let expand = regions.expand; let full = regions.first; @@ -127,20 +88,18 @@ impl<'a> FlowLayouter<'a> { Self { regions, - shared, expand, full, used: Size::zero(), fr: Fr::zero(), - below: None, items: vec![], finished: vec![], } } - /// Layout spacing. - fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) { - match node.amount { + /// Actually layout the spacing. + fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) { + match spacing { Spacing::Relative(v) => { // Resolve the spacing and limit it to the remaining space. let resolved = v.resolve(styles).relative_to(self.full.y); @@ -154,10 +113,6 @@ impl<'a> FlowLayouter<'a> { self.fr += v; } } - - if node.weak || node.amount.is_fractional() { - self.below = None; - } } /// Layout a block. @@ -172,19 +127,9 @@ impl<'a> FlowLayouter<'a> { 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)); @@ -205,6 +150,7 @@ impl<'a> FlowLayouter<'a> { .unwrap_or(Align::Top), ); + // Layout the block itself. let frames = block.layout_block(world, &self.regions, styles)?; let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { @@ -220,10 +166,6 @@ impl<'a> FlowLayouter<'a> { } } - if !is_placed { - self.below = Some(styles.get(BlockNode::BELOW)); - } - Ok(()) } @@ -272,7 +214,6 @@ impl<'a> FlowLayouter<'a> { 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/mod.rs b/library/src/layout/mod.rs index e0eb431c..d6bc9175 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -32,16 +32,18 @@ use typst::diag::SourceResult; use typst::frame::Frame; use typst::geom::*; use typst::model::{ - capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVec, + capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVecBuilder, StyledNode, Target, }; use typst::World; +use crate::core::BehavedBuilder; +use crate::prelude::*; use crate::structure::{ DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST, }; use crate::text::{ - LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, + LinebreakNode, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, }; /// Root-level layout. @@ -468,41 +470,17 @@ impl Default for DocBuilder<'_> { /// Accepts flow content. #[derive(Default)] -struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>, bool); +struct FlowBuilder<'a>(BehavedBuilder<'a>, bool); impl<'a> FlowBuilder<'a> { fn accept(&mut self, content: &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 - let last_was_parbreak = self.1; self.1 = false; if content.is::<ParbreakNode>() { self.1 = true; - } 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); - let frac = vertical.amount.is_fractional(); - if vertical.weak { - let weakness = 1 + 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 content.is::<VNode>() || content.is::<ColbreakNode>() { + self.0.push(content.clone(), styles); } else if content.has::<dyn LayoutBlock>() { if !last_was_parbreak { let tight = if let Some(node) = content.downcast::<ListNode>() { @@ -517,17 +495,16 @@ impl<'a> FlowBuilder<'a> { if tight { let leading = styles.get(ParNode::LEADING); - let spacing = VNode::weak(leading.into()); - self.0.weak(FlowChild::Spacing(spacing), styles, 1); + let spacing = VNode::list_attach(leading.into()); + self.0.push(spacing.pack(), styles); } } - let child = FlowChild::Block(content.clone()); - if content.is::<PlaceNode>() { - self.0.ignorant(child, styles); - } else { - self.0.supportive(child, styles); - } + let above = styles.get(BlockNode::ABOVE); + let below = styles.get(BlockNode::BELOW); + self.0.push(above.pack(), styles); + self.0.push(content.clone(), styles); + self.0.push(below.pack(), styles); } else { return false; } @@ -549,43 +526,22 @@ impl<'a> FlowBuilder<'a> { /// Accepts paragraph content. #[derive(Default)] -struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>); +struct ParBuilder<'a>(BehavedBuilder<'a>); impl<'a> ParBuilder<'a> { fn accept(&mut self, content: &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; + if content.is::<SpaceNode>() + || content.is::<LinebreakNode>() + || content.is::<HNode>() + || content.is::<SmartQuoteNode>() + || content.is::<TextNode>() + || content.has::<dyn LayoutInline>() + { + self.0.push(content.clone(), styles); + return true; } - true + false } fn finish(self, parent: &mut Builder<'a>) { @@ -600,10 +556,14 @@ impl<'a> ParBuilder<'a> { if !indent.is_zero() && children .items() - .find_map(|child| match child { - ParChild::Spacing(_) => None, - ParChild::Text(_) | ParChild::Quote { .. } => Some(true), - ParChild::Inline(_) => Some(false), + .find_map(|child| { + if child.is::<TextNode>() || child.is::<SmartQuoteNode>() { + Some(true) + } else if child.has::<dyn LayoutInline>() { + Some(false) + } else { + None + } }) .unwrap_or_default() && parent @@ -611,14 +571,10 @@ impl<'a> ParBuilder<'a> { .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() + .find(|child| child.has::<dyn LayoutBlock>()) + .map_or(false, |child| child.is::<ParNode>()) { - children.push_front(ParChild::Spacing(indent.into())); + children.push_front(HNode::strong(indent.into()).pack()); } parent.flow.accept(&ParNode(children).pack(), shared); @@ -701,115 +657,3 @@ impl Default for ListBuilder<'_> { } } } - -/// 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. - fn new() -> Self { - Self { - builder: StyleVecBuilder::new(), - staged: vec![], - last: Last::Destructive, - } - } - - /// Whether the builder is empty. - 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. - 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 { - let weak = self.staged.iter().position(|(prev_item, _, prev_weakness)| { - prev_weakness.map_or(false, |prev_weakness| { - weakness < prev_weakness - || (weakness == prev_weakness && item > *prev_item) - }) - }); - - let Some(weak) = weak else { return }; - self.staged.remove(weak); - } - - self.staged.push((item, styles, Some(weakness))); - self.last = Last::Weak; - } - - /// Forces nearby weak items to collapse. - 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. - 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. - fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { - self.staged.push((item, styles, None)); - } - - /// Iterate over the contained items. - 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. - 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/library/src/layout/place.rs b/library/src/layout/place.rs index 7d760ab6..42f7ff7d 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -5,7 +5,7 @@ use crate::prelude::*; #[derive(Debug, Hash)] pub struct PlaceNode(pub Content); -#[node(LayoutBlock)] +#[node(LayoutBlock, Behave)] impl PlaceNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start))); @@ -54,3 +54,9 @@ impl PlaceNode { .map_or(false, |node| node.aligns.y.is_some()) } } + +impl Behave for PlaceNode { + fn behaviour(&self) -> Behaviour { + Behaviour::Ignorant + } +} diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index 66d71ed1..74bd2f0f 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -5,11 +5,13 @@ use crate::prelude::*; /// Horizontal spacing. #[derive(Debug, Copy, Clone, Hash)] pub struct HNode { + /// The amount of horizontal spacing. pub amount: Spacing, + /// Whether the node is weak, see also [`Behaviour`]. pub weak: bool, } -#[node] +#[node(Behave)] impl HNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let amount = args.expect("spacing")?; @@ -18,31 +20,98 @@ impl HNode { } } +impl HNode { + /// Normal strong spacing. + pub fn strong(amount: Spacing) -> Self { + Self { amount, weak: false } + } + + /// User-created weak spacing. + pub fn weak(amount: Spacing) -> Self { + Self { amount, weak: true } + } +} + +impl Behave for HNode { + fn behaviour(&self) -> Behaviour { + if self.amount.is_fractional() { + Behaviour::Destructive + } else if self.weak { + Behaviour::Weak(1) + } else { + Behaviour::Ignorant + } + } + + fn larger(&self, prev: &Content) -> bool { + let Some(prev) = prev.downcast::<Self>() else { return false }; + self.amount > prev.amount + } +} + /// Vertical spacing. #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)] pub struct VNode { + /// The amount of vertical spacing. pub amount: Spacing, - pub weak: bool, + /// The node's weakness level, see also [`Behaviour`]. + pub weakness: u8, } +#[node(Behave)] impl VNode { - /// Create weak vertical spacing. - pub fn weak(amount: Spacing) -> Self { - Self { amount, weak: true } + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { + let amount = args.expect("spacing")?; + let node = if args.named("weak")?.unwrap_or(false) { + Self::weak(amount) + } else { + Self::strong(amount) + }; + Ok(node.pack()) } +} - /// Create strong vertical spacing. +impl VNode { + /// Normal strong spacing. pub fn strong(amount: Spacing) -> Self { - Self { amount, weak: false } + Self { amount, weakness: 0 } + } + + /// User-created weak spacing. + pub fn weak(amount: Spacing) -> Self { + Self { amount, weakness: 1 } + } + + /// Weak spacing with list attach weakness. + pub fn list_attach(amount: Spacing) -> Self { + Self { amount, weakness: 2 } + } + + /// Weak spacing with BlockNode::ABOVE/BELOW weakness. + pub fn block_around(amount: Spacing) -> Self { + Self { amount, weakness: 3 } + } + + /// Weak spacing with BlockNode::SPACING weakness. + pub fn block_spacing(amount: Spacing) -> Self { + Self { amount, weakness: 4 } } } -#[node] -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 }.pack()) +impl Behave for VNode { + fn behaviour(&self) -> Behaviour { + if self.amount.is_fractional() { + Behaviour::Destructive + } else if self.weakness > 0 { + Behaviour::Weak(self.weakness) + } else { + Behaviour::Ignorant + } + } + + fn larger(&self, prev: &Content) -> bool { + let Some(prev) = prev.downcast::<Self>() else { return false }; + self.amount > prev.amount } } |
