diff --git a/src/layout/flex.rs b/src/layout/flex.rs index da1e1d59f..a364b6083 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -21,58 +21,31 @@ use super::*; pub struct FlexLayouter { ctx: FlexContext, units: Vec, - stack: StackLayouter, - usable_width: Size, + + usable: Size, run: FlexRun, - cached_glue: Option, + space: Option, } /// The context for flex layouting. /// /// See [`LayoutContext`] for details about the fields. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct FlexContext { + pub spaces: LayoutSpaces, + pub axes: LayoutAxes, /// The spacing between two lines of boxes. pub flex_spacing: Size, - pub alignment: Alignment, - pub space: LayoutSpace, - pub followup_spaces: Option, - pub shrink_to_fit: bool, -} - -macro_rules! reuse { - ($ctx:expr, $flex_spacing:expr) => ( - FlexContext { - flex_spacing: $flex_spacing, - alignment: $ctx.alignment, - space: $ctx.space, - followup_spaces: $ctx.followup_spaces, - shrink_to_fit: $ctx.shrink_to_fit, - } - ); -} - -impl FlexContext { - /// Create a flex context from a generic layout context. - pub fn from_layout_ctx(ctx: LayoutContext, flex_spacing: Size) -> FlexContext { - reuse!(ctx, flex_spacing) - } - - /// Create a flex context from a stack context. - pub fn from_stack_ctx(ctx: StackContext, flex_spacing: Size) -> FlexContext { - reuse!(ctx, flex_spacing) - } } #[derive(Debug, Clone)] enum FlexUnit { /// A content unit to be arranged flexibly. Boxed(Layout), - /// A unit which acts as glue between two [`FlexUnit::Boxed`] units and - /// is only present if there was no flow break in between the two - /// surrounding boxes. - Glue(Size2D), + /// Space between two box units which is only present if there + /// was no flow break in between the two surrounding units. + Space(Size), /// A forced break of the current flex run. Break, } @@ -86,18 +59,19 @@ struct FlexRun { impl FlexLayouter { /// Create a new flex layouter. pub fn new(ctx: FlexContext) -> FlexLayouter { + let stack = StackLayouter::new(StackContext { + spaces: ctx.spaces, + axes: ctx.axes, + }); + FlexLayouter { ctx, units: vec![], + stack, - stack: StackLayouter::new(StackContext::from_flex_ctx(ctx, Flow::Vertical)), - - usable_width: ctx.space.usable().x, - run: FlexRun { - content: vec![], - size: Size2D::zero() - }, - cached_glue: None, + usable: stack.usable().x, + run: FlexRun { content: vec![], size: Size2D::zero() }, + space: None, } } @@ -111,12 +85,12 @@ impl FlexLayouter { self.units.push(FlexUnit::Boxed(layout)); } - /// Add a glue box which can be replaced by a line break. - pub fn add_glue(&mut self, glue: Size2D) { - self.units.push(FlexUnit::Glue(glue)); + /// Add a space box which can be replaced by a run break. + pub fn add_space(&mut self, space: Size) { + self.units.push(FlexUnit::Space(space)); } - /// Add a forced line break. + /// Add a forced run break. pub fn add_break(&mut self) { self.units.push(FlexUnit::Break); } @@ -133,70 +107,71 @@ impl FlexLayouter { for unit in units { match unit { FlexUnit::Boxed(boxed) => self.layout_box(boxed)?, - FlexUnit::Glue(glue) => self.layout_glue(glue), - FlexUnit::Break => self.layout_break()?, + FlexUnit::Space(space) => { + self.space = Some(space); + } + + FlexUnit::Break => { + self.space = None; + self.finish_run()?; + }, } } // Finish the last flex run. self.finish_run()?; - self.stack.finish() + Ok(self.stack.finish()) } /// Layout a content box into the current flex run or start a new run if /// it does not fit. fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> { - let glue_width = self.cached_glue.unwrap_or(Size2D::zero()).x; - let new_line_width = self.run.size.x + glue_width + boxed.dimensions.x; + let size = boxed.dimensions.generalized(self.ctx.axes); - if self.overflows_line(new_line_width) { - self.cached_glue = None; + let space = self.space.unwrap_or(Size::zero()); + let new_run_size = self.run.size.x + space + size.x; - // If the box does not even fit on its own line, then we try - // it in the next space, or we have to give up if there is none. - if self.overflows_line(boxed.dimensions.x) { - if self.ctx.followup_spaces.is_some() { - self.stack.finish_layout(true)?; - return self.layout_box(boxed); - } else { - return Err(LayoutError::NotEnoughSpace("cannot fit box into flex run")); + if new_run_size > self.usable { + self.space = None; + + while size.x > self.usable { + if self.stack.in_last_space() { + Err(LayoutError::NotEnoughSpace("cannot fix box into flex run"))?; } + + self.stack.finish_layout(true); + self.usable = self.stack.usable().x; } self.finish_run()?; } - self.flush_glue(); + if let Some(space) = self.space.take() { + if self.run.size.x > Size::zero() && self.run.size.x + space <= self.usable { + self.run.size.x += space; + } + } - let dimensions = boxed.dimensions; self.run.content.push((self.run.size.x, boxed)); - self.grow_run(dimensions); + self.run.size.x += size.x; + self.run.size.y = crate::size::max(self.run.size.y, size.y); Ok(()) } - fn layout_glue(&mut self, glue: Size2D) { - self.cached_glue = Some(glue); - } - - fn layout_break(&mut self) -> LayoutResult<()> { - self.cached_glue = None; - self.finish_run() - } - /// Finish the current flex run. fn finish_run(&mut self) -> LayoutResult<()> { - self.run.size.y += self.ctx.flex_spacing; - let mut actions = LayoutActionList::new(); for (x, layout) in self.run.content.drain(..) { - let position = Size2D::with_x(x); + let position = Size2D::with_x(x).specialized(self.ctx.axes); actions.add_layout(position, layout); } + self.run.size.y += self.ctx.flex_spacing; + self.stack.add(Layout { - dimensions: self.run.size, + dimensions: self.run.size.specialized(self.ctx.axes), actions: actions.into_vec(), debug_render: false, })?; @@ -206,25 +181,8 @@ impl FlexLayouter { Ok(()) } - fn flush_glue(&mut self) { - if let Some(glue) = self.cached_glue.take() { - if self.run.size.x > Size::zero() && !self.overflows_line(self.run.size.x + glue.x) { - self.grow_run(glue); - } - } - } - - fn grow_run(&mut self, dimensions: Size2D) { - self.run.size.x += dimensions.x; - self.run.size.y = crate::size::max(self.run.size.y, dimensions.y); - } - /// Whether this layouter contains any items. pub fn is_empty(&self) -> bool { self.units.is_empty() } - - fn overflows_line(&self, line: Size) -> bool { - line > self.usable_width - } } diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index fd21c65c0..419ec6b7b 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -41,11 +41,6 @@ impl StackLayouter { } } - /// This layouter's context. - pub fn ctx(&self) -> StackContext { - self.ctx - } - /// Add a sublayout. pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { let size = layout.dimensions.generalized(self.ctx.axes); @@ -53,12 +48,11 @@ impl StackLayouter { // Search for a suitable space to insert the box. while !self.usable.fits(new_dimensions) { - if self.active_space == self.ctx.spaces.len() - 1 { - return Err(LayoutError::NotEnoughSpace("box is to large for stack spaces")); + if self.in_last_space() { + Err(LayoutError::NotEnoughSpace("cannot fit box into stack"))?; } - self.finish_layout()?; - self.start_new_space(true); + self.finish_layout(true); new_dimensions = self.size_with(size); } @@ -80,33 +74,36 @@ impl StackLayouter { } /// Add space after the last layout. - pub fn add_space(&mut self, space: Size) -> LayoutResult<()> { + pub fn add_space(&mut self, space: Size) { if self.dimensions.y + space > self.usable.y { - self.finish_layout()?; - self.start_new_space(false); + self.finish_layout(false); } else { self.dimensions.y += space; } - - Ok(()) } /// Finish the layouting. /// /// The layouter is not consumed by this to prevent ownership problems. /// Nevertheless, it should not be used further. - pub fn finish(&mut self) -> LayoutResult { + pub fn finish(&mut self) -> MultiLayout { if self.include_empty || !self.boxes.is_empty() { - self.finish_layout()?; + self.finish_boxes(); } - Ok(std::mem::replace(&mut self.layouts, MultiLayout::new())) + std::mem::replace(&mut self.layouts, MultiLayout::new()) } /// Finish the current layout and start a new one in a new space. /// - /// If `start_new_empty` is true, a new empty layout will be started. Otherwise, - /// the new layout only appears once new content is added. - pub fn finish_layout(&mut self) -> LayoutResult<()> { + /// If `include_empty` is true, the followup layout will even be + /// part of the finished multi-layout if it would be empty. + pub fn finish_layout(&mut self, include_empty: bool) { + self.finish_boxes(); + self.start_new_space(include_empty); + } + + /// Compose all cached boxes into a layout. + fn finish_boxes(&mut self) { let mut actions = LayoutActionList::new(); let space = self.ctx.spaces[self.active_space]; @@ -116,7 +113,7 @@ impl StackLayouter { for (offset, layout_anchor, layout) in self.boxes.drain(..) { let general_position = anchor - layout_anchor + Size2D::with_y(offset * factor); - let position = general_position.specialized(self.ctx.axes) + start; + let position = start + general_position.specialized(self.ctx.axes); actions.add_layout(position, layout); } @@ -130,8 +127,6 @@ impl StackLayouter { actions: actions.into_vec(), debug_render: true, }); - - Ok(()) } /// Set up layouting in the next space. Should be preceded by `finish_layout`. @@ -139,19 +134,34 @@ impl StackLayouter { /// If `include_empty` is true, the new empty layout will always be added when /// finishing this stack. Otherwise, the new layout only appears if new /// content is added to it. - pub fn start_new_space(&mut self, include_empty: bool) { + fn start_new_space(&mut self, include_empty: bool) { self.active_space = (self.active_space + 1).min(self.ctx.spaces.len() - 1); self.usable = self.ctx.spaces[self.active_space].usable().generalized(self.ctx.axes); self.dimensions = start_dimensions(self.usable, self.ctx.axes); self.include_empty = include_empty; } - /// The remaining space for new layouts. + /// This layouter's context. + pub fn ctx(&self) -> StackContext { + self.ctx + } + + /// The (generalized) usable area of the current space. + pub fn usable(&self) -> Size2D { + self.usable + } + + /// The (specialized) remaining area for new layouts in the current space. pub fn remaining(&self) -> Size2D { Size2D::new(self.usable.x, self.usable.y - self.dimensions.y) .specialized(self.ctx.axes) } + /// Whether this layouter is in its last space. + pub fn in_last_space(&self) -> bool { + self.active_space == self.ctx.spaces.len() - 1 + } + /// The combined size of the so-far included boxes with the other size. fn size_with(&self, other: Size2D) -> Size2D { Size2D { diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 4f86f721e..b64dd6ebd 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -59,7 +59,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { if self.set_newline { let space = paragraph_spacing(&self.style); - self.stack.add_space(space)?; + self.stack.add_space(space); self.set_newline = false; } @@ -81,7 +81,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Finish the layout. fn finish(mut self) -> LayoutResult { self.finish_flex()?; - self.stack.finish() + Ok(self.stack.finish()) } /// Layout a function. @@ -136,8 +136,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::FinishLayout => { self.finish_flex()?; - self.stack.finish_layout()?; - self.stack.start_new_space(true); + self.stack.finish_layout(true); self.start_new_flex(); } diff --git a/src/lib.rs b/src/lib.rs index 21d8b3c03..43f35511c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub extern crate toddle; use std::cell::RefCell; +use smallvec::smallvec; use toddle::query::{FontLoader, FontProvider, SharedFontLoader}; use crate::func::Scope; @@ -93,27 +94,22 @@ impl<'p> Typesetter<'p> { /// Layout a syntax tree and return the produced layout. pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult { - let space = LayoutSpace { - dimensions: self.page_style.dimensions, - padding: self.page_style.margins, - }; - - let pages = layout_tree( + Ok(layout_tree( &tree, LayoutContext { loader: &self.loader, style: &self.text_style, - space, - followup_spaces: Some(space), - shrink_to_fit: false, + spaces: smallvec![LayoutSpace { + dimensions: self.page_style.dimensions, + padding: self.page_style.margins, + shrink_to_fit: false, + }], axes: LayoutAxes { - primary: AlignedAxis::new(Axis::LeftToRight, Alignment::Left).unwrap(), - secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Top).unwrap(), + primary: AlignedAxis::new(Axis::LeftToRight, Alignment::Origin), + secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Origin), }, }, - )?; - - Ok(pages) + )?) } /// Process source code directly into a layout. diff --git a/src/macros.rs b/src/macros.rs index a162d3cc9..70c67105e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -40,16 +40,6 @@ macro_rules! error_type { }; } -/// Whether an expression matches a pattern. -macro_rules! matches { - ($val:expr, $($pattern:tt)*) => ( - match $val { - $($pattern)* => true, - _ => false, - } - ); -} - /// Create a `Debug` implementation from a `Display` implementation. macro_rules! debug_display { ($type:ident) => (