From 92c01da36016e94ff20163806ddcbcf7e33d4031 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 10 Oct 2020 22:19:36 +0200 Subject: =?UTF-8?q?Switch=20back=20to=20custom=20geometry=20types,=20unifi?= =?UTF-8?q?ed=20with=20layout=20primitives=20=F0=9F=8F=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/document.rs | 47 ++++ src/layout/fixed.rs | 42 ++++ src/layout/mod.rs | 29 ++- src/layout/node.rs | 142 ++++++++++++ src/layout/nodes/document.rs | 47 ---- src/layout/nodes/fixed.rs | 42 ---- src/layout/nodes/mod.rs | 160 ------------- src/layout/nodes/pad.rs | 53 ----- src/layout/nodes/par.rs | 529 ------------------------------------------- src/layout/nodes/spacing.rs | 51 ----- src/layout/nodes/stack.rs | 206 ----------------- src/layout/nodes/text.rs | 51 ----- src/layout/pad.rs | 53 +++++ src/layout/par.rs | 505 +++++++++++++++++++++++++++++++++++++++++ src/layout/primitive.rs | 510 ----------------------------------------- src/layout/spacing.rs | 51 +++++ src/layout/stack.rs | 206 +++++++++++++++++ src/layout/text.rs | 51 +++++ 18 files changed, 1117 insertions(+), 1658 deletions(-) create mode 100644 src/layout/document.rs create mode 100644 src/layout/fixed.rs create mode 100644 src/layout/node.rs delete mode 100644 src/layout/nodes/document.rs delete mode 100644 src/layout/nodes/fixed.rs delete mode 100644 src/layout/nodes/mod.rs delete mode 100644 src/layout/nodes/pad.rs delete mode 100644 src/layout/nodes/par.rs delete mode 100644 src/layout/nodes/spacing.rs delete mode 100644 src/layout/nodes/stack.rs delete mode 100644 src/layout/nodes/text.rs create mode 100644 src/layout/pad.rs create mode 100644 src/layout/par.rs delete mode 100644 src/layout/primitive.rs create mode 100644 src/layout/spacing.rs create mode 100644 src/layout/stack.rs create mode 100644 src/layout/text.rs (limited to 'src/layout') diff --git a/src/layout/document.rs b/src/layout/document.rs new file mode 100644 index 00000000..c2d7b38b --- /dev/null +++ b/src/layout/document.rs @@ -0,0 +1,47 @@ +use super::*; + +/// The top-level layout node. +#[derive(Debug, Clone, PartialEq)] +pub struct Document { + pub runs: Vec, +} + +impl Document { + /// Layout the document. + pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec { + let mut layouts = vec![]; + for run in &self.runs { + layouts.extend(run.layout(ctx).await); + } + layouts + } +} + +/// A variable-length run of pages that all have the same properties. +#[derive(Debug, Clone, PartialEq)] +pub struct Pages { + /// The size of the pages. + pub size: Size, + /// The layout node that produces the actual pages. + pub child: LayoutNode, +} + +impl Pages { + /// Layout the page run. + pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec { + let constraints = LayoutConstraints { + spaces: vec![LayoutSpace { base: self.size, size: self.size }], + repeat: true, + }; + + self.child + .layout(ctx, constraints) + .await + .into_iter() + .filter_map(|item| match item { + LayoutItem::Spacing(_) => None, + LayoutItem::Box(layout, _) => Some(layout), + }) + .collect() + } +} diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs new file mode 100644 index 00000000..0d438879 --- /dev/null +++ b/src/layout/fixed.rs @@ -0,0 +1,42 @@ +use super::*; +use crate::geom::Linear; + +/// A node that can fix its child's width and height. +#[derive(Debug, Clone, PartialEq)] +pub struct Fixed { + pub width: Option, + pub height: Option, + pub child: LayoutNode, +} + +#[async_trait(?Send)] +impl Layout for Fixed { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec { + let space = constraints.spaces[0]; + let size = Size::new( + self.width + .map(|w| w.eval(space.base.width)) + .unwrap_or(space.size.width), + self.height + .map(|h| h.eval(space.base.height)) + .unwrap_or(space.size.height), + ); + + self.child + .layout(ctx, LayoutConstraints { + spaces: vec![LayoutSpace { base: size, size }], + repeat: false, + }) + .await + } +} + +impl From for LayoutNode { + fn from(fixed: Fixed) -> Self { + Self::dynamic(fixed) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index bfd633d8..2368c441 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,17 +1,28 @@ //! Layouting of documents. -pub mod nodes; -pub mod primitive; - -pub use primitive::*; +mod document; +mod fixed; +mod node; +mod pad; +mod par; +mod spacing; +mod stack; +mod text; use async_trait::async_trait; use crate::font::SharedFontLoader; -use crate::geom::{Point, Rect, Size, SizeExt}; +use crate::geom::*; use crate::shaping::Shaped; -use nodes::Document; +pub use document::*; +pub use fixed::*; +pub use node::*; +pub use pad::*; +pub use par::*; +pub use spacing::*; +pub use stack::*; +pub use text::*; /// Layout a document and return the produced layouts. pub async fn layout(document: &Document, loader: SharedFontLoader) -> Vec { @@ -53,9 +64,9 @@ pub trait Layout { #[derive(Debug, Clone, PartialEq)] pub enum LayoutItem { /// Spacing that should be added to the parent. - Spacing(f64), + Spacing(Length), /// A box that should be aligned in the parent. - Box(BoxLayout, Gen2), + Box(BoxLayout, Gen), } /// The constraints for layouting a single node. @@ -101,7 +112,7 @@ impl BoxLayout { /// given position. pub fn push_layout(&mut self, pos: Point, more: Self) { for (subpos, element) in more.elements { - self.push(pos + subpos.to_vec2(), element); + self.push(pos + subpos, element); } } } diff --git a/src/layout/node.rs b/src/layout/node.rs new file mode 100644 index 00000000..3230621f --- /dev/null +++ b/src/layout/node.rs @@ -0,0 +1,142 @@ +//! Layout nodes. + +use std::any::Any; +use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; + +use super::*; + +/// A self-contained, styled layout node. +#[derive(Clone, PartialEq)] +pub enum LayoutNode { + /// A spacing node. + Spacing(Spacing), + /// A text node. + Text(Text), + /// A dynamic that can implement custom layouting behaviour. + Dyn(Dynamic), +} + +impl LayoutNode { + /// Create a new model node form a type implementing `DynNode`. + pub fn dynamic(inner: T) -> Self { + Self::Dyn(Dynamic::new(inner)) + } +} + +impl Debug for LayoutNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Spacing(spacing) => spacing.fmt(f), + Self::Text(text) => text.fmt(f), + Self::Dyn(boxed) => boxed.fmt(f), + } + } +} + +#[async_trait(?Send)] +impl Layout for LayoutNode { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec { + match self { + Self::Spacing(spacing) => spacing.layout(ctx, constraints).await, + Self::Text(text) => text.layout(ctx, constraints).await, + Self::Dyn(boxed) => boxed.layout(ctx, constraints).await, + } + } +} + +/// A wrapper around a boxed dynamic node. +/// +/// _Note_: This is needed because the compiler can't `derive(PartialEq)` for +/// [`LayoutNode`] when directly putting the boxed node in there, see +/// the [Rust Issue]. +/// +/// [`LayoutNode`]: enum.LayoutNode.html +/// [Rust Issue]: https://github.com/rust-lang/rust/issues/31740 +pub struct Dynamic(pub Box); + +impl Dynamic { + /// Wrap a type implementing `DynNode`. + pub fn new(inner: T) -> Self { + Self(Box::new(inner)) + } +} + +impl Deref for Dynamic { + type Target = dyn DynNode; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl Debug for Dynamic { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Clone for Dynamic { + fn clone(&self) -> Self { + Self(self.0.dyn_clone()) + } +} + +impl PartialEq for Dynamic { + fn eq(&self, other: &Self) -> bool { + self.0.dyn_eq(other.0.as_ref()) + } +} + +impl From for LayoutNode { + fn from(dynamic: Dynamic) -> Self { + Self::Dyn(dynamic) + } +} + +/// A dynamic node, which can implement custom layouting behaviour. +/// +/// This trait just combines the requirements for types to qualify as dynamic +/// nodes. The interesting part happens in the inherited trait [`Layout`]. +/// +/// The trait itself also contains three helper methods to make `Box` able to implement `Clone` and `PartialEq`. However, these are +/// automatically provided by a blanket impl as long as the type in question +/// implements[`Layout`], `Debug`, `PartialEq`, `Clone` and is `'static`. +/// +/// [`Layout`]: ../trait.Layout.html +pub trait DynNode: Debug + Layout + 'static { + /// Convert into a `dyn Any` to enable downcasting. + fn as_any(&self) -> &dyn Any; + + /// Check for equality with another trait object. + fn dyn_eq(&self, other: &dyn DynNode) -> bool; + + /// Clone into a trait object. + fn dyn_clone(&self) -> Box; +} + +impl DynNode for T +where + T: Debug + Layout + PartialEq + Clone + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn dyn_eq(&self, other: &dyn DynNode) -> bool { + if let Some(other) = other.as_any().downcast_ref::() { + self == other + } else { + false + } + } + + fn dyn_clone(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/src/layout/nodes/document.rs b/src/layout/nodes/document.rs deleted file mode 100644 index 5c7a2410..00000000 --- a/src/layout/nodes/document.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::*; - -/// The top-level layouting node. -#[derive(Debug, Clone, PartialEq)] -pub struct Document { - pub runs: Vec, -} - -impl Document { - /// Layout the document. - pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec { - let mut layouts = vec![]; - for run in &self.runs { - layouts.extend(run.layout(ctx).await); - } - layouts - } -} - -/// A variable-length run of pages that all have the same properties. -#[derive(Debug, Clone, PartialEq)] -pub struct Pages { - /// The size of the pages. - pub size: Size, - /// The layout node that produces the actual pages. - pub child: LayoutNode, -} - -impl Pages { - /// Layout the page run. - pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec { - let constraints = LayoutConstraints { - spaces: vec![LayoutSpace { base: self.size, size: self.size }], - repeat: true, - }; - - self.child - .layout(ctx, constraints) - .await - .into_iter() - .filter_map(|item| match item { - LayoutItem::Spacing(_) => None, - LayoutItem::Box(layout, _) => Some(layout), - }) - .collect() - } -} diff --git a/src/layout/nodes/fixed.rs b/src/layout/nodes/fixed.rs deleted file mode 100644 index 0d438879..00000000 --- a/src/layout/nodes/fixed.rs +++ /dev/null @@ -1,42 +0,0 @@ -use super::*; -use crate::geom::Linear; - -/// A node that can fix its child's width and height. -#[derive(Debug, Clone, PartialEq)] -pub struct Fixed { - pub width: Option, - pub height: Option, - pub child: LayoutNode, -} - -#[async_trait(?Send)] -impl Layout for Fixed { - async fn layout( - &self, - ctx: &mut LayoutContext, - constraints: LayoutConstraints, - ) -> Vec { - let space = constraints.spaces[0]; - let size = Size::new( - self.width - .map(|w| w.eval(space.base.width)) - .unwrap_or(space.size.width), - self.height - .map(|h| h.eval(space.base.height)) - .unwrap_or(space.size.height), - ); - - self.child - .layout(ctx, LayoutConstraints { - spaces: vec![LayoutSpace { base: size, size }], - repeat: false, - }) - .await - } -} - -impl From for LayoutNode { - fn from(fixed: Fixed) -> Self { - Self::dynamic(fixed) - } -} diff --git a/src/layout/nodes/mod.rs b/src/layout/nodes/mod.rs deleted file mode 100644 index a304e63c..00000000 --- a/src/layout/nodes/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! Layout nodes. - -mod document; -mod fixed; -mod pad; -mod par; -mod spacing; -mod stack; -mod text; - -pub use document::*; -pub use fixed::*; -pub use pad::*; -pub use par::*; -pub use spacing::*; -pub use stack::*; -pub use text::*; - -use std::any::Any; -use std::fmt::{self, Debug, Formatter}; -use std::ops::Deref; - -use async_trait::async_trait; - -use super::*; - -/// A self-contained, styled layout node. -#[derive(Clone, PartialEq)] -pub enum LayoutNode { - /// A spacing node. - Spacing(Spacing), - /// A text node. - Text(Text), - /// A dynamic that can implement custom layouting behaviour. - Dyn(Dynamic), -} - -impl LayoutNode { - /// Create a new model node form a type implementing `DynNode`. - pub fn dynamic(inner: T) -> Self { - Self::Dyn(Dynamic::new(inner)) - } -} - -impl Debug for LayoutNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Spacing(spacing) => spacing.fmt(f), - Self::Text(text) => text.fmt(f), - Self::Dyn(boxed) => boxed.fmt(f), - } - } -} - -#[async_trait(?Send)] -impl Layout for LayoutNode { - async fn layout( - &self, - ctx: &mut LayoutContext, - constraints: LayoutConstraints, - ) -> Vec { - match self { - Self::Spacing(spacing) => spacing.layout(ctx, constraints).await, - Self::Text(text) => text.layout(ctx, constraints).await, - Self::Dyn(boxed) => boxed.layout(ctx, constraints).await, - } - } -} - -/// A wrapper around a boxed dynamic node. -/// -/// _Note_: This is needed because the compiler can't `derive(PartialEq)` for -/// [`LayoutNode`] when directly putting the boxed node in there, see -/// the [Rust Issue]. -/// -/// [`LayoutNode`]: enum.LayoutNode.html -/// [Rust Issue]: https://github.com/rust-lang/rust/issues/31740 -pub struct Dynamic(pub Box); - -impl Dynamic { - /// Wrap a type implementing `DynNode`. - pub fn new(inner: T) -> Self { - Self(Box::new(inner)) - } -} - -impl Deref for Dynamic { - type Target = dyn DynNode; - - fn deref(&self) -> &Self::Target { - self.0.as_ref() - } -} - -impl Debug for Dynamic { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Clone for Dynamic { - fn clone(&self) -> Self { - Self(self.0.dyn_clone()) - } -} - -impl PartialEq for Dynamic { - fn eq(&self, other: &Self) -> bool { - self.0.dyn_eq(other.0.as_ref()) - } -} - -impl From for LayoutNode { - fn from(dynamic: Dynamic) -> Self { - Self::Dyn(dynamic) - } -} - -/// A dynamic node, which can implement custom layouting behaviour. -/// -/// This trait just combines the requirements for types to qualify as dynamic -/// nodes. The interesting part happens in the inherited trait [`Layout`]. -/// -/// The trait itself also contains three helper methods to make `Box` able to implement `Clone` and `PartialEq`. However, these are -/// automatically provided by a blanket impl as long as the type in question -/// implements[`Layout`], `Debug`, `PartialEq`, `Clone` and is `'static`. -/// -/// [`Layout`]: ../trait.Layout.html -pub trait DynNode: Debug + Layout + 'static { - /// Convert into a `dyn Any` to enable downcasting. - fn as_any(&self) -> &dyn Any; - - /// Check for equality with another trait object. - fn dyn_eq(&self, other: &dyn DynNode) -> bool; - - /// Clone into a trait object. - fn dyn_clone(&self) -> Box; -} - -impl DynNode for T -where - T: Debug + Layout + PartialEq + Clone + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn dyn_eq(&self, other: &dyn DynNode) -> bool { - if let Some(other) = other.as_any().downcast_ref::() { - self == other - } else { - false - } - } - - fn dyn_clone(&self) -> Box { - Box::new(self.clone()) - } -} diff --git a/src/layout/nodes/pad.rs b/src/layout/nodes/pad.rs deleted file mode 100644 index 10a9e2c6..00000000 --- a/src/layout/nodes/pad.rs +++ /dev/null @@ -1,53 +0,0 @@ -use super::*; -use crate::geom::Linear; - -/// A node that pads its child at the sides. -#[derive(Debug, Clone, PartialEq)] -pub struct Pad { - pub padding: Sides, - pub child: LayoutNode, -} - -#[async_trait(?Send)] -impl Layout for Pad { - async fn layout( - &self, - ctx: &mut LayoutContext, - constraints: LayoutConstraints, - ) -> Vec { - self.child - .layout(ctx, LayoutConstraints { - spaces: constraints - .spaces - .into_iter() - .map(|space| LayoutSpace { - base: space.base + self.padding.insets(space.base).size(), - size: space.size + self.padding.insets(space.size).size(), - }) - .collect(), - repeat: constraints.repeat, - }) - .await - .into_iter() - .map(|item| match item { - LayoutItem::Box(boxed, align) => { - let padding = self.padding.insets(boxed.size); - let padded = boxed.size - padding.size(); - - let mut outer = BoxLayout::new(padded); - let start = Point::new(-padding.x0, -padding.y0); - outer.push_layout(start, boxed); - - LayoutItem::Box(outer, align) - } - item => item, - }) - .collect() - } -} - -impl From for LayoutNode { - fn from(pad: Pad) -> Self { - Self::dynamic(pad) - } -} diff --git a/src/layout/nodes/par.rs b/src/layout/nodes/par.rs deleted file mode 100644 index 082ab963..00000000 --- a/src/layout/nodes/par.rs +++ /dev/null @@ -1,529 +0,0 @@ -use super::*; - -/// A node that arranges its children into a paragraph. -/// -/// Boxes are laid out along the cross axis as long as they fit into a line. -/// When necessary, a line break is inserted and the new line is offset along -/// the main axis by the height of the previous line plus extra line spacing. -#[derive(Debug, Clone, PartialEq)] -pub struct Par { - pub dirs: Gen2, - pub line_spacing: f64, - pub children: Vec, - pub aligns: Gen2, - pub expand: Spec2, -} - -#[async_trait(?Send)] -impl Layout for Par { - async fn layout( - &self, - ctx: &mut LayoutContext, - constraints: LayoutConstraints, - ) -> Vec { - let mut layouter = LineLayouter::new(LineContext { - dirs: self.dirs, - spaces: constraints.spaces, - repeat: constraints.repeat, - line_spacing: self.line_spacing, - expand: self.expand, - }); - - for child in &self.children { - let items = child - .layout(ctx, LayoutConstraints { - spaces: layouter.remaining(), - repeat: constraints.repeat, - }) - .await; - - for item in items { - match item { - LayoutItem::Spacing(amount) => layouter.push_spacing(amount), - LayoutItem::Box(boxed, aligns) => layouter.push_box(boxed, aligns), - } - } - } - - layouter - .finish() - .into_iter() - .map(|boxed| LayoutItem::Box(boxed, self.aligns)) - .collect() - } -} - -impl From for LayoutNode { - fn from(par: Par) -> Self { - Self::dynamic(par) - } -} - -/// Performs the line layouting. -struct LineLayouter { - /// The context used for line layouting. - ctx: LineContext, - /// The underlying layouter that stacks the finished lines. - stack: StackLayouter, - /// The in-progress line. - run: LineRun, -} - -/// The context for line layouting. -#[derive(Debug, Clone)] -struct LineContext { - /// The layout directions. - dirs: Gen2, - /// The spaces to layout into. - spaces: Vec, - /// Whether to spill over into copies of the last space or finish layouting - /// when the last space is used up. - repeat: bool, - /// The spacing to be inserted between each pair of lines. - line_spacing: f64, - /// Whether to expand the size of the resulting layout to the full size of - /// this space or to shrink it to fit the content. - expand: Spec2, -} - -impl LineLayouter { - /// Create a new line layouter. - fn new(ctx: LineContext) -> Self { - Self { - stack: StackLayouter::new(StackContext { - spaces: ctx.spaces.clone(), - dirs: ctx.dirs, - repeat: ctx.repeat, - expand: ctx.expand, - }), - ctx, - run: LineRun::new(), - } - } - - /// Add a layout. - fn push_box(&mut self, layout: BoxLayout, aligns: Gen2) { - let dirs = self.ctx.dirs; - if let Some(prev) = self.run.aligns { - if aligns.main != prev.main { - // TODO: Issue warning for non-fitting alignment in - // non-repeating context. - let fitting = aligns.main >= self.stack.space.allowed_align; - if !fitting && self.ctx.repeat { - self.finish_space(true); - } else { - self.finish_line(); - } - } else if aligns.cross < prev.cross { - self.finish_line(); - } else if aligns.cross > prev.cross { - let usable = self.stack.usable().get(dirs.cross.axis()); - - let mut rest_run = LineRun::new(); - rest_run.size.main = self.run.size.main; - - // FIXME: Alignment in non-expanding parent. - rest_run.usable = Some(match aligns.cross { - GenAlign::Start => unreachable!("start > x"), - GenAlign::Center => usable - 2.0 * self.run.size.cross, - GenAlign::End => usable - self.run.size.cross, - }); - - self.finish_line(); - - // Move back up in the stack layouter. - self.stack.push_spacing(-rest_run.size.main - self.ctx.line_spacing); - self.run = rest_run; - } - } - - let size = layout.size.switch(dirs); - let usable = self.usable(); - - if usable.main < size.main || usable.cross < size.cross { - if !self.line_is_empty() { - self.finish_line(); - } - - // TODO: Issue warning about overflow if there is overflow. - let usable = self.usable(); - if usable.main < size.main || usable.cross < size.cross { - self.stack.skip_to_fitting_space(layout.size); - } - } - - self.run.aligns = Some(aligns); - self.run.layouts.push((self.run.size.cross, layout)); - - self.run.size.cross += size.cross; - self.run.size.main = self.run.size.main.max(size.main); - } - - /// Add spacing to the line. - fn push_spacing(&mut self, mut spacing: f64) { - spacing = spacing.min(self.usable().cross); - self.run.size.cross += spacing; - } - - /// The remaining usable size of the line. - /// - /// This specifies how much more would fit before a line break would be - /// needed. - fn usable(&self) -> Gen2 { - // The base is the usable space of the stack layouter. - let mut usable = self.stack.usable().switch(self.ctx.dirs); - - // If there was another run already, override the stack's size. - if let Some(cross) = self.run.usable { - usable.cross = cross; - } - - usable.cross -= self.run.size.cross; - usable - } - - /// The remaining inner spaces. If something is laid out into these spaces, - /// it will fit into this layouter's underlying stack. - fn remaining(&self) -> Vec { - let mut spaces = self.stack.remaining(); - *spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.main; - spaces - } - - /// Whether the currently set line is empty. - fn line_is_empty(&self) -> bool { - self.run.size == Gen2::ZERO && self.run.layouts.is_empty() - } - - /// Finish everything up and return the final collection of boxes. - fn finish(mut self) -> Vec { - self.finish_line_if_not_empty(); - self.stack.finish() - } - - /// Finish the active space and start a new one. - /// - /// At the top level, this is a page break. - fn finish_space(&mut self, hard: bool) { - self.finish_line_if_not_empty(); - self.stack.finish_space(hard) - } - - /// Finish the active line and start a new one. - fn finish_line(&mut self) { - let dirs = self.ctx.dirs; - - let mut layout = BoxLayout::new(self.run.size.switch(dirs).to_size()); - let aligns = self.run.aligns.unwrap_or_default(); - - let children = std::mem::take(&mut self.run.layouts); - for (offset, child) in children { - let cross = if dirs.cross.is_positive() { - offset - } else { - self.run.size.cross - offset - child.size.get(dirs.cross.axis()) - }; - - let pos = Gen2::new(0.0, cross).switch(dirs).to_point(); - layout.push_layout(pos, child); - } - - self.stack.push_box(layout, aligns); - self.stack.push_spacing(self.ctx.line_spacing); - self.run = LineRun::new(); - } - - fn finish_line_if_not_empty(&mut self) { - if !self.line_is_empty() { - self.finish_line() - } - } -} - -/// A sequence of boxes with the same alignment. A real line can consist of -/// multiple runs with different alignments. -struct LineRun { - /// The so-far accumulated items of the run. - layouts: Vec<(f64, BoxLayout)>, - /// The summed width and maximal height of the run. - size: Gen2, - /// The alignment of all layouts in the line. - /// - /// When a new run is created the alignment is yet to be determined and - /// `None` as such. Once a layout is added, its alignment decides the - /// alignment for the whole run. - aligns: Option>, - /// The amount of cross-space left by another run on the same line or `None` - /// if this is the only run so far. - usable: Option, -} - -impl LineRun { - fn new() -> Self { - Self { - layouts: vec![], - size: Gen2::ZERO, - aligns: None, - usable: None, - } - } -} - -/// Performs the stack layouting. -pub(super) struct StackLayouter { - /// The context used for stack layouting. - pub ctx: StackContext, - /// The finished layouts. - pub layouts: Vec, - /// The in-progress space. - pub space: Space, -} - -/// The context for stack layouting. -#[derive(Debug, Clone)] -pub(super) struct StackContext { - /// The layouting directions. - pub dirs: Gen2, - /// The spaces to layout into. - pub spaces: Vec, - /// Whether to spill over into copies of the last space or finish layouting - /// when the last space is used up. - pub repeat: bool, - /// Whether to expand the size of the resulting layout to the full size of - /// this space or to shrink it to fit the content. - pub expand: Spec2, -} - -impl StackLayouter { - /// Create a new stack layouter. - pub fn new(ctx: StackContext) -> Self { - let space = ctx.spaces[0]; - Self { - ctx, - layouts: vec![], - space: Space::new(0, true, space.size), - } - } - - /// Add a layout to the stack. - pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen2) { - // If the alignment cannot be fitted in this space, finish it. - // - // TODO: Issue warning for non-fitting alignment in non-repeating - // context. - if aligns.main < self.space.allowed_align && self.ctx.repeat { - self.finish_space(true); - } - - // TODO: Issue warning about overflow if there is overflow in a - // non-repeating context. - if !self.space.usable.fits(layout.size) && self.ctx.repeat { - self.skip_to_fitting_space(layout.size); - } - - // Change the usable space and size of the space. - self.update_metrics(layout.size.switch(self.ctx.dirs)); - - // Add the box to the vector and remember that spacings are allowed - // again. - self.space.layouts.push((layout, aligns)); - self.space.allowed_align = aligns.main; - } - - /// Add spacing to the stack. - pub fn push_spacing(&mut self, mut spacing: f64) { - // Reduce the spacing such that it definitely fits. - let axis = self.ctx.dirs.main.axis(); - spacing = spacing.min(self.space.usable.get(axis)); - - let size = Gen2::new(spacing, 0.0); - self.update_metrics(size); - self.space.layouts.push(( - BoxLayout::new(size.switch(self.ctx.dirs).to_size()), - Gen2::default(), - )); - } - - fn update_metrics(&mut self, added: Gen2) { - let mut used = self.space.used.switch(self.ctx.dirs); - used.cross = used.cross.max(added.cross); - used.main += added.main; - self.space.used = used.switch(self.ctx.dirs).to_size(); - *self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.main; - } - - /// Move to the first space that can fit the given size or do nothing - /// if no space is capable of that. - pub fn skip_to_fitting_space(&mut self, size: Size) { - let start = self.next_space(); - for (index, space) in self.ctx.spaces[start ..].iter().enumerate() { - if space.size.fits(size) { - self.finish_space(true); - self.start_space(start + index, true); - break; - } - } - } - - /// The remaining inner spaces. If something is laid out into these spaces, - /// it will fit into this stack. - pub fn remaining(&self) -> Vec { - let mut spaces = vec![LayoutSpace { - base: self.space.size, - size: self.space.usable, - }]; - - spaces.extend(&self.ctx.spaces[self.next_space() ..]); - spaces - } - - /// The remaining usable size. - pub fn usable(&self) -> Size { - self.space.usable - } - - /// Whether the current layout space is empty. - pub fn space_is_empty(&self) -> bool { - self.space.used == Size::ZERO && self.space.layouts.is_empty() - } - - /// Finish everything up and return the final collection of boxes. - pub fn finish(mut self) -> Vec { - if self.space.hard || !self.space_is_empty() { - self.finish_space(false); - } - self.layouts - } - - /// Finish active current space and start a new one. - pub fn finish_space(&mut self, hard: bool) { - let dirs = self.ctx.dirs; - - // ------------------------------------------------------------------ // - // Step 1: Determine the full size of the space. - // (Mostly done already while collecting the boxes, but here we - // expand if necessary.) - - let space = self.ctx.spaces[self.space.index]; - let layout_size = { - let mut used_size = self.space.used; - if self.ctx.expand.horizontal { - used_size.width = space.size.width; - } - if self.ctx.expand.vertical { - used_size.height = space.size.height; - } - used_size - }; - - let mut layout = BoxLayout::new(layout_size); - - // ------------------------------------------------------------------ // - // Step 2: Forward pass. Create a bounding box for each layout in which - // it will be aligned. Then, go forwards through the boxes and remove - // what is taken by previous layouts from the following layouts. - - let mut bounds = vec![]; - let mut bound = Rect { - x0: 0.0, - y0: 0.0, - x1: layout_size.width, - y1: layout_size.height, - }; - - for (layout, _) in &self.space.layouts { - // First, store the bounds calculated so far (which were reduced - // by the predecessors of this layout) as the initial bounding box - // of this layout. - bounds.push(bound); - - // Then, reduce the bounding box for the following layouts. This - // layout uses up space from the origin to the end. Thus, it reduces - // the usable space for following layouts at its origin by its - // main-axis extent. - *bound.get_mut(dirs.main.start()) += - dirs.main.factor() * layout.size.get(dirs.main.axis()); - } - - // ------------------------------------------------------------------ // - // Step 3: Backward pass. Reduce the bounding boxes from the previous - // layouts by what is taken by the following ones. - - let mut main_extent = 0.0; - for (child, bound) in self.space.layouts.iter().zip(&mut bounds).rev() { - let (layout, _) = child; - - // Reduce the bounding box of this layout by the following one's - // main-axis extents. - *bound.get_mut(dirs.main.end()) -= dirs.main.factor() * main_extent; - - // And then, include this layout's main-axis extent. - main_extent += layout.size.get(dirs.main.axis()); - } - - // ------------------------------------------------------------------ // - // Step 4: Align each layout in its bounding box and collect everything - // into a single finished layout. - - let children = std::mem::take(&mut self.space.layouts); - for ((child, aligns), bound) in children.into_iter().zip(bounds) { - // Align the child in its own bounds. - let local = - bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns); - - // Make the local position in the bounds global. - let pos = bound.origin() + local; - layout.push_layout(pos, child); - } - - self.layouts.push(layout); - - // ------------------------------------------------------------------ // - // Step 5: Start the next space. - - self.start_space(self.next_space(), hard) - } - - fn start_space(&mut self, index: usize, hard: bool) { - let space = self.ctx.spaces[index]; - self.space = Space::new(index, hard, space.size); - } - - fn next_space(&self) -> usize { - (self.space.index + 1).min(self.ctx.spaces.len() - 1) - } -} - -/// A layout space composed of subspaces which can have different directions and -/// alignments. -#[derive(Debug)] -pub(super) struct Space { - /// The index of this space in `ctx.spaces`. - index: usize, - /// Whether to include a layout for this space even if it would be empty. - hard: bool, - /// The so-far accumulated layouts. - layouts: Vec<(BoxLayout, Gen2)>, - /// The full size of this space. - size: Size, - /// The used size of this space. - used: Size, - /// The remaining space. - usable: Size, - /// Which alignments for new boxes are still allowed. - pub(super) allowed_align: GenAlign, -} - -impl Space { - fn new(index: usize, hard: bool, size: Size) -> Self { - Self { - index, - hard, - layouts: vec![], - size, - used: Size::ZERO, - usable: size, - allowed_align: GenAlign::Start, - } - } -} diff --git a/src/layout/nodes/spacing.rs b/src/layout/nodes/spacing.rs deleted file mode 100644 index 9d72f7ca..00000000 --- a/src/layout/nodes/spacing.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use super::*; - -/// A node that inserts spacing. -#[derive(Copy, Clone, PartialEq)] -pub struct Spacing { - pub amount: f64, - pub softness: Softness, -} - -#[async_trait(?Send)] -impl Layout for Spacing { - async fn layout( - &self, - _: &mut LayoutContext, - _: LayoutConstraints, - ) -> Vec { - vec![LayoutItem::Spacing(self.amount)] - } -} - -impl Debug for Spacing { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self.softness { - Softness::Soft => write!(f, "Soft({})", self.amount), - Softness::Hard => write!(f, "Hard({})", self.amount), - } - } -} - -impl From for LayoutNode { - fn from(spacing: Spacing) -> Self { - Self::Spacing(spacing) - } -} - -/// Defines how spacing interacts with surrounding spacing. -/// -/// Hard spacing assures that a fixed amount of spacing will always be inserted. -/// Soft spacing will be consumed by previous soft spacing or neighbouring hard -/// spacing and can be used to insert overridable spacing, e.g. between words or -/// paragraphs. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Softness { - /// Soft spacing is not laid out if it directly follows other soft spacing - /// or if it touches hard spacing. - Soft, - /// Hard spacing is always laid out and consumes surrounding soft spacing. - Hard, -} diff --git a/src/layout/nodes/stack.rs b/src/layout/nodes/stack.rs deleted file mode 100644 index cca64e62..00000000 --- a/src/layout/nodes/stack.rs +++ /dev/null @@ -1,206 +0,0 @@ -use super::*; - -/// A node that stacks and aligns its children. -/// -/// # Alignment -/// Individual layouts can be aligned at `Start`, `Center` or `End` along both -/// axes. These alignments are with processed with respect to the size of the -/// finished layout and not the total usable size. This means that a later -/// layout can have influence on the position of an earlier one. Consider the -/// following example. -/// ```typst -/// [align: right][A word.] -/// [align: left][A sentence with a couple more words.] -/// ``` -/// The resulting layout looks like this: -/// ```text -/// |--------------------------------------| -/// | A word. | -/// | | -/// | A sentence with a couple more words. | -/// |--------------------------------------| -/// ``` -/// The position of the first aligned box thus depends on the length of the -/// sentence in the second box. -#[derive(Debug, Clone, PartialEq)] -pub struct Stack { - pub dirs: Gen2, - pub children: Vec, - pub aligns: Gen2, - pub expand: Spec2, -} - -#[async_trait(?Send)] -impl Layout for Stack { - async fn layout( - &self, - ctx: &mut LayoutContext, - constraints: LayoutConstraints, - ) -> Vec { - let mut items = vec![]; - - let size = constraints.spaces[0].size; - let mut space = StackSpace::new(self.dirs, self.expand, size); - let mut i = 0; - - for child in &self.children { - let child_constraints = LayoutConstraints { - spaces: { - let mut remaining = vec![LayoutSpace { - base: space.full_size, - size: space.usable, - }]; - let next = (i + 1).min(constraints.spaces.len() - 1); - remaining.extend(&constraints.spaces[next ..]); - remaining - }, - repeat: constraints.repeat, - }; - - for item in child.layout(ctx, child_constraints).await { - match item { - LayoutItem::Spacing(spacing) => space.push_spacing(spacing), - LayoutItem::Box(mut boxed, aligns) => { - let mut last = false; - while let Err(back) = space.push_box(boxed, aligns) { - boxed = back; - if last { - break; - } - - items.push(LayoutItem::Box(space.finish(), self.aligns)); - - if i + 1 < constraints.spaces.len() { - i += 1; - } else { - last = true; - } - - let size = constraints.spaces[i].size; - space = StackSpace::new(self.dirs, self.expand, size); - } - } - } - } - } - - items.push(LayoutItem::Box(space.finish(), self.aligns)); - items - } -} - -struct StackSpace { - dirs: Gen2, - expand: Spec2, - boxes: Vec<(BoxLayout, Gen2)>, - full_size: Size, - usable: Size, - used: Size, - ruler: GenAlign, -} - -impl StackSpace { - fn new(dirs: Gen2, expand: Spec2, size: Size) -> Self { - Self { - dirs, - expand, - boxes: vec![], - full_size: size, - usable: size, - used: Size::ZERO, - ruler: GenAlign::Start, - } - } - - fn push_box( - &mut self, - boxed: BoxLayout, - aligns: Gen2, - ) -> Result<(), BoxLayout> { - let main = self.dirs.main.axis(); - let cross = self.dirs.cross.axis(); - if aligns.main < self.ruler || !self.usable.fits(boxed.size) { - return Err(boxed); - } - - let size = boxed.size.switch(self.dirs); - *self.used.get_mut(cross) = self.used.get(cross).max(size.cross); - *self.used.get_mut(main) += size.main; - *self.usable.get_mut(main) -= size.main; - self.boxes.push((boxed, aligns)); - self.ruler = aligns.main; - - Ok(()) - } - - fn push_spacing(&mut self, spacing: f64) { - let main = self.dirs.main.axis(); - let max = self.usable.get(main); - let trimmed = spacing.min(max); - *self.used.get_mut(main) += trimmed; - *self.usable.get_mut(main) -= trimmed; - - let size = Gen2::new(trimmed, 0.0).switch(self.dirs); - self.boxes.push((BoxLayout::new(size.to_size()), Gen2::default())); - } - - fn finish(mut self) -> BoxLayout { - let dirs = self.dirs; - let main = dirs.main.axis(); - - if self.expand.horizontal { - self.used.width = self.full_size.width; - } - - if self.expand.vertical { - self.used.height = self.full_size.height; - } - - let mut sum = 0.0; - let mut sums = Vec::with_capacity(self.boxes.len() + 1); - - for (boxed, _) in &self.boxes { - sums.push(sum); - sum += boxed.size.get(main); - } - - sums.push(sum); - - let mut layout = BoxLayout::new(self.used); - let used = self.used.switch(dirs); - - for (i, (boxed, aligns)) in self.boxes.into_iter().enumerate() { - let size = boxed.size.switch(dirs); - - let before = sums[i]; - let after = sum - sums[i + 1]; - let main_len = used.main - size.main; - let main_range = if dirs.main.is_positive() { - before .. main_len - after - } else { - main_len - before .. after - }; - - let cross_len = used.cross - size.cross; - let cross_range = if dirs.cross.is_positive() { - 0.0 .. cross_len - } else { - cross_len .. 0.0 - }; - - let main = aligns.main.apply(main_range); - let cross = aligns.cross.apply(cross_range); - let pos = Gen2::new(main, cross).switch(dirs).to_point(); - - layout.push_layout(pos, boxed); - } - - layout - } -} - -impl From for LayoutNode { - fn from(stack: Stack) -> Self { - Self::dynamic(stack) - } -} diff --git a/src/layout/nodes/text.rs b/src/layout/nodes/text.rs deleted file mode 100644 index b0c4a458..00000000 --- a/src/layout/nodes/text.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::rc::Rc; - -use fontdock::{FallbackTree, FontVariant}; - -use super::*; -use crate::shaping; - -/// A text node. -#[derive(Clone, PartialEq)] -pub struct Text { - pub text: String, - pub size: f64, - pub dir: Dir, - pub fallback: Rc, - pub variant: FontVariant, - pub aligns: Gen2, -} - -#[async_trait(?Send)] -impl Layout for Text { - async fn layout( - &self, - ctx: &mut LayoutContext, - _constraints: LayoutConstraints, - ) -> Vec { - let mut loader = ctx.loader.borrow_mut(); - let boxed = shaping::shape( - &self.text, - self.size, - self.dir, - &mut loader, - &self.fallback, - self.variant, - ) - .await; - vec![LayoutItem::Box(boxed, self.aligns)] - } -} - -impl Debug for Text { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Text({})", self.text) - } -} - -impl From for LayoutNode { - fn from(text: Text) -> Self { - Self::Text(text) - } -} diff --git a/src/layout/pad.rs b/src/layout/pad.rs new file mode 100644 index 00000000..2e1817b7 --- /dev/null +++ b/src/layout/pad.rs @@ -0,0 +1,53 @@ +use super::*; +use crate::geom::Linear; + +/// A node that pads its child at the sides. +#[derive(Debug, Clone, PartialEq)] +pub struct Pad { + pub padding: Sides, + pub child: LayoutNode, +} + +#[async_trait(?Send)] +impl Layout for Pad { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec { + self.child + .layout(ctx, LayoutConstraints { + spaces: constraints + .spaces + .into_iter() + .map(|space| LayoutSpace { + base: space.base - self.padding.eval(space.base).size(), + size: space.size - self.padding.eval(space.size).size(), + }) + .collect(), + repeat: constraints.repeat, + }) + .await + .into_iter() + .map(|item| match item { + LayoutItem::Box(boxed, align) => { + let padding = self.padding.eval(boxed.size); + let padded = boxed.size + padding.size(); + + let mut outer = BoxLayout::new(padded); + let start = Point::new(padding.left, padding.top); + outer.push_layout(start, boxed); + + LayoutItem::Box(outer, align) + } + item => item, + }) + .collect() + } +} + +impl From for LayoutNode { + fn from(pad: Pad) -> Self { + Self::dynamic(pad) + } +} diff --git a/src/layout/par.rs b/src/layout/par.rs new file mode 100644 index 00000000..2a139760 --- /dev/null +++ b/src/layout/par.rs @@ -0,0 +1,505 @@ +use super::*; + +/// A node that arranges its children into a paragraph. +/// +/// Boxes are laid out along the cross axis as long as they fit into a line. +/// When necessary, a line break is inserted and the new line is offset along +/// the main axis by the height of the previous line plus extra line spacing. +#[derive(Debug, Clone, PartialEq)] +pub struct Par { + pub dirs: Gen, + pub line_spacing: Length, + pub children: Vec, + pub aligns: Gen, + pub expand: Spec, +} + +#[async_trait(?Send)] +impl Layout for Par { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec { + let mut layouter = LineLayouter::new(LineContext { + dirs: self.dirs, + spaces: constraints.spaces, + repeat: constraints.repeat, + line_spacing: self.line_spacing, + expand: self.expand, + }); + + for child in &self.children { + let items = child + .layout(ctx, LayoutConstraints { + spaces: layouter.remaining(), + repeat: constraints.repeat, + }) + .await; + + for item in items { + match item { + LayoutItem::Spacing(amount) => layouter.push_spacing(amount), + LayoutItem::Box(boxed, aligns) => layouter.push_box(boxed, aligns), + } + } + } + + layouter + .finish() + .into_iter() + .map(|boxed| LayoutItem::Box(boxed, self.aligns)) + .collect() + } +} + +impl From for LayoutNode { + fn from(par: Par) -> Self { + Self::dynamic(par) + } +} + +/// Performs the line layouting. +struct LineLayouter { + /// The context used for line layouting. + ctx: LineContext, + /// The underlying layouter that stacks the finished lines. + stack: StackLayouter, + /// The in-progress line. + run: LineRun, +} + +/// The context for line layouting. +#[derive(Debug, Clone)] +struct LineContext { + /// The layout directions. + dirs: Gen, + /// The spaces to layout into. + spaces: Vec, + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. + repeat: bool, + /// The spacing to be inserted between each pair of lines. + line_spacing: Length, + /// Whether to expand the size of the resulting layout to the full size of + /// this space or to shrink it to fit the content. + expand: Spec, +} + +impl LineLayouter { + /// Create a new line layouter. + fn new(ctx: LineContext) -> Self { + Self { + stack: StackLayouter::new(StackContext { + spaces: ctx.spaces.clone(), + dirs: ctx.dirs, + repeat: ctx.repeat, + expand: ctx.expand, + }), + ctx, + run: LineRun::new(), + } + } + + /// Add a layout. + fn push_box(&mut self, layout: BoxLayout, aligns: Gen) { + let dirs = self.ctx.dirs; + if let Some(prev) = self.run.aligns { + if aligns.main != prev.main { + // TODO: Issue warning for non-fitting alignment in + // non-repeating context. + let fitting = aligns.main >= self.stack.space.allowed_align; + if !fitting && self.ctx.repeat { + self.finish_space(true); + } else { + self.finish_line(); + } + } else if aligns.cross < prev.cross { + self.finish_line(); + } else if aligns.cross > prev.cross { + let usable = self.stack.usable().get(dirs.cross.axis()); + + let mut rest_run = LineRun::new(); + rest_run.size.main = self.run.size.main; + + // FIXME: Alignment in non-expanding parent. + rest_run.usable = Some(match aligns.cross { + Align::Start => unreachable!("start > x"), + Align::Center => usable - 2.0 * self.run.size.cross, + Align::End => usable - self.run.size.cross, + }); + + self.finish_line(); + + // Move back up in the stack layouter. + self.stack.push_spacing(-rest_run.size.main - self.ctx.line_spacing); + self.run = rest_run; + } + } + + let size = layout.size.switch(dirs); + let usable = self.usable(); + + if usable.main < size.main || usable.cross < size.cross { + if !self.line_is_empty() { + self.finish_line(); + } + + // TODO: Issue warning about overflow if there is overflow. + let usable = self.usable(); + if usable.main < size.main || usable.cross < size.cross { + self.stack.skip_to_fitting_space(layout.size); + } + } + + self.run.aligns = Some(aligns); + self.run.layouts.push((self.run.size.cross, layout)); + + self.run.size.cross += size.cross; + self.run.size.main = self.run.size.main.max(size.main); + } + + /// Add spacing to the line. + fn push_spacing(&mut self, mut spacing: Length) { + spacing = spacing.min(self.usable().cross); + self.run.size.cross += spacing; + } + + /// The remaining usable size of the line. + /// + /// This specifies how much more would fit before a line break would be + /// needed. + fn usable(&self) -> Gen { + // The base is the usable space of the stack layouter. + let mut usable = self.stack.usable().switch(self.ctx.dirs); + + // If there was another run already, override the stack's size. + if let Some(cross) = self.run.usable { + usable.cross = cross; + } + + usable.cross -= self.run.size.cross; + usable + } + + /// The remaining inner spaces. If something is laid out into these spaces, + /// it will fit into this layouter's underlying stack. + fn remaining(&self) -> Vec { + let mut spaces = self.stack.remaining(); + *spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.main; + spaces + } + + /// Whether the currently set line is empty. + fn line_is_empty(&self) -> bool { + self.run.size == Gen::ZERO && self.run.layouts.is_empty() + } + + /// Finish everything up and return the final collection of boxes. + fn finish(mut self) -> Vec { + self.finish_line_if_not_empty(); + self.stack.finish() + } + + /// Finish the active space and start a new one. + /// + /// At the top level, this is a page break. + fn finish_space(&mut self, hard: bool) { + self.finish_line_if_not_empty(); + self.stack.finish_space(hard) + } + + /// Finish the active line and start a new one. + fn finish_line(&mut self) { + let dirs = self.ctx.dirs; + + let mut layout = BoxLayout::new(self.run.size.switch(dirs).to_size()); + let aligns = self.run.aligns.unwrap_or_default(); + + let children = std::mem::take(&mut self.run.layouts); + for (offset, child) in children { + let cross = if dirs.cross.is_positive() { + offset + } else { + self.run.size.cross - offset - child.size.get(dirs.cross.axis()) + }; + + let pos = Gen::new(Length::ZERO, cross).switch(dirs).to_point(); + layout.push_layout(pos, child); + } + + self.stack.push_box(layout, aligns); + self.stack.push_spacing(self.ctx.line_spacing); + self.run = LineRun::new(); + } + + fn finish_line_if_not_empty(&mut self) { + if !self.line_is_empty() { + self.finish_line() + } + } +} + +/// A sequence of boxes with the same alignment. A real line can consist of +/// multiple runs with different alignments. +struct LineRun { + /// The so-far accumulated items of the run. + layouts: Vec<(Length, BoxLayout)>, + /// The summed width and maximal height of the run. + size: Gen, + /// The alignment of all layouts in the line. + /// + /// When a new run is created the alignment is yet to be determined and + /// `None` as such. Once a layout is added, its alignment decides the + /// alignment for the whole run. + aligns: Option>, + /// The amount of cross-space left by another run on the same line or `None` + /// if this is the only run so far. + usable: Option, +} + +impl LineRun { + fn new() -> Self { + Self { + layouts: vec![], + size: Gen::ZERO, + aligns: None, + usable: None, + } + } +} + +/// Performs the stack layouting. +pub(super) struct StackLayouter { + /// The context used for stack layouting. + pub ctx: StackContext, + /// The finished layouts. + pub layouts: Vec, + /// The in-progress space. + pub space: Space, +} + +/// The context for stack layouting. +#[derive(Debug, Clone)] +pub(super) struct StackContext { + /// The layouting directions. + pub dirs: Gen, + /// The spaces to layout into. + pub spaces: Vec, + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. + pub repeat: bool, + /// Whether to expand the size of the resulting layout to the full size of + /// this space or to shrink it to fit the content. + pub expand: Spec, +} + +impl StackLayouter { + /// Create a new stack layouter. + pub fn new(ctx: StackContext) -> Self { + let space = ctx.spaces[0]; + Self { + ctx, + layouts: vec![], + space: Space::new(0, true, space.size), + } + } + + /// Add a layout to the stack. + pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen) { + // If the alignment cannot be fitted in this space, finish it. + // + // TODO: Issue warning for non-fitting alignment in non-repeating + // context. + if aligns.main < self.space.allowed_align && self.ctx.repeat { + self.finish_space(true); + } + + // TODO: Issue warning about overflow if there is overflow in a + // non-repeating context. + if !self.space.usable.fits(layout.size) && self.ctx.repeat { + self.skip_to_fitting_space(layout.size); + } + + // Change the usable space and size of the space. + self.update_metrics(layout.size.switch(self.ctx.dirs)); + + // Add the box to the vector and remember that spacings are allowed + // again. + self.space.layouts.push((layout, aligns)); + self.space.allowed_align = aligns.main; + } + + /// Add spacing to the stack. + pub fn push_spacing(&mut self, mut spacing: Length) { + // Reduce the spacing such that it definitely fits. + let axis = self.ctx.dirs.main.axis(); + spacing = spacing.min(self.space.usable.get(axis)); + + let size = Gen::new(spacing, Length::ZERO); + self.update_metrics(size); + self.space.layouts.push(( + BoxLayout::new(size.switch(self.ctx.dirs).to_size()), + Gen::default(), + )); + } + + fn update_metrics(&mut self, added: Gen) { + let mut used = self.space.used.switch(self.ctx.dirs); + used.cross = used.cross.max(added.cross); + used.main += added.main; + self.space.used = used.switch(self.ctx.dirs).to_size(); + *self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.main; + } + + /// Move to the first space that can fit the given size or do nothing + /// if no space is capable of that. + pub fn skip_to_fitting_space(&mut self, size: Size) { + let start = self.next_space(); + for (index, space) in self.ctx.spaces[start ..].iter().enumerate() { + if space.size.fits(size) { + self.finish_space(true); + self.start_space(start + index, true); + break; + } + } + } + + /// The remaining inner spaces. If something is laid out into these spaces, + /// it will fit into this stack. + pub fn remaining(&self) -> Vec { + let mut spaces = vec![LayoutSpace { + base: self.space.size, + size: self.space.usable, + }]; + + spaces.extend(&self.ctx.spaces[self.next_space() ..]); + spaces + } + + /// The remaining usable size. + pub fn usable(&self) -> Size { + self.space.usable + } + + /// Whether the current layout space is empty. + pub fn space_is_empty(&self) -> bool { + self.space.used == Size::ZERO && self.space.layouts.is_empty() + } + + /// Finish everything up and return the final collection of boxes. + pub fn finish(mut self) -> Vec { + if self.space.hard || !self.space_is_empty() { + self.finish_space(false); + } + self.layouts + } + + /// Finish active current space and start a new one. + pub fn finish_space(&mut self, hard: bool) { + let dirs = self.ctx.dirs; + let main = dirs.main.axis(); + + let space = self.ctx.spaces[self.space.index]; + let layout_size = { + let mut used_size = self.space.used; + if self.ctx.expand.horizontal { + used_size.width = space.size.width; + } + if self.ctx.expand.vertical { + used_size.height = space.size.height; + } + used_size + }; + + let mut sum = Length::ZERO; + let mut sums = Vec::with_capacity(self.space.layouts.len() + 1); + + for (boxed, _) in &self.space.layouts { + sums.push(sum); + sum += boxed.size.get(main); + } + + sums.push(sum); + + let mut layout = BoxLayout::new(layout_size); + let used = layout_size.switch(dirs); + + let children = std::mem::take(&mut self.space.layouts); + for (i, (boxed, aligns)) in children.into_iter().enumerate() { + let size = boxed.size.switch(dirs); + + let before = sums[i]; + let after = sum - sums[i + 1]; + let main_len = used.main - size.main; + let main_range = if dirs.main.is_positive() { + before .. main_len - after + } else { + main_len - before .. after + }; + + let cross_len = used.cross - size.cross; + let cross_range = if dirs.cross.is_positive() { + Length::ZERO .. cross_len + } else { + cross_len .. Length::ZERO + }; + + let main = aligns.main.apply(main_range); + let cross = aligns.cross.apply(cross_range); + let pos = Gen::new(main, cross).switch(dirs).to_point(); + + layout.push_layout(pos, boxed); + } + + self.layouts.push(layout); + + // ------------------------------------------------------------------ // + // Step 5: Start the next space. + + self.start_space(self.next_space(), hard) + } + + fn start_space(&mut self, index: usize, hard: bool) { + let space = self.ctx.spaces[index]; + self.space = Space::new(index, hard, space.size); + } + + fn next_space(&self) -> usize { + (self.space.index + 1).min(self.ctx.spaces.len() - 1) + } +} + +/// A layout space composed of subspaces which can have different directions and +/// alignments. +#[derive(Debug)] +pub(super) struct Space { + /// The index of this space in `ctx.spaces`. + index: usize, + /// Whether to include a layout for this space even if it would be empty. + hard: bool, + /// The so-far accumulated layouts. + layouts: Vec<(BoxLayout, Gen)>, + /// The full size of this space. + size: Size, + /// The used size of this space. + used: Size, + /// The remaining space. + usable: Size, + /// Which alignments for new boxes are still allowed. + pub(super) allowed_align: Align, +} + +impl Space { + fn new(index: usize, hard: bool, size: Size) -> Self { + Self { + index, + hard, + layouts: vec![], + size, + used: Size::ZERO, + usable: size, + allowed_align: Align::Start, + } + } +} diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs deleted file mode 100644 index 30bd9363..00000000 --- a/src/layout/primitive.rs +++ /dev/null @@ -1,510 +0,0 @@ -//! Layouting primitives. - -use std::fmt::{self, Display, Formatter}; -use std::ops::Range; - -use crate::geom::{Insets, Linear, Point, Size, Vec2}; - -/// Generic access to a structure's components. -pub trait Get { - /// The structure's component type. - type Component; - - /// Return the component for the specified index. - fn get(self, index: Index) -> Self::Component; - - /// Borrow the component for the specified index mutably. - fn get_mut(&mut self, index: Index) -> &mut Self::Component; -} - -/// Switch between the specific and generic representations of a type. -/// -/// The generic representation deals with main and cross axes while the specific -/// representation deals with horizontal and vertical axes. -pub trait Switch { - /// The type of the other version. - type Other; - - /// The other version of this type based on the current directions. - fn switch(self, dirs: Gen2) -> Self::Other; -} - -/// The four directions into which content can be laid out. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Dir { - /// Left to right. - LTR, - /// Right to left. - RTL, - /// Top to bottom. - TTB, - /// Bottom to top. - BTT, -} - -impl Dir { - /// The specific axis this direction belongs to. - pub fn axis(self) -> SpecAxis { - match self { - Self::LTR | Self::RTL => SpecAxis::Horizontal, - Self::TTB | Self::BTT => SpecAxis::Vertical, - } - } - - /// The side this direction starts at. - pub fn start(self) -> Side { - match self { - Self::LTR => Side::Left, - Self::RTL => Side::Right, - Self::TTB => Side::Top, - Self::BTT => Side::Bottom, - } - } - - /// The side this direction ends at. - pub fn end(self) -> Side { - match self { - Self::LTR => Side::Right, - Self::RTL => Side::Left, - Self::TTB => Side::Bottom, - Self::BTT => Side::Top, - } - } - - /// Whether this direction points into the positive coordinate direction. - /// - /// The positive directions are left-to-right and top-to-bottom. - pub fn is_positive(self) -> bool { - match self { - Self::LTR | Self::TTB => true, - Self::RTL | Self::BTT => false, - } - } - - /// The factor for this direction. - /// - /// - `1.0` if the direction is positive. - /// - `-1.0` if the direction is negative. - pub fn factor(self) -> f64 { - if self.is_positive() { 1.0 } else { -1.0 } - } - - /// The inverse direction. - pub fn inv(self) -> Self { - match self { - Self::LTR => Self::RTL, - Self::RTL => Self::LTR, - Self::TTB => Self::BTT, - Self::BTT => Self::TTB, - } - } -} - -impl Display for Dir { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::LTR => "ltr", - Self::RTL => "rtl", - Self::TTB => "ttb", - Self::BTT => "btt", - }) - } -} - -/// A generic container with two components for the two generic axes. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Gen2 { - /// The main component. - pub main: T, - /// The cross component. - pub cross: T, -} - -impl Gen2 { - /// Create a new instance from the two components. - pub fn new(main: T, cross: T) -> Self { - Self { main, cross } - } -} - -impl Gen2 { - /// The instance that has both components set to zero. - pub const ZERO: Self = Self { main: 0.0, cross: 0.0 }; -} - -impl Get for Gen2 { - type Component = T; - - fn get(self, axis: GenAxis) -> T { - match axis { - GenAxis::Main => self.main, - GenAxis::Cross => self.cross, - } - } - - fn get_mut(&mut self, axis: GenAxis) -> &mut T { - match axis { - GenAxis::Main => &mut self.main, - GenAxis::Cross => &mut self.cross, - } - } -} - -impl Switch for Gen2 { - type Other = Spec2; - - fn switch(self, dirs: Gen2) -> Self::Other { - match dirs.main.axis() { - SpecAxis::Horizontal => Spec2::new(self.main, self.cross), - SpecAxis::Vertical => Spec2::new(self.cross, self.main), - } - } -} - -/// A generic container with two components for the two specific axes. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Spec2 { - /// The horizontal component. - pub horizontal: T, - /// The vertical component. - pub vertical: T, -} - -impl Spec2 { - /// Create a new instance from the two components. - pub fn new(horizontal: T, vertical: T) -> Self { - Self { horizontal, vertical } - } -} - -impl Spec2 { - /// The instance that has both components set to zero. - pub const ZERO: Self = Self { horizontal: 0.0, vertical: 0.0 }; - - /// Convert to a 2D vector. - pub fn to_vec2(self) -> Vec2 { - Vec2::new(self.horizontal, self.vertical) - } - - /// Convert to a point. - pub fn to_point(self) -> Point { - Point::new(self.horizontal, self.vertical) - } - - /// Convert to a size. - pub fn to_size(self) -> Size { - Size::new(self.horizontal, self.vertical) - } -} - -impl Get for Spec2 { - type Component = T; - - fn get(self, axis: SpecAxis) -> T { - match axis { - SpecAxis::Horizontal => self.horizontal, - SpecAxis::Vertical => self.vertical, - } - } - - fn get_mut(&mut self, axis: SpecAxis) -> &mut T { - match axis { - SpecAxis::Horizontal => &mut self.horizontal, - SpecAxis::Vertical => &mut self.vertical, - } - } -} - -impl Switch for Spec2 { - type Other = Gen2; - - fn switch(self, dirs: Gen2) -> Self::Other { - match dirs.main.axis() { - SpecAxis::Horizontal => Gen2::new(self.horizontal, self.vertical), - SpecAxis::Vertical => Gen2::new(self.vertical, self.horizontal), - } - } -} - -/// The two generic layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum GenAxis { - /// The axis pages and paragraphs are set along. - Main, - /// The axis words and lines are set along. - Cross, -} - -impl GenAxis { - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Main => Self::Cross, - Self::Cross => Self::Main, - } - } -} - -impl Switch for GenAxis { - type Other = SpecAxis; - - fn switch(self, dirs: Gen2) -> Self::Other { - match self { - Self::Main => dirs.main.axis(), - Self::Cross => dirs.cross.axis(), - } - } -} - -impl Display for GenAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Main => "main", - Self::Cross => "cross", - }) - } -} - -/// The two specific layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum SpecAxis { - /// The vertical layouting axis. - Vertical, - /// The horizontal layouting axis. - Horizontal, -} - -impl SpecAxis { - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Horizontal => Self::Vertical, - Self::Vertical => Self::Horizontal, - } - } -} - -impl Switch for SpecAxis { - type Other = GenAxis; - - fn switch(self, dirs: Gen2) -> Self::Other { - if self == dirs.main.axis() { - GenAxis::Main - } else { - debug_assert_eq!(self, dirs.cross.axis()); - GenAxis::Cross - } - } -} - -impl Display for SpecAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Vertical => "vertical", - Self::Horizontal => "horizontal", - }) - } -} - -/// Where to align content along an axis in a generic context. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum GenAlign { - Start, - Center, - End, -} - -impl GenAlign { - /// Returns the position of this alignment in the given length. - pub fn apply(self, range: Range) -> f64 { - match self { - Self::Start => range.start, - Self::Center => (range.start + range.end) / 2.0, - Self::End => range.end, - } - } - - /// The inverse alignment. - pub fn inv(self) -> Self { - match self { - Self::Start => Self::End, - Self::Center => Self::Center, - Self::End => Self::Start, - } - } -} - -impl Default for GenAlign { - fn default() -> Self { - Self::Start - } -} - -impl Display for GenAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Start => "start", - Self::Center => "center", - Self::End => "end", - }) - } -} - -/// Where to align content along an axis in a specific context. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum SpecAlign { - Left, - Right, - Top, - Bottom, - Center, -} - -impl SpecAlign { - /// The specific axis this alignment refers to. - /// - /// Returns `None` if this is `Center` since the axis is unknown. - pub fn axis(self) -> Option { - match self { - Self::Left => Some(SpecAxis::Horizontal), - Self::Right => Some(SpecAxis::Horizontal), - Self::Top => Some(SpecAxis::Vertical), - Self::Bottom => Some(SpecAxis::Vertical), - Self::Center => None, - } - } - - /// The inverse alignment. - pub fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Right => Self::Left, - Self::Top => Self::Bottom, - Self::Bottom => Self::Top, - Self::Center => Self::Center, - } - } -} - -impl Switch for SpecAlign { - type Other = GenAlign; - - fn switch(self, dirs: Gen2) -> Self::Other { - let get = |dir: Dir, at_positive_start| { - if dir.is_positive() == at_positive_start { - GenAlign::Start - } else { - GenAlign::End - } - }; - - let dirs = dirs.switch(dirs); - match self { - Self::Left => get(dirs.horizontal, true), - Self::Right => get(dirs.horizontal, false), - Self::Top => get(dirs.vertical, true), - Self::Bottom => get(dirs.vertical, false), - Self::Center => GenAlign::Center, - } - } -} - -impl Display for SpecAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Left => "left", - Self::Right => "right", - Self::Top => "top", - Self::Bottom => "bottom", - Self::Center => "center", - }) - } -} - -/// A generic container with left, top, right and bottom components. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Sides { - /// The value for the left side. - pub left: T, - /// The value for the top side. - pub top: T, - /// The value for the right side. - pub right: T, - /// The value for the bottom side. - pub bottom: T, -} - -impl Sides { - /// Create a new box from four sizes. - pub fn new(left: T, top: T, right: T, bottom: T) -> Self { - Self { left, top, right, bottom } - } - - /// Create an instance with all four components set to the same `value`. - pub fn uniform(value: T) -> Self - where - T: Clone, - { - Self { - left: value.clone(), - top: value.clone(), - right: value.clone(), - bottom: value, - } - } -} - -impl Sides { - /// The absolute insets. - pub fn insets(self, Size { width, height }: Size) -> Insets { - Insets { - x0: -self.left.eval(width), - y0: -self.top.eval(height), - x1: -self.right.eval(width), - y1: -self.bottom.eval(height), - } - } -} - -impl Get for Sides { - type Component = T; - - fn get(self, side: Side) -> T { - match side { - Side::Left => self.left, - Side::Top => self.top, - Side::Right => self.right, - Side::Bottom => self.bottom, - } - } - - fn get_mut(&mut self, side: Side) -> &mut T { - match side { - Side::Left => &mut self.left, - Side::Top => &mut self.top, - Side::Right => &mut self.right, - Side::Bottom => &mut self.bottom, - } - } -} - -/// A side of a container. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Side { - Left, - Top, - Right, - Bottom, -} - -impl Side { - /// The opposite side. - pub fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Top => Self::Bottom, - Self::Right => Self::Left, - Self::Bottom => Self::Top, - } - } -} diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs new file mode 100644 index 00000000..9eac1ad5 --- /dev/null +++ b/src/layout/spacing.rs @@ -0,0 +1,51 @@ +use std::fmt::{self, Debug, Formatter}; + +use super::*; + +/// A node that inserts spacing. +#[derive(Copy, Clone, PartialEq)] +pub struct Spacing { + pub amount: Length, + pub softness: Softness, +} + +#[async_trait(?Send)] +impl Layout for Spacing { + async fn layout( + &self, + _: &mut LayoutContext, + _: LayoutConstraints, + ) -> Vec { + vec![LayoutItem::Spacing(self.amount)] + } +} + +impl Debug for Spacing { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self.softness { + Softness::Soft => write!(f, "Soft({})", self.amount), + Softness::Hard => write!(f, "Hard({})", self.amount), + } + } +} + +impl From for LayoutNode { + fn from(spacing: Spacing) -> Self { + Self::Spacing(spacing) + } +} + +/// Defines how spacing interacts with surrounding spacing. +/// +/// Hard spacing assures that a fixed amount of spacing will always be inserted. +/// Soft spacing will be consumed by previous soft spacing or neighbouring hard +/// spacing and can be used to insert overridable spacing, e.g. between words or +/// paragraphs. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Softness { + /// Soft spacing is not laid out if it directly follows other soft spacing + /// or if it touches hard spacing. + Soft, + /// Hard spacing is always laid out and consumes surrounding soft spacing. + Hard, +} diff --git a/src/layout/stack.rs b/src/layout/stack.rs new file mode 100644 index 00000000..6cbe03e3 --- /dev/null +++ b/src/layout/stack.rs @@ -0,0 +1,206 @@ +use super::*; + +/// A node that stacks and aligns its children. +/// +/// # Alignment +/// Individual layouts can be aligned at `Start`, `Center` or `End` along both +/// axes. These alignments are with processed with respect to the size of the +/// finished layout and not the total usable size. This means that a later +/// layout can have influence on the position of an earlier one. Consider the +/// following example. +/// ```typst +/// [align: right][A word.] +/// [align: left][A sentence with a couple more words.] +/// ``` +/// The resulting layout looks like this: +/// ```text +/// |--------------------------------------| +/// | A word. | +/// | | +/// | A sentence with a couple more words. | +/// |--------------------------------------| +/// ``` +/// The position of the first aligned box thus depends on the length of the +/// sentence in the second box. +#[derive(Debug, Clone, PartialEq)] +pub struct Stack { + pub dirs: Gen, + pub children: Vec, + pub aligns: Gen, + pub expand: Spec, +} + +#[async_trait(?Send)] +impl Layout for Stack { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec { + let mut items = vec![]; + + let size = constraints.spaces[0].size; + let mut space = StackSpace::new(self.dirs, self.expand, size); + let mut i = 0; + + for child in &self.children { + let child_constraints = LayoutConstraints { + spaces: { + let mut remaining = vec![LayoutSpace { + base: space.full_size, + size: space.usable, + }]; + let next = (i + 1).min(constraints.spaces.len() - 1); + remaining.extend(&constraints.spaces[next ..]); + remaining + }, + repeat: constraints.repeat, + }; + + for item in child.layout(ctx, child_constraints).await { + match item { + LayoutItem::Spacing(spacing) => space.push_spacing(spacing), + LayoutItem::Box(mut boxed, aligns) => { + let mut last = false; + while let Err(back) = space.push_box(boxed, aligns) { + boxed = back; + if last { + break; + } + + items.push(LayoutItem::Box(space.finish(), self.aligns)); + + if i + 1 < constraints.spaces.len() { + i += 1; + } else { + last = true; + } + + let size = constraints.spaces[i].size; + space = StackSpace::new(self.dirs, self.expand, size); + } + } + } + } + } + + items.push(LayoutItem::Box(space.finish(), self.aligns)); + items + } +} + +struct StackSpace { + dirs: Gen, + expand: Spec, + boxes: Vec<(BoxLayout, Gen)>, + full_size: Size, + usable: Size, + used: Size, + ruler: Align, +} + +impl StackSpace { + fn new(dirs: Gen, expand: Spec, size: Size) -> Self { + Self { + dirs, + expand, + boxes: vec![], + full_size: size, + usable: size, + used: Size::ZERO, + ruler: Align::Start, + } + } + + fn push_box( + &mut self, + boxed: BoxLayout, + aligns: Gen, + ) -> Result<(), BoxLayout> { + let main = self.dirs.main.axis(); + let cross = self.dirs.cross.axis(); + if aligns.main < self.ruler || !self.usable.fits(boxed.size) { + return Err(boxed); + } + + let size = boxed.size.switch(self.dirs); + *self.used.get_mut(cross) = self.used.get(cross).max(size.cross); + *self.used.get_mut(main) += size.main; + *self.usable.get_mut(main) -= size.main; + self.boxes.push((boxed, aligns)); + self.ruler = aligns.main; + + Ok(()) + } + + fn push_spacing(&mut self, spacing: Length) { + let main = self.dirs.main.axis(); + let max = self.usable.get(main); + let trimmed = spacing.min(max); + *self.used.get_mut(main) += trimmed; + *self.usable.get_mut(main) -= trimmed; + + let size = Gen::new(trimmed, Length::ZERO).switch(self.dirs); + self.boxes.push((BoxLayout::new(size.to_size()), Gen::default())); + } + + fn finish(mut self) -> BoxLayout { + let dirs = self.dirs; + let main = dirs.main.axis(); + + if self.expand.horizontal { + self.used.width = self.full_size.width; + } + + if self.expand.vertical { + self.used.height = self.full_size.height; + } + + let mut sum = Length::ZERO; + let mut sums = Vec::with_capacity(self.boxes.len() + 1); + + for (boxed, _) in &self.boxes { + sums.push(sum); + sum += boxed.size.get(main); + } + + sums.push(sum); + + let mut layout = BoxLayout::new(self.used); + let used = self.used.switch(dirs); + + for (i, (boxed, aligns)) in self.boxes.into_iter().enumerate() { + let size = boxed.size.switch(dirs); + + let before = sums[i]; + let after = sum - sums[i + 1]; + let main_len = used.main - size.main; + let main_range = if dirs.main.is_positive() { + before .. main_len - after + } else { + main_len - before .. after + }; + + let cross_len = used.cross - size.cross; + let cross_range = if dirs.cross.is_positive() { + Length::ZERO .. cross_len + } else { + cross_len .. Length::ZERO + }; + + let main = aligns.main.apply(main_range); + let cross = aligns.cross.apply(cross_range); + let pos = Gen::new(main, cross).switch(dirs).to_point(); + + layout.push_layout(pos, boxed); + } + + layout + } +} + +impl From for LayoutNode { + fn from(stack: Stack) -> Self { + Self::dynamic(stack) + } +} diff --git a/src/layout/text.rs b/src/layout/text.rs new file mode 100644 index 00000000..fafc1b14 --- /dev/null +++ b/src/layout/text.rs @@ -0,0 +1,51 @@ +use std::fmt::{self, Debug, Formatter}; +use std::rc::Rc; + +use fontdock::{FallbackTree, FontVariant}; + +use super::*; +use crate::shaping; + +/// A text node. +#[derive(Clone, PartialEq)] +pub struct Text { + pub text: String, + pub size: Length, + pub dir: Dir, + pub fallback: Rc, + pub variant: FontVariant, + pub aligns: Gen, +} + +#[async_trait(?Send)] +impl Layout for Text { + async fn layout( + &self, + ctx: &mut LayoutContext, + _constraints: LayoutConstraints, + ) -> Vec { + let mut loader = ctx.loader.borrow_mut(); + let boxed = shaping::shape( + &self.text, + self.size, + self.dir, + &mut loader, + &self.fallback, + self.variant, + ) + .await; + vec![LayoutItem::Box(boxed, self.aligns)] + } +} + +impl Debug for Text { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Text({})", self.text) + } +} + +impl From for LayoutNode { + fn from(text: Text) -> Self { + Self::Text(text) + } +} -- cgit v1.2.3