diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-07 17:07:44 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-07 17:07:44 +0200 |
| commit | 537545e7f8351d7677c396456e46568f5a5e2a7a (patch) | |
| tree | f4c7614293246db06c7fa7496458da01b15c3b84 /src/layout | |
| parent | ca1256c924f3672feb76dbc2bc2e309eb4fc4cf5 (diff) | |
Evaluation and node-based layouting 🚀
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/mod.rs | 234 | ||||
| -rw-r--r-- | src/layout/nodes/document.rs | 52 | ||||
| -rw-r--r-- | src/layout/nodes/fixed.rs | 42 | ||||
| -rw-r--r-- | src/layout/nodes/mod.rs | 167 | ||||
| -rw-r--r-- | src/layout/nodes/pad.rs | 53 | ||||
| -rw-r--r-- | src/layout/nodes/par.rs (renamed from src/layout/line.rs) | 157 | ||||
| -rw-r--r-- | src/layout/nodes/spacing.rs | 51 | ||||
| -rw-r--r-- | src/layout/nodes/stack.rs (renamed from src/layout/stack.rs) | 257 | ||||
| -rw-r--r-- | src/layout/nodes/text.rs | 51 | ||||
| -rw-r--r-- | src/layout/primitive.rs | 64 | ||||
| -rw-r--r-- | src/layout/tree.rs | 234 |
11 files changed, 735 insertions, 627 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 912ca010..f709da1a 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,88 +1,24 @@ -//! Layouting of syntax trees. +//! Layouting of documents. +pub mod nodes; pub mod primitive; -mod line; -mod stack; -mod tree; - -pub use line::*; pub use primitive::*; -pub use stack::*; -pub use tree::*; -use crate::diag::Diag; +use async_trait::async_trait; + use crate::eval::{PageState, State, TextState}; use crate::font::SharedFontLoader; use crate::geom::{Insets, Point, Rect, Size, SizeExt}; use crate::shaping::Shaped; -use crate::syntax::{Deco, Spanned, SynTree}; -use crate::{Feedback, Pass}; - -/// Layout a syntax tree and return the produced layout. -pub async fn layout( - tree: &SynTree, - state: State, - loader: SharedFontLoader, -) -> Pass<Vec<BoxLayout>> { - let space = LayoutSpace { - size: state.page.size, - insets: state.page.insets(), - expansion: Spec2::new(true, true), - }; - - let constraints = LayoutConstraints { - root: true, - base: space.usable(), - spaces: vec![space], - repeat: true, - }; - - let mut ctx = LayoutContext { - loader, - state, - constraints, - f: Feedback::new(), - }; - - let layouts = layout_tree(&tree, &mut ctx).await; - Pass::new(layouts, ctx.f) -} - -/// A finished box with content at fixed positions. -#[derive(Debug, Clone, PartialEq)] -pub struct BoxLayout { - /// The size of the box. - pub size: Size, - /// The elements composing this layout. - pub elements: Vec<(Point, LayoutElement)>, -} +use crate::syntax::SynTree; -impl BoxLayout { - /// Create a new empty collection. - pub fn new(size: Size) -> Self { - Self { size, elements: vec![] } - } +use nodes::Document; - /// Add an element at a position. - pub fn push(&mut self, pos: Point, element: LayoutElement) { - self.elements.push((pos, element)); - } - - /// Add all elements of another collection, placing them relative to the - /// 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); - } - } -} - -/// A layout element, the basic building block layouts are composed of. -#[derive(Debug, Clone, PartialEq)] -pub enum LayoutElement { - /// Shaped text. - Text(Shaped), +/// Layout a document and return the produced layouts. +pub async fn layout(document: &Document, loader: SharedFontLoader) -> Vec<BoxLayout> { + let mut ctx = LayoutContext { loader }; + document.layout(&mut ctx).await } /// The context for layouting. @@ -90,33 +26,43 @@ pub enum LayoutElement { pub struct LayoutContext { /// The font loader to query fonts from when typesetting text. pub loader: SharedFontLoader, - /// The active state. - pub state: State, - /// The active constraints. - pub constraints: LayoutConstraints, - /// The accumulated feedback. - pub f: Feedback, } -impl LayoutContext { - /// Add a diagnostic to the feedback. - pub fn diag(&mut self, diag: Spanned<Diag>) { - self.f.diags.push(diag); - } +/// Layout a node. +#[async_trait(?Send)] +pub trait Layout { + /// Layout the node in the given layout context. + /// + /// This signature looks pretty horrible due to async in trait methods, but + /// it's actually just the following: + /// ```rust,ignore + /// async fn layout( + /// &self, + /// ctx: &mut LayoutContext, + /// constraints: LayoutConstraints, + /// ) -> Vec<LayoutItem>; + /// ``` + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec<LayoutItem>; +} - /// Add a decoration to the feedback. - pub fn deco(&mut self, deco: Spanned<Deco>) { - self.f.decos.push(deco); - } +/// An item that is produced by [layouting] a node. +/// +/// [layouting]: trait.Layout.html#method.layout +#[derive(Debug, Clone, PartialEq)] +pub enum LayoutItem { + /// Spacing that should be added to the parent. + Spacing(f64), + /// A box that should be aligned in the parent. + Box(BoxLayout, Gen2<GenAlign>), } /// The constraints for layouting a single node. #[derive(Debug, Clone)] pub struct LayoutConstraints { - /// Whether this layouting process is the root page-building process. - pub root: bool, - /// The unpadded size of this container (the base 100% for relative sizes). - pub base: Size, /// The spaces to layout into. pub spaces: Vec<LayoutSpace>, /// Whether to spill over into copies of the last space or finish layouting @@ -127,92 +73,44 @@ pub struct LayoutConstraints { /// The space into which content is laid out. #[derive(Debug, Copy, Clone, PartialEq)] pub struct LayoutSpace { + /// The full size of this container (the base for relative sizes). + pub base: Size, /// The maximum size of the rectangle to layout into. pub size: Size, - /// Padding that should be respected on each side. - pub insets: Insets, - /// 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 expansion: Spec2<bool>, } -impl LayoutSpace { - /// The position of the padded start in the space. - pub fn start(&self) -> Point { - Point::new(-self.insets.x0, -self.insets.y0) +/// A finished box with content at fixed positions. +#[derive(Debug, Clone, PartialEq)] +pub struct BoxLayout { + /// The size of the box. + pub size: Size, + /// The elements composing this layout. + pub elements: Vec<(Point, LayoutElement)>, +} + +impl BoxLayout { + /// Create a new empty collection. + pub fn new(size: Size) -> Self { + Self { size, elements: vec![] } } - /// The actually usable area (size minus padding). - pub fn usable(&self) -> Size { - self.size + self.insets.size() + /// Add an element at a position. + pub fn push(&mut self, pos: Point, element: LayoutElement) { + self.elements.push((pos, element)); } - /// The inner layout space with size reduced by the padding, zero padding of - /// its own and no layout expansion. - pub fn inner(&self) -> Self { - Self { - size: self.usable(), - insets: Insets::ZERO, - expansion: Spec2::new(false, false), + /// Add all elements of another collection, placing them relative to the + /// 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); } } } -/// Commands executable by the layouting engine. +/// A layout element, the basic building block layouts are composed of. #[derive(Debug, Clone, PartialEq)] -pub enum Command { - /// Layout the given tree in the current context (i.e. not nested). The - /// content of the tree is not laid out into a separate box and then added, - /// but simply laid out flatly in the active layouting process. - /// - /// This has the effect that the content fits nicely into the active line - /// layouting, enabling functions to e.g. change the style of some piece of - /// text while keeping it part of the current paragraph. - LayoutSyntaxTree(SynTree), - - /// Add a finished layout. - Add(BoxLayout, Gen2<GenAlign>), - /// Add spacing of the given kind along the given axis. The - /// kind defines how the spacing interacts with surrounding spacing. - AddSpacing(f64, SpacingKind, GenAxis), - - /// Start a new line. - BreakLine, - /// Start a new page, which will be part of the finished layout even if it - /// stays empty (since the page break is a _hard_ space break). - BreakPage, - - /// Update the text style. - SetTextState(TextState), - /// Update the page style. - SetPageState(PageState), - /// Update the alignment for future boxes added to this layouting process. - SetAlignment(Gen2<GenAlign>), -} - -/// Defines how spacing interacts with surrounding spacing. -/// -/// There are two options for interaction: Hard and soft spacing. Typically, -/// hard spacing is used when a fixed amount of space needs to be inserted no -/// matter what. In contrast, soft spacing can be used to insert a default -/// spacing between e.g. two words or paragraphs that can still be overridden by -/// a hard space. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum SpacingKind { - /// Hard spaces are always laid out and consume surrounding soft space. - Hard, - /// Soft spaces are not laid out if they are touching a hard space and - /// consume neighbouring soft spaces with higher levels. - Soft(u32), -} - -impl SpacingKind { - /// The standard spacing kind used for paragraph spacing. - pub const PARAGRAPH: Self = Self::Soft(1); - - /// The standard spacing kind used for line spacing. - pub const LINE: Self = Self::Soft(2); - - /// The standard spacing kind used for word spacing. - pub const WORD: Self = Self::Soft(1); +pub enum LayoutElement { + /// Shaped text. + Text(Shaped), } diff --git a/src/layout/nodes/document.rs b/src/layout/nodes/document.rs new file mode 100644 index 00000000..af7a31e6 --- /dev/null +++ b/src/layout/nodes/document.rs @@ -0,0 +1,52 @@ +use super::*; + +/// The top-level layouting node. +#[derive(Debug, Clone, PartialEq)] +pub struct Document { + pub runs: Vec<Pages>, +} + +impl Document { + /// Create a new document. + pub fn new() -> Self { + Self { runs: vec![] } + } + + /// Layout the document. + pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> { + 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<BoxLayout> { + 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 new file mode 100644 index 00000000..0d438879 --- /dev/null +++ b/src/layout/nodes/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<Linear>, + pub height: Option<Linear>, + pub child: LayoutNode, +} + +#[async_trait(?Send)] +impl Layout for Fixed { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec<LayoutItem> { + 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<Fixed> for LayoutNode { + fn from(fixed: Fixed) -> Self { + Self::dynamic(fixed) + } +} diff --git a/src/layout/nodes/mod.rs b/src/layout/nodes/mod.rs new file mode 100644 index 00000000..44c18284 --- /dev/null +++ b/src/layout/nodes/mod.rs @@ -0,0 +1,167 @@ +//! 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<T: DynNode>(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<LayoutItem> { + 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 +#[derive(Clone)] +pub struct Dynamic(pub Box<dyn DynNode>); + +impl Dynamic { + /// Wrap a type implementing `DynNode`. + pub fn new<T: DynNode>(inner: T) -> Self { + Self(Box::new(inner)) + } +} + +impl PartialEq for Dynamic { + fn eq(&self, other: &Self) -> bool { + &self.0 == &other.0 + } +} + +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 From<Dynamic> 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<dyn +/// DynNode>` 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<dyn DynNode>; +} + +impl<T> 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>() { + self == other + } else { + false + } + } + + fn dyn_clone(&self) -> Box<dyn DynNode> { + Box::new(self.clone()) + } +} + +impl Clone for Box<dyn DynNode> { + fn clone(&self) -> Self { + self.dyn_clone() + } +} + +impl PartialEq for Box<dyn DynNode> { + fn eq(&self, other: &Self) -> bool { + self.dyn_eq(other.as_ref()) + } +} diff --git a/src/layout/nodes/pad.rs b/src/layout/nodes/pad.rs new file mode 100644 index 00000000..10a9e2c6 --- /dev/null +++ b/src/layout/nodes/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<Linear>, + pub child: LayoutNode, +} + +#[async_trait(?Send)] +impl Layout for Pad { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec<LayoutItem> { + 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<Pad> for LayoutNode { + fn from(pad: Pad) -> Self { + Self::dynamic(pad) + } +} diff --git a/src/layout/line.rs b/src/layout/nodes/par.rs index ae3bd969..38b11529 100644 --- a/src/layout/line.rs +++ b/src/layout/nodes/par.rs @@ -1,16 +1,66 @@ -//! Arranging boxes into lines. -//! -//! The 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. -//! -//! Internally, the line layouter uses a stack layouter to stack the finished -//! lines on top of each. - 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<Dir>, + pub line_spacing: f64, + pub children: Vec<LayoutNode>, + pub aligns: Gen2<GenAlign>, + pub expand: Spec2<bool>, +} + +#[async_trait(?Send)] +impl Layout for Par { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec<LayoutItem> { + 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<Par> for LayoutNode { + fn from(par: Par) -> Self { + Self::dynamic(par) + } +} + /// Performs the line layouting. -pub struct LineLayouter { +struct LineLayouter { /// The context used for line layouting. ctx: LineContext, /// The underlying layouter that stacks the finished lines. @@ -21,26 +71,30 @@ pub struct LineLayouter { /// The context for line layouting. #[derive(Debug, Clone)] -pub struct LineContext { +struct LineContext { /// The layout directions. - pub dirs: Gen2<Dir>, + dirs: Gen2<Dir>, /// The spaces to layout into. - pub spaces: Vec<LayoutSpace>, + spaces: Vec<LayoutSpace>, /// Whether to spill over into copies of the last space or finish layouting /// when the last space is used up. - pub repeat: bool, + repeat: bool, /// The spacing to be inserted between each pair of lines. - pub line_spacing: f64, + 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<bool>, } impl LineLayouter { /// Create a new line layouter. - pub fn new(ctx: LineContext) -> Self { + 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(), @@ -48,7 +102,7 @@ impl LineLayouter { } /// Add a layout. - pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) { + fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) { let dirs = self.ctx.dirs; if let Some(prev) = self.run.aligns { if aligns.main != prev.main { @@ -67,6 +121,8 @@ impl LineLayouter { 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, @@ -76,15 +132,11 @@ impl LineLayouter { self.finish_line(); // Move back up in the stack layouter. - self.stack.add_spacing(-rest_run.size.main, SpacingKind::Hard); + self.stack.push_spacing(-rest_run.size.main - self.ctx.line_spacing); self.run = rest_run; } } - if let LastSpacing::Soft(spacing, _) = self.run.last_spacing { - self.add_cross_spacing(spacing, SpacingKind::Hard); - } - let size = layout.size.switch(dirs); let usable = self.usable(); @@ -105,7 +157,12 @@ impl LineLayouter { self.run.size.cross += size.cross; self.run.size.main = self.run.size.main.max(size.main); - self.run.last_spacing = LastSpacing::None; + } + + /// 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. @@ -125,66 +182,35 @@ impl LineLayouter { usable } - /// Finish the line and add spacing to the underlying stack. - pub fn add_main_spacing(&mut self, spacing: f64, kind: SpacingKind) { - self.finish_line_if_not_empty(); - self.stack.add_spacing(spacing, kind) - } - - /// Add spacing to the line. - pub fn add_cross_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { - match kind { - SpacingKind::Hard => { - spacing = spacing.min(self.usable().cross); - self.run.size.cross += spacing; - self.run.last_spacing = LastSpacing::Hard; - } - - // A soft space is cached since it might be consumed by a hard - // spacing. - SpacingKind::Soft(level) => { - let consumes = match self.run.last_spacing { - LastSpacing::None => true, - LastSpacing::Soft(_, prev) if level < prev => true, - _ => false, - }; - - if consumes { - self.run.last_spacing = LastSpacing::Soft(spacing, level); - } - } - } - } - /// Update the layouting spaces. /// /// If `replace_empty` is true, the current space is replaced if there are /// no boxes laid out into it yet. Otherwise, the followup spaces are /// replaced. - pub fn set_spaces(&mut self, spaces: Vec<LayoutSpace>, replace_empty: bool) { + fn set_spaces(&mut self, spaces: Vec<LayoutSpace>, replace_empty: bool) { self.stack.set_spaces(spaces, replace_empty && self.line_is_empty()); } /// Update the line spacing. - pub fn set_line_spacing(&mut self, line_spacing: f64) { + fn set_line_spacing(&mut self, line_spacing: f64) { self.ctx.line_spacing = line_spacing; } /// The remaining inner spaces. If something is laid out into these spaces, /// it will fit into this layouter's underlying stack. - pub fn remaining(&self) -> Vec<LayoutSpace> { + fn remaining(&self) -> Vec<LayoutSpace> { 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. - pub fn line_is_empty(&self) -> bool { + 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. - pub fn finish(mut self) -> Vec<BoxLayout> { + fn finish(mut self) -> Vec<BoxLayout> { self.finish_line_if_not_empty(); self.stack.finish() } @@ -192,13 +218,13 @@ impl LineLayouter { /// Finish the active space and start a new one. /// /// At the top level, this is a page break. - pub fn finish_space(&mut self, hard: bool) { + 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. - pub fn finish_line(&mut self) { + fn finish_line(&mut self) { let dirs = self.ctx.dirs; let mut layout = BoxLayout::new(self.run.size.switch(dirs).to_size()); @@ -216,10 +242,9 @@ impl LineLayouter { layout.push_layout(pos, child); } - self.stack.add(layout, aligns); - + self.stack.push_box(layout, aligns); + self.stack.push_spacing(self.ctx.line_spacing); self.run = LineRun::new(); - self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE); } fn finish_line_if_not_empty(&mut self) { @@ -245,9 +270,6 @@ struct LineRun { /// 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<f64>, - /// The spacing state. This influences how new spacing is handled, e.g. hard - /// spacing may override soft spacing. - last_spacing: LastSpacing, } impl LineRun { @@ -257,7 +279,6 @@ impl LineRun { size: Gen2::ZERO, aligns: None, usable: None, - last_spacing: LastSpacing::Hard, } } } diff --git a/src/layout/nodes/spacing.rs b/src/layout/nodes/spacing.rs new file mode 100644 index 00000000..66af0d17 --- /dev/null +++ b/src/layout/nodes/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: f64, + pub softness: Softness, +} + +#[async_trait(?Send)] +impl Layout for Spacing { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec<LayoutItem> { + 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<Spacing> 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/nodes/stack.rs index cca2a315..983175b8 100644 --- a/src/layout/stack.rs +++ b/src/layout/nodes/stack.rs @@ -1,39 +1,93 @@ -//! Arranging boxes into a stack along the main axis. -//! -//! Individual layouts can be aligned at `Start`, `Center` or `End` along both -//! axes. These alignments are 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. - use super::*; +use crate::geom::Linear; + +/// 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<Dir>, + pub children: Vec<LayoutNode>, + pub aligns: Gen2<GenAlign>, + pub expand: Spec2<bool>, +} + +#[async_trait(?Send)] +impl Layout for Stack { + async fn layout( + &self, + ctx: &mut LayoutContext, + constraints: LayoutConstraints, + ) -> Vec<LayoutItem> { + let mut layouter = StackLayouter::new(StackContext { + dirs: self.dirs, + spaces: constraints.spaces, + repeat: constraints.repeat, + 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<Stack> for LayoutNode { + fn from(stack: Stack) -> Self { + Self::dynamic(stack) + } +} /// Performs the stack layouting. -pub struct StackLayouter { +pub(super) struct StackLayouter { /// The context used for stack layouting. - ctx: StackContext, + pub ctx: StackContext, /// The finished layouts. - layouts: Vec<BoxLayout>, + pub layouts: Vec<BoxLayout>, /// The in-progress space. - pub(super) space: Space, + pub space: Space, } /// The context for stack layouting. #[derive(Debug, Clone)] -pub struct StackContext { +pub(super) struct StackContext { /// The layouting directions. pub dirs: Gen2<Dir>, /// The spaces to layout into. @@ -41,6 +95,9 @@ pub struct StackContext { /// 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<bool>, } impl StackLayouter { @@ -50,12 +107,12 @@ impl StackLayouter { Self { ctx, layouts: vec![], - space: Space::new(0, true, space.usable()), + space: Space::new(0, true, space.size), } } /// Add a layout to the stack. - pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) { + pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) { // If the alignment cannot be fitted in this space, finish it. // // TODO: Issue warning for non-fitting alignment in non-repeating @@ -64,14 +121,9 @@ impl StackLayouter { self.finish_space(true); } - // Add a possibly cached soft spacing. - if let LastSpacing::Soft(spacing, _) = self.space.last_spacing { - self.add_spacing(spacing, SpacingKind::Hard); - } - // TODO: Issue warning about overflow if there is overflow in a // non-repeating context. - if !self.usable().fits(layout.size) && self.ctx.repeat { + if !self.space.usable.fits(layout.size) && self.ctx.repeat { self.skip_to_fitting_space(layout.size); } @@ -82,49 +134,27 @@ impl StackLayouter { // again. self.space.layouts.push((layout, aligns)); self.space.allowed_align = aligns.main; - self.space.last_spacing = LastSpacing::None; } /// Add spacing to the stack. - pub fn add_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { - match kind { - // A hard space is simply an empty box. - SpacingKind::Hard => { - self.space.last_spacing = LastSpacing::Hard; - - // Reduce the spacing such that it definitely fits. - let axis = self.ctx.dirs.main.axis(); - spacing = spacing.min(self.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(), - )); - } - - // A soft space is cached if it is not consumed by a hard space or - // previous soft space with higher level. - SpacingKind::Soft(level) => { - let consumes = match self.space.last_spacing { - LastSpacing::None => true, - LastSpacing::Soft(_, prev) if level < prev => true, - _ => false, - }; - - if consumes { - self.space.last_spacing = LastSpacing::Soft(spacing, level); - } - } - } + 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<f64>) { - let mut size = self.space.size.switch(self.ctx.dirs); - size.cross = size.cross.max(added.cross); - size.main += added.main; - self.space.size = size.switch(self.ctx.dirs).to_size(); + 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; } @@ -148,7 +178,7 @@ impl StackLayouter { 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.usable().fits(size) { + if space.size.fits(size) { self.finish_space(true); self.start_space(start + index, true); break; @@ -160,29 +190,22 @@ impl StackLayouter { /// it will fit into this stack. pub fn remaining(&self) -> Vec<LayoutSpace> { let mut spaces = vec![LayoutSpace { - size: self.usable(), - insets: Insets::ZERO, - expansion: Spec2::new(false, false), + base: self.space.size, + size: self.space.usable, }]; - for space in &self.ctx.spaces[self.next_space() ..] { - spaces.push(space.inner()); - } - + spaces.extend(&self.ctx.spaces[self.next_space() ..]); spaces } /// The remaining usable size. pub fn usable(&self) -> Size { self.space.usable - - Gen2::new(self.space.last_spacing.soft_or_zero(), 0.0) - .switch(self.ctx.dirs) - .to_size() } /// Whether the current layout space is empty. pub fn space_is_empty(&self) -> bool { - self.space.size == Size::ZERO && self.space.layouts.is_empty() + self.space.used == Size::ZERO && self.space.layouts.is_empty() } /// Whether the current layout space is the last in the followup list. @@ -208,23 +231,18 @@ impl StackLayouter { // expand if necessary.) let space = self.ctx.spaces[self.space.index]; - let start = space.start(); - let padded_size = { - let mut used_size = self.space.size; - - let usable = space.usable(); - if space.expansion.horizontal { - used_size.width = usable.width; + let layout_size = { + let mut used_size = self.space.used; + if self.ctx.expand.horizontal { + used_size.width = space.size.width; } - if space.expansion.vertical { - used_size.height = usable.height; + if self.ctx.expand.vertical { + used_size.height = space.size.height; } - used_size }; - let unpadded_size = padded_size - space.insets.size(); - let mut layout = BoxLayout::new(unpadded_size); + let mut layout = BoxLayout::new(layout_size); // ------------------------------------------------------------------ // // Step 2: Forward pass. Create a bounding box for each layout in which @@ -233,10 +251,10 @@ impl StackLayouter { let mut bounds = vec![]; let mut bound = Rect { - x0: start.x, - y0: start.y, - x1: start.x + self.space.size.width, - y1: start.y + self.space.size.height, + x0: 0.0, + y0: 0.0, + x1: layout_size.width, + y1: layout_size.height, }; for (layout, _) in &self.space.layouts { @@ -294,7 +312,7 @@ impl StackLayouter { fn start_space(&mut self, index: usize, hard: bool) { let space = self.ctx.spaces[index]; - self.space = Space::new(index, hard, space.usable()); + self.space = Space::new(index, hard, space.size); } fn next_space(&self) -> usize { @@ -304,6 +322,7 @@ impl StackLayouter { /// 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, @@ -311,50 +330,26 @@ pub(super) struct Space { hard: bool, /// The so-far accumulated layouts. layouts: Vec<(BoxLayout, Gen2<GenAlign>)>, - /// The size of this space. + /// 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, - /// The spacing state. This influences how new spacing is handled, e.g. hard - /// spacing may override soft spacing. - last_spacing: LastSpacing, } impl Space { - fn new(index: usize, hard: bool, usable: Size) -> Self { + fn new(index: usize, hard: bool, size: Size) -> Self { Self { index, hard, layouts: vec![], - size: Size::ZERO, - usable, + size, + used: Size::ZERO, + usable: size, allowed_align: GenAlign::Start, - last_spacing: LastSpacing::Hard, - } - } -} - -/// The spacing kind of the most recently inserted item in a layouting process. -/// -/// Since the last inserted item may not be spacing at all, this can be `None`. -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum LastSpacing { - /// The last item was hard spacing. - Hard, - /// The last item was soft spacing with the given width and level. - Soft(f64, u32), - /// The last item wasn't spacing. - None, -} - -impl LastSpacing { - /// The width of the soft space if this is a soft space or zero otherwise. - fn soft_or_zero(self) -> f64 { - match self { - LastSpacing::Soft(space, _) => space, - _ => 0.0, } } } diff --git a/src/layout/nodes/text.rs b/src/layout/nodes/text.rs new file mode 100644 index 00000000..b0c4a458 --- /dev/null +++ b/src/layout/nodes/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: f64, + pub dir: Dir, + pub fallback: Rc<FallbackTree>, + pub variant: FontVariant, + pub aligns: Gen2<GenAlign>, +} + +#[async_trait(?Send)] +impl Layout for Text { + async fn layout( + &self, + ctx: &mut LayoutContext, + _constraints: LayoutConstraints, + ) -> Vec<LayoutItem> { + 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<Text> for LayoutNode { + fn from(text: Text) -> Self { + Self::Text(text) + } +} diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs index f932b85b..b641b5c7 100644 --- a/src/layout/primitive.rs +++ b/src/layout/primitive.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter}; -use crate::geom::{Point, Size, Vec2}; +use crate::geom::{Insets, Linear, Point, Size, Vec2}; /// Generic access to a structure's components. pub trait Get<Index> { @@ -126,6 +126,11 @@ impl<T> Gen2<T> { } } +impl Gen2<f64> { + /// The instance that has both components set to zero. + pub const ZERO: Self = Self { main: 0.0, cross: 0.0 }; +} + impl<T> Get<GenAxis> for Gen2<T> { type Component = T; @@ -155,11 +160,6 @@ impl<T> Switch for Gen2<T> { } } -impl Gen2<f64> { - /// The instance that has both components set to zero. - pub const ZERO: Self = Self { main: 0.0, cross: 0.0 }; -} - /// A generic container with two components for the two specific axes. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] pub struct Spec2<T> { @@ -176,6 +176,26 @@ impl<T> Spec2<T> { } } +impl Spec2<f64> { + /// 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<T> Get<SpecAxis> for Spec2<T> { type Component = T; @@ -205,26 +225,6 @@ impl<T> Switch for Spec2<T> { } } -impl Spec2<f64> { - /// 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) - } -} - /// The two generic layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum GenAxis { @@ -444,6 +444,18 @@ impl<T> Sides<T> { } } +impl Sides<Linear> { + /// 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<T> Get<Side> for Sides<T> { type Component = T; diff --git a/src/layout/tree.rs b/src/layout/tree.rs deleted file mode 100644 index 4e53cedc..00000000 --- a/src/layout/tree.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! Layouting of syntax trees. - -use fontdock::FontStyle; - -use super::*; -use crate::eval::Eval; -use crate::shaping; -use crate::syntax::*; -use crate::DynFuture; - -/// Layout a syntax tree in a given context. -pub async fn layout_tree(tree: &SynTree, ctx: &mut LayoutContext) -> Vec<BoxLayout> { - let mut layouter = TreeLayouter::new(ctx); - layouter.layout_tree(tree).await; - layouter.finish() -} - -/// Layouts trees. -struct TreeLayouter<'a> { - ctx: &'a mut LayoutContext, - constraints: LayoutConstraints, - layouter: LineLayouter, -} - -impl<'a> TreeLayouter<'a> { - fn new(ctx: &'a mut LayoutContext) -> Self { - let layouter = LineLayouter::new(LineContext { - spaces: ctx.constraints.spaces.clone(), - dirs: ctx.state.dirs, - repeat: ctx.constraints.repeat, - line_spacing: ctx.state.text.line_spacing(), - }); - - Self { - layouter, - constraints: ctx.constraints.clone(), - ctx, - } - } - - fn finish(self) -> Vec<BoxLayout> { - self.layouter.finish() - } - - fn layout_tree<'t>(&'t mut self, tree: &'t SynTree) -> DynFuture<'t, ()> { - Box::pin(async move { - for node in tree { - self.layout_node(node).await; - } - }) - } - - async fn layout_node(&mut self, node: &Spanned<SynNode>) { - let decorate = |this: &mut Self, deco: Deco| { - this.ctx.f.decos.push(deco.span_with(node.span)); - }; - - match &node.v { - SynNode::Space => self.layout_space(), - SynNode::Text(text) => { - if self.ctx.state.text.emph { - decorate(self, Deco::Emph); - } - if self.ctx.state.text.strong { - decorate(self, Deco::Strong); - } - self.layout_text(text).await; - } - - SynNode::Linebreak => self.layouter.finish_line(), - SynNode::Parbreak => self.layout_parbreak(), - SynNode::Emph => { - self.ctx.state.text.emph ^= true; - decorate(self, Deco::Emph); - } - SynNode::Strong => { - self.ctx.state.text.strong ^= true; - decorate(self, Deco::Strong); - } - - SynNode::Heading(heading) => self.layout_heading(heading).await, - SynNode::Raw(raw) => self.layout_raw(raw).await, - - SynNode::Expr(expr) => { - self.layout_expr(expr.span_with(node.span)).await; - } - } - } - - fn layout_space(&mut self) { - self.layouter - .add_cross_spacing(self.ctx.state.text.word_spacing(), SpacingKind::WORD); - } - - fn layout_parbreak(&mut self) { - self.layouter - .add_main_spacing(self.ctx.state.text.par_spacing(), SpacingKind::PARAGRAPH); - } - - async fn layout_text(&mut self, text: &str) { - let mut variant = self.ctx.state.text.variant; - - if self.ctx.state.text.strong { - variant.weight = variant.weight.thicken(300); - } - - if self.ctx.state.text.emph { - variant.style = match variant.style { - FontStyle::Normal => FontStyle::Italic, - FontStyle::Italic => FontStyle::Normal, - FontStyle::Oblique => FontStyle::Normal, - } - } - - let boxed = shaping::shape( - text, - self.ctx.state.text.font_size(), - self.ctx.state.dirs.cross, - &mut self.ctx.loader.borrow_mut(), - &self.ctx.state.text.fallback, - variant, - ) - .await; - - self.layouter.add(boxed, self.ctx.state.aligns); - } - - async fn layout_heading(&mut self, heading: &NodeHeading) { - let style = self.ctx.state.text.clone(); - - let factor = 1.5 - 0.1 * heading.level.v as f64; - self.ctx.state.text.font_size.scale *= factor; - self.ctx.state.text.strong = true; - - self.layout_parbreak(); - self.layout_tree(&heading.contents).await; - self.layout_parbreak(); - - self.ctx.state.text = style; - } - - async fn layout_raw(&mut self, raw: &NodeRaw) { - if !raw.inline { - self.layout_parbreak(); - } - - // TODO: Make this more efficient. - let fallback = self.ctx.state.text.fallback.clone(); - self.ctx.state.text.fallback.list.insert(0, "monospace".to_string()); - self.ctx.state.text.fallback.flatten(); - - let mut first = true; - for line in &raw.lines { - if !first { - self.layouter.finish_line(); - } - first = false; - self.layout_text(line).await; - } - - self.ctx.state.text.fallback = fallback; - - if !raw.inline { - self.layout_parbreak(); - } - } - - async fn layout_expr(&mut self, expr: Spanned<&Expr>) { - self.ctx.constraints = LayoutConstraints { - root: false, - base: self.constraints.base, - spaces: self.layouter.remaining(), - repeat: self.constraints.repeat, - }; - - let val = expr.v.eval(self.ctx).await; - let commands = val.span_with(expr.span).into_commands(); - for command in commands { - self.execute_command(command, expr.span).await; - } - } - - async fn execute_command(&mut self, command: Command, span: Span) { - use Command::*; - match command { - LayoutSyntaxTree(tree) => self.layout_tree(&tree).await, - - Add(layout, aligns) => self.layouter.add(layout, aligns), - AddSpacing(space, kind, axis) => match axis { - GenAxis::Main => self.layouter.add_main_spacing(space, kind), - GenAxis::Cross => self.layouter.add_cross_spacing(space, kind), - }, - - BreakLine => self.layouter.finish_line(), - BreakPage => { - if self.constraints.root { - self.layouter.finish_space(true) - } else { - self.ctx.diag(error!( - span, - "page break can only be issued from root context", - )); - } - } - - SetTextState(style) => { - self.layouter.set_line_spacing(style.line_spacing()); - self.ctx.state.text = style; - } - SetPageState(style) => { - if self.constraints.root { - self.ctx.state.page = style; - - // The line layouter has no idea of page styles and thus we - // need to recompute the layouting space resulting of the - // new page style and update it within the layouter. - let space = LayoutSpace { - size: style.size, - insets: style.insets(), - expansion: Spec2::new(true, true), - }; - self.constraints.base = space.usable(); - self.layouter.set_spaces(vec![space], true); - } else { - self.ctx.diag(error!( - span, - "page style can only be changed from root context", - )); - } - } - SetAlignment(aligns) => self.ctx.state.aligns = aligns, - } - } -} |
