diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-03 11:44:53 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-03 13:35:39 +0100 |
| commit | 37a7afddfaffd44cb9bc013c9506599267e08983 (patch) | |
| tree | 20e7d62d3c5418baff01a21d0406b91bf3096214 /library/src/layout/flow.rs | |
| parent | 56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff) | |
Split crates
Diffstat (limited to 'library/src/layout/flow.rs')
| -rw-r--r-- | library/src/layout/flow.rs | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs new file mode 100644 index 00000000..a5992796 --- /dev/null +++ b/library/src/layout/flow.rs @@ -0,0 +1,267 @@ +use std::cmp::Ordering; + +use super::{AlignNode, PlaceNode, Spacing}; +use crate::prelude::*; +use crate::text::ParNode; + +/// 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. +#[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(Spacing), + /// Arbitrary block-level content. + Block(Content), + /// A column / region break. + Colbreak, +} + +#[node(LayoutBlock)] +impl FlowNode {} + +impl LayoutBlock for FlowNode { + fn layout_block( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + let mut layouter = FlowLayouter::new(regions); + + for (child, map) in self.0.iter() { + let styles = map.chain(&styles); + match child { + FlowChild::Spacing(kind) => { + layouter.layout_spacing(*kind, styles); + } + FlowChild::Block(block) => { + layouter.layout_block(world, block, styles)?; + } + FlowChild::Colbreak => { + layouter.finish_region(); + } + } + } + + Ok(layouter.finish()) + } +} + +impl Debug for FlowNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("Flow ")?; + self.0.fmt(f) + } +} + +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. +pub struct FlowLayouter { + /// The regions to layout children into. + regions: Regions, + /// Whether the flow should expand to fill the region. + expand: Axes<bool>, + /// The full size of `regions.size` that was available before we started + /// subtracting. + full: Size, + /// The size used by the frames for the current region. + used: Size, + /// The sum of fractions in the current region. + fr: Fr, + /// Spacing and layouted blocks. + items: Vec<FlowItem>, + /// Finished frames for previous regions. + finished: Vec<Frame>, +} + +/// A prepared item in a flow layout. +enum FlowItem { + /// Absolute spacing between other items. + Absolute(Abs), + /// Fractional spacing between other items. + Fractional(Fr), + /// A frame for a layouted block and how to align it. + Frame(Frame, Axes<Align>), + /// An absolutely placed frame. + Placed(Frame), +} + +impl FlowLayouter { + /// Create a new flow layouter. + pub fn new(regions: &Regions) -> Self { + let expand = regions.expand; + let full = regions.first; + + // Disable vertical expansion for children. + let mut regions = regions.clone(); + regions.expand.y = false; + + Self { + regions, + expand, + full, + used: Size::zero(), + fr: Fr::zero(), + items: vec![], + finished: vec![], + } + } + + /// Layout spacing. + pub 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); + let limited = resolved.min(self.regions.first.y); + self.regions.first.y -= limited; + self.used.y += limited; + self.items.push(FlowItem::Absolute(resolved)); + } + Spacing::Fractional(v) => { + self.items.push(FlowItem::Fractional(v)); + self.fr += v; + } + } + } + + /// Layout a block. + pub fn layout_block( + &mut self, + world: Tracked<dyn World>, + block: &Content, + styles: StyleChain, + ) -> SourceResult<()> { + // Don't even try layouting into a full region. + if self.regions.is_full() { + self.finish_region(); + } + + // Placed nodes that are out of flow produce placed items which aren't + // aligned later. + if let Some(placed) = block.downcast::<PlaceNode>() { + if placed.out_of_flow() { + let frame = block.layout_block(world, &self.regions, styles)?.remove(0); + self.items.push(FlowItem::Placed(frame)); + return Ok(()); + } + } + + // 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. + block + .downcast::<AlignNode>() + .and_then(|aligned| aligned.aligns.y) + .map(|align| align.resolve(styles)) + .unwrap_or(Align::Top), + ); + + 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. + frame.apply_role(Role::GenericBlock); + + // Grow our size, shrink the region and save the frame for later. + let size = frame.size(); + self.used.y += size.y; + self.used.x.set_max(size.x); + self.regions.first.y -= size.y; + self.items.push(FlowItem::Frame(frame, aligns)); + + if i + 1 < len { + self.finish_region(); + } + } + + Ok(()) + } + + /// Finish the frame for one region. + pub fn finish_region(&mut self) { + // Determine the size of the flow in this region dependening on whether + // the region expands. + let mut size = self.expand.select(self.full, self.used); + + // Account for fractional spacing in the size calculation. + let remaining = self.full.y - self.used.y; + if self.fr.get() > 0.0 && self.full.y.is_finite() { + self.used.y = self.full.y; + size.y = self.full.y; + } + + let mut output = Frame::new(size); + let mut offset = Abs::zero(); + let mut ruler = Align::Top; + + // Place all frames. + for item in self.items.drain(..) { + match item { + FlowItem::Absolute(v) => { + offset += v; + } + FlowItem::Fractional(v) => { + offset += v.share(self.fr, remaining); + } + FlowItem::Frame(frame, aligns) => { + ruler = ruler.max(aligns.y); + let x = aligns.x.position(size.x - frame.width()); + let y = offset + ruler.position(size.y - self.used.y); + let pos = Point::new(x, y); + offset += frame.height(); + output.push_frame(pos, frame); + } + FlowItem::Placed(frame) => { + output.push_frame(Point::zero(), frame); + } + } + } + + // Advance to the next region. + self.regions.next(); + self.full = self.regions.first; + self.used = Size::zero(); + self.fr = Fr::zero(); + self.finished.push(output); + } + + /// Finish layouting and return the resulting frames. + pub fn finish(mut self) -> Vec<Frame> { + if self.expand.y { + while !self.regions.backlog.is_empty() { + self.finish_region(); + } + } + + self.finish_region(); + self.finished + } +} |
