diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-12-30 22:28:56 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-12-30 22:28:56 +0100 |
| commit | 269f069a4d721a986807293ef71be1348bfae3d4 (patch) | |
| tree | 31b737c4ff2aead3eb5e2673828595bb26622032 /src/layout | |
| parent | b8620121a692df6313eeb5ccf7baf89c1e364116 (diff) | |
Simple line layouter 🧾
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/flex.rs | 198 | ||||
| -rw-r--r-- | src/layout/line.rs | 283 | ||||
| -rw-r--r-- | src/layout/mod.rs | 9 | ||||
| -rw-r--r-- | src/layout/stack.rs | 100 | ||||
| -rw-r--r-- | src/layout/text.rs | 44 | ||||
| -rw-r--r-- | src/layout/tree.rs | 57 |
6 files changed, 378 insertions, 313 deletions
diff --git a/src/layout/flex.rs b/src/layout/flex.rs deleted file mode 100644 index 72878a29..00000000 --- a/src/layout/flex.rs +++ /dev/null @@ -1,198 +0,0 @@ -use super::*; - -/// The flex layouter first arranges boxes along a primary and if necessary also -/// along a secondary axis. -#[derive(Debug, Clone)] -pub struct FlexLayouter { - axes: LayoutAxes, - flex_spacing: Size, - stack: StackLayouter, - - units: Vec<FlexUnit>, - line: FlexLine, - part: PartialLine, -} - -#[derive(Debug, Clone)] -enum FlexUnit { - Boxed(Layout), - Space(Size, SpacingKind), - SetAxes(LayoutAxes), - Break, -} - -#[derive(Debug, Clone)] -struct FlexLine { - usable: Size, - actions: LayoutActions, - combined_dimensions: Size2D, -} - -impl FlexLine { - fn new(usable: Size) -> FlexLine { - FlexLine { - usable, - actions: LayoutActions::new(), - combined_dimensions: Size2D::ZERO, - } - } -} - -#[derive(Debug, Clone)] -struct PartialLine { - usable: Size, - content: Vec<(Size, Layout)>, - dimensions: Size2D, - space: LastSpacing, -} - -impl PartialLine { - fn new(usable: Size) -> PartialLine { - PartialLine { - usable, - content: vec![], - dimensions: Size2D::ZERO, - space: LastSpacing::Hard, - } - } -} - -/// The context for flex layouting. -/// -/// See [`LayoutContext`] for details about the fields. -#[derive(Debug, Clone)] -pub struct FlexContext { - pub spaces: LayoutSpaces, - pub axes: LayoutAxes, - pub alignment: LayoutAlignment, - pub flex_spacing: Size, - pub repeat: bool, - pub debug: bool, -} - -impl FlexLayouter { - /// Create a new flex layouter. - pub fn new(ctx: FlexContext) -> FlexLayouter { - let stack = StackLayouter::new(StackContext { - spaces: ctx.spaces, - axes: ctx.axes, - alignment: ctx.alignment, - repeat: ctx.repeat, - debug: ctx.debug, - }); - - let usable = stack.primary_usable(); - - FlexLayouter { - axes: ctx.axes, - flex_spacing: ctx.flex_spacing, - stack, - - units: vec![], - line: FlexLine::new(usable), - part: PartialLine::new(usable), - } - } - - pub fn add(&mut self, layout: Layout) { - self.units.push(FlexUnit::Boxed(layout)); - } - - pub fn add_multiple(&mut self, layouts: MultiLayout) { - for layout in layouts { - self.add(layout); - } - } - - pub fn add_break(&mut self) { - self.units.push(FlexUnit::Break); - } - - pub fn add_primary_space(&mut self, space: Size, kind: SpacingKind) { - self.units.push(FlexUnit::Space(space, kind)) - } - - pub fn add_secondary_space(&mut self, space: Size, kind: SpacingKind) -> LayoutResult<()> { - if !self.run_is_empty() { - self.finish_run()?; - } - Ok(self.stack.add_spacing(space, kind)) - } - - pub fn set_axes(&mut self, axes: LayoutAxes) { - self.units.push(FlexUnit::SetAxes(axes)); - } - - pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { - if replace_empty && self.run_is_empty() && self.stack.space_is_empty() { - self.stack.set_spaces(spaces, true); - self.start_line(); - } else { - self.stack.set_spaces(spaces, false); - } - } - - pub fn remaining(&self) -> LayoutSpaces { - self.stack.remaining() - } - - pub fn run_is_empty(&self) -> bool { - !self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_))) - } - - pub fn run_last_is_space(&self) -> bool { - matches!(self.units.last(), Some(FlexUnit::Space(_, _))) - } - - pub fn finish(mut self) -> LayoutResult<MultiLayout> { - self.finish_space(false)?; - self.stack.finish() - } - - pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { - if !self.run_is_empty() { - self.finish_run()?; - } - - self.stack.finish_space(hard)?; - Ok(self.start_line()) - } - - pub fn finish_run(&mut self) -> LayoutResult<Size2D> { - let units = std::mem::replace(&mut self.units, vec![]); - for unit in units { - match unit { - FlexUnit::Boxed(boxed) => self.layout_box(boxed)?, - FlexUnit::Space(space, kind) => self.layout_space(space, kind), - FlexUnit::SetAxes(axes) => self.layout_set_axes(axes), - FlexUnit::Break => { self.finish_line()?; }, - } - } - - self.finish_line() - } - - fn finish_line(&mut self) -> LayoutResult<Size2D> { - unimplemented!() - } - - fn start_line(&mut self) { - unimplemented!() - } - - fn finish_partial_line(&mut self) { - unimplemented!() - } - - fn layout_box(&mut self, _boxed: Layout) -> LayoutResult<()> { - unimplemented!() - } - - fn layout_space(&mut self, _space: Size, _kind: SpacingKind) { - unimplemented!() - } - - fn layout_set_axes(&mut self, _axes: LayoutAxes) { - unimplemented!() - } -} diff --git a/src/layout/line.rs b/src/layout/line.rs new file mode 100644 index 00000000..5aae7dcd --- /dev/null +++ b/src/layout/line.rs @@ -0,0 +1,283 @@ +use super::*; + +/// The line layouter arranges boxes next to each other along a primary axis +/// and arranges the resulting lines using an underlying stack layouter. +#[derive(Debug, Clone)] +pub struct LineLayouter { + /// The context for layouting. + ctx: LineContext, + /// The underlying stack layouter. + stack: StackLayouter, + /// The currently written line. + run: LineRun, +} + +/// The context for line layouting. +#[derive(Debug, Clone)] +pub struct LineContext { + /// The spaces to layout in. + pub spaces: LayoutSpaces, + /// The initial layouting axes, which can be updated by the + /// [`LineLayouter::set_axes`] method. + pub axes: LayoutAxes, + /// Which alignment to set on the resulting layout. This affects how it will + /// be positioned in a parent box. + pub alignment: LayoutAlignment, + /// Whether to have repeated spaces or to use only the first and only once. + pub repeat: bool, + /// Whether to output a command which renders a debugging box showing the + /// extent of the layout. + pub debug: bool, + /// The line spacing. + pub line_spacing: Size, +} + +/// A simple line of boxes. +#[derive(Debug, Clone)] +struct LineRun { + /// The so-far accumulated layouts in the line. + layouts: Vec<(Size, Layout)>, + /// The width (primary size) and maximal height (secondary size) of the + /// line. + size: Size2D, + /// The alignment of all layouts in the line. + alignment: Option<LayoutAlignment>, + /// The remaining usable space if another differently aligned line run + /// already took up some space. + usable: Option<Size>, + /// A possibly cached soft spacing or spacing state. + last_spacing: LastSpacing, +} + +impl LineLayouter { + /// Create a new line layouter. + pub fn new(ctx: LineContext) -> LineLayouter { + LineLayouter { + stack: StackLayouter::new(StackContext { + spaces: ctx.spaces.clone(), + axes: ctx.axes, + alignment: ctx.alignment, + repeat: ctx.repeat, + debug: ctx.debug, + }), + ctx, + run: LineRun::new(), + } + } + + /// Add a layout to the run. + pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { + let axes = self.ctx.axes; + + if let Some(alignment) = self.run.alignment { + if layout.alignment.secondary != alignment.secondary { + if self.stack.is_fitting_alignment(layout.alignment) { + self.finish_line()?; + } else { + self.finish_space(true)?; + } + } else if layout.alignment.primary < alignment.primary { + self.finish_line()?; + + } else if layout.alignment.primary > alignment.primary { + let mut rest_run = LineRun::new(); + + let usable = self.stack.usable().get_primary(axes); + rest_run.usable = Some(match layout.alignment.primary { + Alignment::Origin => unreachable!("origin > x"), + Alignment::Center => usable - 2 * self.run.size.x, + Alignment::End => usable - self.run.size.x, + }); + + rest_run.size.y = self.run.size.y; + + self.finish_line()?; + self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard); + + self.run = rest_run; + } + } + + if let LastSpacing::Soft(spacing, _) = self.run.last_spacing { + self.add_primary_spacing(spacing, SpacingKind::Hard); + } + + let size = layout.dimensions.generalized(axes); + + while !self.usable().fits(size) { + if !self.line_is_empty() { + self.finish_line()?; + } else { + self.finish_space(true)?; + } + } + + self.run.alignment = Some(layout.alignment); + self.run.layouts.push((self.run.size.x, layout)); + + self.run.size.x += size.x; + self.run.size.y.max_eq(size.y); + self.run.last_spacing = LastSpacing::None; + + Ok(()) + } + + /// Add multiple layouts to the run. + /// + /// This function simply calls `add` repeatedly for each layout. + pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> { + for layout in layouts { + self.add(layout)?; + } + Ok(()) + } + + /// The remaining usable size in the run. + fn usable(&self) -> Size2D { + // The base is the usable space per stack layouter. + let mut usable = self.stack.usable().generalized(self.ctx.axes); + + // If this is a alignment-continuing line, we override the primary + // usable size. + if let Some(primary) = self.run.usable { + usable.x = primary; + } + + usable.x -= self.run.size.x; + usable + } + + /// Add primary spacing to the line. + pub fn add_primary_spacing(&mut self, mut spacing: Size, kind: SpacingKind) { + match kind { + // A hard space is simply an empty box. + SpacingKind::Hard => { + spacing.min_eq(self.usable().x); + self.run.size.x += spacing; + self.run.last_spacing = LastSpacing::Hard; + } + + // 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.run.last_spacing { + LastSpacing::None => true, + LastSpacing::Soft(_, prev) if level < prev => true, + _ => false, + }; + + if consumes { + self.run.last_spacing = LastSpacing::Soft(spacing, level); + } + } + } + } + + /// Finish the run and add secondary spacing to the underlying stack. + pub fn add_secondary_spacing( + &mut self, + mut spacing: Size, + kind: SpacingKind + ) -> LayoutResult<()> { + self.finish_line_if_not_empty()?; + Ok(self.stack.add_spacing(spacing, kind)) + } + + /// Change the layouting axes used by this layouter. + pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> { + self.finish_line_if_not_empty()?; + self.ctx.axes = axes; + Ok(self.stack.set_axes(axes)) + } + + /// Change the layouting spaces to use. + /// + /// If `replace_empty` is true, the current space is replaced if there are + /// no boxes laid into it yet. Otherwise, only the followup spaces are + /// replaced. + pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { + self.stack.set_spaces(spaces, replace_empty && self.line_is_empty()); + } + + /// Change the line spacing. + pub fn set_line_spacing(&mut self, line_spacing: Size) { + self.ctx.line_spacing = line_spacing; + } + + /// The remaining unpadded, unexpanding spaces. + pub fn remaining(&self) -> LayoutSpaces { + let mut spaces = self.stack.remaining(); + *spaces[0].dimensions.get_secondary_mut(self.ctx.axes) + -= self.run.size.y; + spaces + } + + /// Whether the currently set line is empty. + pub fn line_is_empty(&self) -> bool { + self.run.size == Size2D::ZERO && self.run.layouts.is_empty() + } + + /// Finish the last line and compute the final multi-layout. + pub fn finish(mut self) -> LayoutResult<MultiLayout> { + self.finish_line_if_not_empty()?; + self.stack.finish() + } + + /// Finish the currently active space and start a new one. + pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { + self.finish_line_if_not_empty()?; + self.stack.finish_space(hard) + } + + /// Add the current line to the stack and start a new line. + pub fn finish_line(&mut self) -> LayoutResult<()> { + let mut actions = LayoutActions::new(); + + let layouts = std::mem::replace(&mut self.run.layouts, vec![]); + for (offset, layout) in layouts { + let x = match self.ctx.axes.primary.is_positive() { + true => offset, + false => self.run.size.x + - offset + - layout.dimensions.get_primary(self.ctx.axes), + }; + + let pos = Size2D::with_x(x); + actions.add_layout(pos, layout); + } + + self.stack.add(Layout { + dimensions: self.run.size.specialized(self.ctx.axes), + alignment: self.run.alignment + .unwrap_or(LayoutAlignment::new(Origin, Origin)), + actions: actions.to_vec(), + })?; + + self.run = LineRun::new(); + + self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND); + + Ok(()) + } + + /// Finish the current line if it is not empty. + fn finish_line_if_not_empty(&mut self) -> LayoutResult<()> { + if !self.line_is_empty() { + self.finish_line() + } else { + Ok(()) + } + } +} + +impl LineRun { + fn new() -> LineRun { + LineRun { + layouts: vec![], + size: Size2D::ZERO, + alignment: None, + usable: None, + last_spacing: LastSpacing::Hard, + } + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index f937a054..53c3e91e 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -9,7 +9,7 @@ use crate::style::LayoutStyle; mod actions; mod tree; -mod flex; +mod line; mod stack; mod text; @@ -30,7 +30,7 @@ pub mod prelude { /// Different kinds of layouters (fully re-exported). pub mod layouters { pub use super::tree::layout; - pub use super::flex::{FlexLayouter, FlexContext}; + pub use super::line::{LineLayouter, LineContext}; pub use super::stack::{StackLayouter, StackContext}; pub use super::text::{layout_text, TextContext}; } @@ -174,7 +174,7 @@ impl LayoutExpansion { LayoutExpansion { horizontal, vertical } } - /// Borrow the spcified component mutably. + /// Borrow the specified component mutably. pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { match axis { Horizontal => &mut self.horizontal, @@ -366,6 +366,9 @@ const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1); /// The standard spacing kind used for line spacing. const LINE_KIND: SpacingKind = SpacingKind::Soft(2); +/// The standard spacing kind used for word spacing. +const WORD_KIND: SpacingKind = SpacingKind::Soft(1); + /// The last appeared spacing. #[derive(Debug, Copy, Clone, PartialEq)] enum LastSpacing { diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 3c659d8a..e0562672 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,11 +1,9 @@ use smallvec::smallvec; +use crate::size::ValueBox; use super::*; /// The stack layouter stack boxes onto each other along the secondary layouting /// axis. -/// -/// The boxes are aligned along both axes according to their requested -/// alignment. #[derive(Debug, Clone)] pub struct StackLayouter { /// The context for layouting. @@ -42,30 +40,21 @@ struct Space { index: usize, /// Whether to add the layout for this space even if it would be empty. hard: bool, - /// The so-far accumulated subspaces. + /// The so-far accumulated layouts. layouts: Vec<(LayoutAxes, Layout)>, - /// The specialized size of this subspace. + /// The specialized size of this space. size: Size2D, /// The specialized remaining space. usable: Size2D, /// The specialized extra-needed dimensions to affect the size at all. extra: Size2D, - /// Dictates the valid alignments for new boxes in this space. - rulers: Rulers, + /// The rulers of a space dictate which alignments for new boxes are still + /// allowed and which require a new space to be started. + rulers: ValueBox<Alignment>, /// The last added spacing if the last added thing was spacing. last_spacing: LastSpacing, } -/// The rulers of a space dictate which alignments for new boxes are still -/// allowed and which require a new space to be started. -#[derive(Debug, Clone)] -struct Rulers { - top: Alignment, - bottom: Alignment, - left: Alignment, - right: Alignment, -} - impl StackLayouter { /// Create a new stack layouter. pub fn new(ctx: StackContext) -> StackLayouter { @@ -157,26 +146,6 @@ impl StackLayouter { } } - /// Update the rulers to account for the new layout. Returns true if a - /// space break is necessary. - fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool { - let axes = self.ctx.axes; - let allowed = self.alignment_allowed(axes.primary, alignment.primary) - && self.alignment_allowed(axes.secondary, alignment.secondary); - - if allowed { - *self.space.rulers.get(axes.secondary) = alignment.secondary; - } - - allowed - } - - /// Whether the given alignment is still allowed according to the rulers. - fn alignment_allowed(&mut self, direction: Direction, alignment: Alignment) -> bool { - alignment >= *self.space.rulers.get(direction) - && alignment <= self.space.rulers.get(direction.inv()).inv() - } - /// Update the size metrics to reflect that a layout or spacing with the /// given generalized dimensions has been added. fn update_metrics(&mut self, dimensions: Size2D) { @@ -196,10 +165,31 @@ impl StackLayouter { *self.space.usable.get_secondary_mut(axes) -= dimensions.y; } + /// Update the rulers to account for the new layout. Returns true if a + /// space break is necessary. + fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool { + let allowed = self.is_fitting_alignment(alignment); + if allowed { + *self.space.rulers.get_mut(self.ctx.axes.secondary, Origin) + = alignment.secondary; + } + allowed + } + + /// Whether a layout with the given alignment can still be layouted in the + /// active space. + pub fn is_fitting_alignment(&mut self, alignment: LayoutAlignment) -> bool { + self.is_fitting_axis(self.ctx.axes.primary, alignment.primary) + && self.is_fitting_axis(self.ctx.axes.secondary, alignment.secondary) + } + + /// Whether the given alignment is still allowed according to the rulers. + fn is_fitting_axis(&mut self, direction: Direction, alignment: Alignment) -> bool { + alignment >= *self.space.rulers.get_mut(direction, Origin) + && alignment <= self.space.rulers.get_mut(direction, End).inv() + } + /// Change the layouting axes used by this layouter. - /// - /// This starts a new subspace (if the axes are actually different from the - /// current ones). pub fn set_axes(&mut self, axes: LayoutAxes) { // Forget the spacing because it is not relevant anymore. if axes.secondary != self.ctx.axes.secondary { @@ -227,9 +217,7 @@ impl StackLayouter { /// The remaining unpadded, unexpanding spaces. If a multi-layout is laid /// out into these spaces, it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { - let dimensions = self.space.usable - - Size2D::with_y(self.space.last_spacing.soft_or_zero()) - .specialized(self.ctx.axes); + let dimensions = self.usable(); let mut spaces = smallvec![LayoutSpace { dimensions, @@ -244,9 +232,11 @@ impl StackLayouter { spaces } - /// The usable size along the primary axis. - pub fn primary_usable(&self) -> Size { - self.space.usable.get_primary(self.ctx.axes) + /// The remaining usable size. + pub fn usable(&self) -> Size2D { + self.space.usable + - Size2D::with_y(self.space.last_spacing.soft_or_zero()) + .specialized(self.ctx.axes) } /// Whether the current layout space (not subspace) is empty. @@ -409,24 +399,8 @@ impl Space { size: Size2D::ZERO, usable, extra: Size2D::ZERO, - rulers: Rulers { - top: Origin, - bottom: Origin, - left: Origin, - right: Origin, - }, + rulers: ValueBox::with_all(Origin), last_spacing: LastSpacing::Hard, } } } - -impl Rulers { - fn get(&mut self, direction: Direction) -> &mut Alignment { - match direction { - TopToBottom => &mut self.top, - BottomToTop => &mut self.bottom, - LeftToRight => &mut self.left, - RightToLeft => &mut self.right, - } - } -} diff --git a/src/layout/text.rs b/src/layout/text.rs index 996c5139..e9721429 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -12,6 +12,7 @@ use super::*; pub struct TextContext<'a, 'p> { pub loader: &'a SharedFontLoader<'p>, pub style: &'a TextStyle, + pub axes: LayoutAxes, pub alignment: LayoutAlignment, } @@ -50,22 +51,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> { /// Layout the text fn layout(mut self) -> LayoutResult<Layout> { - for c in self.text.chars() { - let (index, char_width) = self.select_font(c)?; - - self.width += char_width; - - if self.active_font != index { - if !self.buffer.is_empty() { - self.actions.add(LayoutAction::WriteText(self.buffer)); - self.buffer = String::new(); - } - - self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size())); - self.active_font = index; + if self.ctx.axes.primary.is_positive() { + for c in self.text.chars() { + self.layout_char(c)?; + } + } else { + for c in self.text.chars().rev() { + self.layout_char(c)?; } - - self.buffer.push(c); } if !self.buffer.is_empty() { @@ -79,6 +72,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> { }) } + /// Layout an individual character. + fn layout_char(&mut self, c: char) -> LayoutResult<()> { + let (index, char_width) = self.select_font(c)?; + + self.width += char_width; + + if self.active_font != index { + if !self.buffer.is_empty() { + let text = std::mem::replace(&mut self.buffer, String::new()); + self.actions.add(LayoutAction::WriteText(text)); + } + + self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size())); + self.active_font = index; + } + + self.buffer.push(c); + + Ok(()) + } + /// Select the best font for a character and return its index along with /// the width of the char in the font. fn select_font(&mut self, c: char) -> LayoutResult<(usize, Size)> { diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 7910fdd3..db59ca8d 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -16,7 +16,7 @@ pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout #[derive(Debug, Clone)] struct TreeLayouter<'a, 'p> { ctx: LayoutContext<'a, 'p>, - stack: StackLayouter, + layouter: LineLayouter, style: LayoutStyle, } @@ -24,12 +24,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Create a new syntax tree layouter. fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { - stack: StackLayouter::new(StackContext { + layouter: LineLayouter::new(LineContext { spaces: ctx.spaces.clone(), axes: ctx.axes, alignment: ctx.alignment, repeat: ctx.repeat, debug: ctx.debug, + line_spacing: ctx.style.text.line_spacing(), }), style: ctx.style.clone(), ctx, @@ -59,29 +60,27 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { let layout = layout_text(text, TextContext { loader: &self.ctx.loader, style: &self.style.text, + axes: self.ctx.axes, alignment: self.ctx.alignment, })?; - self.stack.add(layout) + self.layouter.add(layout) } fn layout_space(&mut self) { - + self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND); } fn layout_paragraph(&mut self) -> LayoutResult<()> { - Ok(self.stack.add_spacing( - paragraph_spacing(&self.style.text), - PARAGRAPH_KIND, - )) + self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND) } fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let commands = func.0.layout(LayoutContext { style: &self.style, - spaces: self.stack.remaining(), + spaces: self.layouter.remaining(), nested: true, - debug: true, + debug: false, .. self.ctx })?; @@ -98,26 +97,28 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { match command { LayoutTree(tree) => self.layout(tree)?, - Add(layout) => self.stack.add(layout)?, - AddMultiple(layouts) => self.stack.add_multiple(layouts)?, + Add(layout) => self.layouter.add(layout)?, + AddMultiple(layouts) => self.layouter.add_multiple(layouts)?, AddSpacing(space, kind, axis) => match axis { - Primary => {}, - Secondary => self.stack.add_spacing(space, kind), + Primary => self.layouter.add_primary_spacing(space, kind), + Secondary => self.layouter.add_secondary_spacing(space, kind)?, } - FinishLine => {}, - FinishRun => {}, - FinishSpace => self.stack.finish_space(true)?, + FinishLine => self.layouter.finish_line()?, + FinishSpace => self.layouter.finish_space(true)?, BreakParagraph => self.layout_paragraph()?, BreakPage => { if self.ctx.nested { error!("page break cannot be issued from nested context"); } - self.stack.finish_space(true)? + self.layouter.finish_space(true)? } - SetTextStyle(style) => self.style.text = style, + SetTextStyle(style) => { + self.layouter.set_line_spacing(style.line_spacing()); + self.style.text = style; + } SetPageStyle(style) => { if self.ctx.nested { error!("page style cannot be altered in nested context"); @@ -127,7 +128,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { let margins = style.margins(); self.ctx.base = style.dimensions.unpadded(margins); - self.stack.set_spaces(smallvec