diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-02-07 20:00:21 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-02-07 20:00:21 +0100 |
| commit | 68503b9a07b00bce3f4d377bcfe945452de815ea (patch) | |
| tree | 3719ef491b993c59b619ca215963000f4847e78f /src/library | |
| parent | 9730e785a885a4ab5fcc52ce705298654f82f9c2 (diff) | |
Redesigned template layout
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/flow.rs | 178 | ||||
| -rw-r--r-- | src/library/mod.rs | 4 | ||||
| -rw-r--r-- | src/library/par.rs | 89 | ||||
| -rw-r--r-- | src/library/spacing.rs | 7 | ||||
| -rw-r--r-- | src/library/text.rs | 8 |
5 files changed, 152 insertions, 134 deletions
diff --git a/src/library/flow.rs b/src/library/flow.rs index 227a7a35..bba296a0 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -1,16 +1,27 @@ //! A flow of paragraphs and other block-level nodes. -use std::fmt::{self, Debug, Formatter}; - use super::prelude::*; use super::{AlignNode, ParNode, PlaceNode, SpacingKind, TextNode}; -/// A vertical flow of content consisting of paragraphs and other layout nodes. +/// Arrange spacing, paragraphs and other 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 Vec<Styled<FlowChild>>); +pub struct FlowNode(pub StyleVec<FlowChild>); + +/// A child of a flow node. +#[derive(Hash)] +pub enum FlowChild { + /// A paragraph / block break. + Parbreak, + /// A column / region break. + Colbreak, + /// Vertical spacing between other children. + Spacing(SpacingKind), + /// An arbitrary block-level node. + Node(PackedNode), +} impl Layout for FlowNode { fn layout( @@ -19,45 +30,58 @@ impl Layout for FlowNode { regions: &Regions, styles: StyleChain, ) -> Vec<Constrained<Arc<Frame>>> { - FlowLayouter::new(self, regions.clone()).layout(ctx, styles) + let mut layouter = FlowLayouter::new(regions); + + for (child, map) in self.0.iter() { + let styles = map.chain(&styles); + match child { + FlowChild::Parbreak => { + let em = styles.get(TextNode::SIZE).abs; + let amount = styles.get(ParNode::SPACING).resolve(em); + layouter.layout_spacing(SpacingKind::Linear(amount.into())); + } + FlowChild::Colbreak => { + layouter.finish_region(); + } + FlowChild::Spacing(kind) => { + layouter.layout_spacing(*kind); + } + FlowChild::Node(ref node) => { + layouter.layout_node(ctx, node, styles); + } + } + } + + layouter.finish() + } +} + +impl Merge for FlowChild { + fn merge(&mut self, _: &Self) -> bool { + false } } impl Debug for FlowNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Flow ")?; - f.debug_list().entries(&self.0).finish() + self.0.fmt(f) } } -/// A child of a flow node. -#[derive(Hash)] -pub enum FlowChild { - /// A paragraph/block break. - Break, - /// Skip the rest of the region and move to the next. - Skip, - /// Vertical spacing between other children. - Spacing(SpacingKind), - /// An arbitrary node. - Node(PackedNode), -} - impl Debug for FlowChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Break => f.pad("Break"), - Self::Skip => f.pad("Skip"), - Self::Spacing(kind) => kind.fmt(f), + Self::Parbreak => f.pad("Parbreak"), + Self::Colbreak => f.pad("Colbreak"), + Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Node(node) => node.fmt(f), } } } /// Performs flow layout. -struct FlowLayouter<'a> { - /// The children of the flow. - children: &'a [Styled<FlowChild>], +pub struct FlowLayouter { /// The regions to layout children into. regions: Regions, /// Whether the flow should expand to fill the region. @@ -69,6 +93,8 @@ struct FlowLayouter<'a> { used: Size, /// The sum of fractional ratios in the current region. fr: Fractional, + /// Whether to add leading before the next node. + leading: bool, /// Spacing and layouted nodes. items: Vec<FlowItem>, /// Finished frames for previous regions. @@ -87,98 +113,64 @@ enum FlowItem { Placed(Arc<Frame>), } -impl<'a> FlowLayouter<'a> { +impl FlowLayouter { /// Create a new flow layouter. - fn new(flow: &'a FlowNode, mut regions: Regions) -> Self { + pub fn new(regions: &Regions) -> Self { let expand = regions.expand; let full = regions.current; // Disable vertical expansion for children. + let mut regions = regions.clone(); regions.expand.y = false; Self { - children: &flow.0, regions, expand, full, used: Size::zero(), fr: Fractional::zero(), + leading: false, items: vec![], finished: vec![], } } - /// Layout all children. - fn layout( - mut self, - ctx: &mut LayoutContext, - styles: StyleChain, - ) -> Vec<Constrained<Arc<Frame>>> { - for styled in self.children { - let styles = styled.map.chain(&styles); - match styled.item { - FlowChild::Break => { - let em = styles.get(TextNode::SIZE).abs; - let amount = styles.get(ParNode::SPACING).resolve(em); - self.layout_absolute(amount.into()); - } - FlowChild::Skip => { - self.finish_region(); - } - FlowChild::Spacing(kind) => { - self.layout_spacing(kind); - } - FlowChild::Node(ref node) => { - if self.regions.is_full() { - self.finish_region(); - } - - self.layout_node(ctx, node, styles); - } - } - } - - if self.expand.y { - while self.regions.backlog.len() > 0 { - self.finish_region(); - } - } - - self.finish_region(); - self.finished - } - /// Layout spacing. - fn layout_spacing(&mut self, spacing: SpacingKind) { + pub fn layout_spacing(&mut self, spacing: SpacingKind) { match spacing { - SpacingKind::Linear(v) => self.layout_absolute(v), + SpacingKind::Linear(v) => { + // Resolve the linear and limit it to the remaining space. + let resolved = v.resolve(self.full.y); + let limited = resolved.min(self.regions.current.y); + self.regions.current.y -= limited; + self.used.y += limited; + self.items.push(FlowItem::Absolute(resolved)); + } SpacingKind::Fractional(v) => { self.items.push(FlowItem::Fractional(v)); self.fr += v; + self.leading = false; } } } - /// Layout absolute spacing. - fn layout_absolute(&mut self, amount: Linear) { - // Resolve the linear, limiting it to the remaining available space. - let resolved = amount.resolve(self.full.y); - let limited = resolved.min(self.regions.current.y); - self.regions.current.y -= limited; - self.used.y += limited; - self.items.push(FlowItem::Absolute(resolved)); - } - /// Layout a node. - fn layout_node( + pub fn layout_node( &mut self, ctx: &mut LayoutContext, node: &PackedNode, styles: StyleChain, ) { + // 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. + let mut is_placed = false; if let Some(placed) = node.downcast::<PlaceNode>() { + is_placed = true; if placed.out_of_flow() { let frame = node.layout(ctx, &self.regions, styles).remove(0); self.items.push(FlowItem::Placed(frame.item)); @@ -186,6 +178,13 @@ impl<'a> FlowLayouter<'a> { } } + // Add leading. + if self.leading { + let em = styles.get(TextNode::SIZE).abs; + let amount = styles.get(ParNode::LEADING).resolve(em); + self.layout_spacing(SpacingKind::Linear(amount.into())); + } + // How to align the node. let aligns = Spec::new( // For non-expanding paragraphs it is crucial that we align the @@ -211,10 +210,12 @@ impl<'a> FlowLayouter<'a> { self.finish_region(); } } + + self.leading = !is_placed; } /// Finish the frame for one region. - fn finish_region(&mut self) { + 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); @@ -263,6 +264,19 @@ impl<'a> FlowLayouter<'a> { self.full = self.regions.current; self.used = Size::zero(); self.fr = Fractional::zero(); + self.leading = false; self.finished.push(output.constrain(cts)); } + + /// Finish layouting and return the resulting frames. + pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> { + if self.expand.y { + while self.regions.backlog.len() > 0 { + self.finish_region(); + } + } + + self.finish_region(); + self.finished + } } diff --git a/src/library/mod.rs b/src/library/mod.rs index 9e3bf18b..ef0fa016 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -68,8 +68,8 @@ prelude! { pub use crate::diag::{At, TypResult}; pub use crate::eval::{ - Args, Construct, EvalContext, Template, Property, Scope, Set, Smart, StyleChain, - StyleMap, Styled, Value, + Args, Construct, EvalContext, Merge, Property, Scope, Set, Smart, StyleChain, + StyleMap, StyleVec, Template, Value, }; pub use crate::frame::*; pub use crate::geom::*; diff --git a/src/library/par.rs b/src/library/par.rs index 4568a214..962834f2 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -1,6 +1,5 @@ //! Paragraph layout. -use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; use itertools::Either; @@ -11,9 +10,20 @@ use super::prelude::*; use super::{shape, ShapedText, SpacingKind, TextNode}; use crate::util::{ArcExt, EcoString, RangeExt, SliceExt}; -/// Arrange text, spacing and inline nodes into a paragraph. +/// Arrange text, spacing and inline-level nodes into a paragraph. #[derive(Hash)] -pub struct ParNode(pub Vec<Styled<ParChild>>); +pub struct ParNode(pub StyleVec<ParChild>); + +/// A uniformly styled atomic piece of a paragraph. +#[derive(Hash)] +pub enum ParChild { + /// A chunk of text. + Text(EcoString), + /// Horizontal spacing between other children. + Spacing(SpacingKind), + /// An arbitrary inline-level node. + Node(PackedNode), +} #[class] impl ParNode { @@ -23,8 +33,8 @@ impl ParNode { pub const ALIGN: Align = Align::Left; /// The spacing between lines (dependent on scaled font size). pub const LEADING: Linear = Relative::new(0.65).into(); - /// The spacing between paragraphs (dependent on scaled font size). - pub const SPACING: Linear = Relative::new(1.2).into(); + /// The extra spacing between paragraphs (dependent on scaled font size). + pub const SPACING: Linear = Relative::new(0.55).into(); fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { // The paragraph constructor is special: It doesn't create a paragraph @@ -116,9 +126,9 @@ impl ParNode { /// The string representation of each child. fn strings(&self) -> impl Iterator<Item = &str> { - self.0.iter().map(|styled| match &styled.item { + self.0.items().map(|child| match child { + ParChild::Text(text) => text, ParChild::Spacing(_) => " ", - ParChild::Text(text) => &text.0, ParChild::Node(_) => "\u{FFFC}", }) } @@ -127,38 +137,31 @@ impl ParNode { impl Debug for ParNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Par ")?; - f.debug_list().entries(&self.0).finish() - } -} - -/// A child of a paragraph node. -#[derive(Hash)] -pub enum ParChild { - /// Spacing between other nodes. - Spacing(SpacingKind), - /// A run of text and how to align it in its line. - Text(TextNode), - /// Any child node and how to align it in its line. - Node(PackedNode), -} - -impl ParChild { - /// Create a text child. - pub fn text(text: impl Into<EcoString>) -> Self { - Self::Text(TextNode(text.into())) + self.0.fmt(f) } } impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Spacing(kind) => kind.fmt(f), - Self::Text(text) => text.fmt(f), + Self::Text(text) => write!(f, "Text({:?})", text), + Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Node(node) => node.fmt(f), } } } +impl Merge for ParChild { + fn merge(&mut self, next: &Self) -> bool { + if let (Self::Text(left), Self::Text(right)) = (self, next) { + left.push_str(right); + true + } else { + false + } + } +} + /// A paragraph break. pub struct ParbreakNode; @@ -222,20 +225,9 @@ impl<'a> ParLayouter<'a> { let mut ranges = vec![]; // Layout the children and collect them into items. - for (range, styled) in par.ranges().zip(&par.0) { - let styles = styled.map.chain(styles); - match styled.item { - ParChild::Spacing(kind) => match kind { - SpacingKind::Linear(v) => { - let resolved = v.resolve(regions.current.x); - items.push(ParItem::Absolute(resolved)); - ranges.push(range); - } - SpacingKind::Fractional(v) => { - items.push(ParItem::Fractional(v)); - ranges.push(range); - } - }, + for (range, (child, map)) in par.ranges().zip(par.0.iter()) { + let styles = map.chain(styles); + match child { ParChild::Text(_) => { // TODO: Also split by language and script. let mut cursor = range.start; @@ -249,7 +241,18 @@ impl<'a> ParLayouter<'a> { ranges.push(subrange); } } - ParChild::Node(ref node) => { + ParChild::Spacing(kind) => match *kind { + SpacingKind::Linear(v) => { + let resolved = v.resolve(regions.current.x); + items.push(ParItem::Absolute(resolved)); + ranges.push(range); + } + SpacingKind::Fractional(v) => { + items.push(ParItem::Fractional(v)); + ranges.push(range); + } + }, + ParChild::Node(node) => { let size = Size::new(regions.current.x, regions.base.y); let pod = Regions::one(size, regions.base, Spec::splat(false)); let frame = node.layout(ctx, &pod, styles).remove(0); diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 0b555510..feadf7f3 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -31,6 +31,13 @@ pub enum SpacingKind { Fractional(Fractional), } +impl SpacingKind { + /// Whether this is fractional spacing. + pub fn is_fractional(self) -> bool { + matches!(self, Self::Fractional(_)) + } +} + castable! { SpacingKind, Expected: "linear or fractional", diff --git a/src/library/text.rs b/src/library/text.rs index 4aa1bdec..fb0f7e08 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -19,7 +19,7 @@ use crate::util::{EcoString, SliceExt}; /// A single run of text with the same style. #[derive(Hash)] -pub struct TextNode(pub EcoString); +pub struct TextNode; #[class] impl TextNode { @@ -144,12 +144,6 @@ impl TextNode { } } -impl Debug for TextNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Text({:?})", self.0) - } -} - /// Strong text, rendered in boldface. pub struct StrongNode; |
