From b13ed627fff73a599b34d760cd99aa2f08d58ea8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 30 Nov 2019 14:10:35 +0100 Subject: =?UTF-8?q?Better=20error=20reporting=20=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/flex.rs | 18 +--- src/layout/mod.rs | 47 +++------ src/layout/stack.rs | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/layout/stacked.rs | 263 -------------------------------------------------- src/layout/text.rs | 2 +- src/layout/tree.rs | 36 +++---- 6 files changed, 291 insertions(+), 338 deletions(-) create mode 100644 src/layout/stack.rs delete mode 100644 src/layout/stacked.rs (limited to 'src/layout') diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 053954a4..53f6dfdf 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -126,20 +126,8 @@ impl FlexLayouter { } } - pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, Option)> { - if self.run_is_empty() { - Ok((self.stack.remaining(), None)) - } else { - let mut future = self.clone(); - let remaining_run = future.finish_run()?; - - let stack_spaces = future.stack.remaining(); - let mut flex_spaces = stack_spaces.clone(); - flex_spaces[0].dimensions.x = remaining_run.x; - flex_spaces[0].dimensions.y += remaining_run.y; - - Ok((flex_spaces, Some(stack_spaces))) - } + pub fn remaining(&self) -> LayoutSpaces { + self.stack.remaining() } pub fn run_is_empty(&self) -> bool { @@ -242,7 +230,7 @@ impl FlexLayouter { while size.x > self.line.usable { if self.stack.space_is_last() { - Err(LayoutError::NotEnoughSpace("failed to add box to flex run"))?; + lerr!("box does not fit into line"); } self.stack.finish_space(true); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 2e643295..cd4986d9 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -15,14 +15,14 @@ use crate::syntax::{FuncCall, Node, SyntaxTree}; mod actions; mod tree; mod flex; -mod stacked; +mod stack; mod text; /// Different kinds of layouters (fully re-exported). pub mod layouters { pub use super::tree::layout_tree; pub use super::flex::{FlexLayouter, FlexContext}; - pub use super::stacked::{StackLayouter, StackContext}; + pub use super::stack::{StackLayouter, StackContext}; pub use super::text::{layout_text, TextContext}; } @@ -130,23 +130,17 @@ pub struct LayoutContext<'a, 'p> { /// The font loader to retrieve fonts from when typesetting text /// using [`layout_text`]. pub loader: &'a SharedFontLoader<'p>, - /// Whether this layouting process handles the top-level pages. pub top_level: bool, - /// The style to set text with. This includes sizes and font classes /// which determine which font from the loaders selection is used. pub text_style: &'a TextStyle, - /// The current size and margins of the top-level pages. pub page_style: PageStyle, - /// The spaces to layout in. pub spaces: LayoutSpaces, - /// The axes to flow on. pub axes: LayoutAxes, - /// Whether layouts should expand to the full dimensions of the space /// they lie on or whether should tightly fit the content. pub expand: bool, @@ -160,7 +154,6 @@ pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>; pub struct LayoutSpace { /// The maximum size of the box to layout in. pub dimensions: Size2D, - /// Padding that should be respected on each side. pub padding: SizeBox, } @@ -331,35 +324,21 @@ impl SpaceState { } /// The error type for layouting. -pub enum LayoutError { - /// An action is unallowed in the active context. - Unallowed(&'static str), - /// A specifier or operation is invalid for the given axis. - UnalignedAxis(&'static str), - /// There is not enough space to add an item. - NotEnoughSpace(&'static str), - /// There was no suitable font for the given character. - NoSuitableFont(char), - /// An error occured while gathering font data. - Font(FontError), -} +pub struct LayoutError(String); /// The result type for layouting. pub type LayoutResult = Result; +impl LayoutError { + /// Create a new layout error with a message. + pub fn new>(message: S) -> LayoutError { + LayoutError(message.into()) + } +} + error_type! { err: LayoutError, - show: f => match err { - LayoutError::Unallowed(desc) => write!(f, "unallowed: {}", desc), - LayoutError::UnalignedAxis(desc) => write!(f, "unaligned axis: {}", desc), - LayoutError::NotEnoughSpace(desc) => write!(f, "not enough space: {}", desc), - LayoutError::NoSuitableFont(c) => write!(f, "no suitable font for '{}'", c), - LayoutError::Font(err) => write!(f, "font error: {}", err), - }, - source: match err { - LayoutError::Font(err) => Some(err), - _ => None, - }, - from: (std::io::Error, LayoutError::Font(FontError::Io(err))), - from: (FontError, LayoutError::Font(err)), + show: f => f.write_str(&err.0), + from: (std::io::Error, LayoutError::new(err.to_string())), + from: (FontError, LayoutError::new(err.to_string())), } diff --git a/src/layout/stack.rs b/src/layout/stack.rs new file mode 100644 index 00000000..f46c3da0 --- /dev/null +++ b/src/layout/stack.rs @@ -0,0 +1,263 @@ +use smallvec::smallvec; +use super::*; + +#[derive(Debug, Clone)] +pub struct StackLayouter { + ctx: StackContext, + layouts: MultiLayout, + + space: Space, + sub: Subspace, +} + +#[derive(Debug, Clone)] +struct Space { + index: usize, + hard: bool, + actions: LayoutActionList, + combined_dimensions: Size2D, +} + +impl Space { + fn new(index: usize, hard: bool) -> Space { + Space { + index, + hard, + actions: LayoutActionList::new(), + combined_dimensions: Size2D::zero(), + } + } +} + +#[derive(Debug, Clone)] +struct Subspace { + origin: Size2D, + anchor: Size2D, + factor: i32, + + boxes: Vec<(Size, Size, Layout)>, + + usable: Size2D, + dimensions: Size2D, + + space: SpaceState, +} + +impl Subspace { + fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace { + Subspace { + origin, + anchor: axes.anchor(usable), + factor: axes.secondary.axis.factor(), + boxes: vec![], + usable: axes.generalize(usable), + dimensions: Size2D::zero(), + space: SpaceState::Forbidden, + } + } +} + +/// The context for stack layouting. +/// +/// See [`LayoutContext`] for details about the fields. +#[derive(Debug, Clone)] +pub struct StackContext { + pub spaces: LayoutSpaces, + pub axes: LayoutAxes, + pub expand: bool, +} + +impl StackLayouter { + /// Create a new stack layouter. + pub fn new(ctx: StackContext) -> StackLayouter { + let axes = ctx.axes; + let space = ctx.spaces[0]; + + StackLayouter { + ctx, + layouts: MultiLayout::new(), + space: Space::new(0, true), + sub: Subspace::new(space.start(), space.usable(), axes), + } + } + + pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { + if let SpaceState::Soft(space) = self.sub.space { + self.add_space(space, SpaceKind::Hard); + } + + let size = self.ctx.axes.generalize(layout.dimensions); + + let mut new_dimensions = Size2D { + x: crate::size::max(self.sub.dimensions.x, size.x), + y: self.sub.dimensions.y + size.y + }; + + while !self.sub.usable.fits(new_dimensions) { + if self.space_is_last() && self.space_is_empty() { + lerr!("box does not fit into stack"); + } + + self.finish_space(true); + new_dimensions = size; + } + + let offset = self.sub.dimensions.y; + let anchor = self.ctx.axes.primary.anchor(size.x); + + self.sub.boxes.push((offset, anchor, layout)); + self.sub.dimensions = new_dimensions; + self.sub.space = SpaceState::Allowed; + + Ok(()) + } + + pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> { + for layout in layouts { + self.add(layout)?; + } + Ok(()) + } + + pub fn add_space(&mut self, space: Size, kind: SpaceKind) { + if kind == SpaceKind::Soft { + if self.sub.space != SpaceState::Forbidden { + self.sub.space = SpaceState::Soft(space); + } + } else { + if self.sub.dimensions.y + space > self.sub.usable.y { + self.sub.dimensions.y = self.sub.usable.y; + } else { + self.sub.dimensions.y += space; + } + + if kind == SpaceKind::Hard { + self.sub.space = SpaceState::Forbidden; + } + } + } + + pub fn set_axes(&mut self, axes: LayoutAxes) { + if axes != self.ctx.axes { + self.finish_subspace(); + let (origin, usable) = self.remaining_subspace(); + self.ctx.axes = axes; + self.sub = Subspace::new(origin, usable, axes); + } + } + + pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { + if replace_empty && self.space_is_empty() { + self.ctx.spaces = spaces; + self.start_space(0, self.space.hard); + } else { + self.ctx.spaces.truncate(self.space.index + 1); + self.ctx.spaces.extend(spaces); + } + } + + pub fn remaining(&self) -> LayoutSpaces { + let mut spaces = smallvec![LayoutSpace { + dimensions: self.remaining_subspace().1, + padding: SizeBox::zero(), + }]; + + for space in &self.ctx.spaces[self.next_space()..] { + spaces.push(space.usable_space()); + } + + spaces + } + + pub fn primary_usable(&self) -> Size { + self.sub.usable.x + } + + pub fn space_is_empty(&self) -> bool { + self.space.combined_dimensions == Size2D::zero() + && self.space.actions.is_empty() + && self.sub.dimensions == Size2D::zero() + } + + pub fn space_is_last(&self) -> bool { + self.space.index == self.ctx.spaces.len() - 1 + } + + pub fn finish(mut self) -> MultiLayout { + if self.space.hard || !self.space_is_empty() { + self.finish_space(false); + } + self.layouts + } + + pub fn finish_space(&mut self, hard: bool) { + self.finish_subspace(); + + let space = self.ctx.spaces[self.space.index]; + + self.layouts.add(Layout { + dimensions: match self.ctx.expand { + true => space.dimensions, + false => self.space.combined_dimensions.padded(space.padding), + }, + actions: self.space.actions.to_vec(), + debug_render: true, + }); + + self.start_space(self.next_space(), hard); + } + + fn start_space(&mut self, space: usize, hard: bool) { + self.space = Space::new(space, hard); + + let space = self.ctx.spaces[space]; + self.sub = Subspace::new(space.start(), space.usable(), self.ctx.axes); + } + + fn next_space(&self) -> usize { + (self.space.index + 1).min(self.ctx.spaces.len() - 1) + } + + fn finish_subspace(&mut self) { + let factor = self.ctx.axes.secondary.axis.factor(); + let anchor = + self.ctx.axes.anchor(self.sub.usable) + - self.ctx.axes.anchor(Size2D::with_y(self.sub.dimensions.y)); + + for (offset, layout_anchor, layout) in self.sub.boxes.drain(..) { + let pos = self.sub.origin + + self.ctx.axes.specialize( + anchor + Size2D::new(-layout_anchor, factor * offset) + ); + + self.space.actions.add_layout(pos, layout); + } + + if self.ctx.axes.primary.needs_expansion() { + self.sub.dimensions.x = self.sub.usable.x; + } + + if self.ctx.axes.secondary.needs_expansion() { + self.sub.dimensions.y = self.sub.usable.y; + } + + let space = self.ctx.spaces[self.space.index]; + let origin = self.sub.origin; + let dimensions = self.ctx.axes.specialize(self.sub.dimensions); + self.space.combined_dimensions.max_eq(origin - space.start() + dimensions); + } + + fn remaining_subspace(&self) -> (Size2D, Size2D) { + let new_origin = self.sub.origin + match self.ctx.axes.secondary.axis.is_positive() { + true => self.ctx.axes.specialize(Size2D::with_y(self.sub.dimensions.y)), + false => Size2D::zero(), + }; + + let new_usable = self.ctx.axes.specialize(Size2D { + x: self.sub.usable.x, + y: self.sub.usable.y - self.sub.dimensions.y - self.sub.space.soft_or_zero(), + }); + + (new_origin, new_usable) + } +} diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs deleted file mode 100644 index 2e128a5f..00000000 --- a/src/layout/stacked.rs +++ /dev/null @@ -1,263 +0,0 @@ -use smallvec::smallvec; -use super::*; - -#[derive(Debug, Clone)] -pub struct StackLayouter { - ctx: StackContext, - layouts: MultiLayout, - - space: Space, - sub: Subspace, -} - -#[derive(Debug, Clone)] -struct Space { - index: usize, - hard: bool, - actions: LayoutActionList, - combined_dimensions: Size2D, -} - -impl Space { - fn new(index: usize, hard: bool) -> Space { - Space { - index, - hard, - actions: LayoutActionList::new(), - combined_dimensions: Size2D::zero(), - } - } -} - -#[derive(Debug, Clone)] -struct Subspace { - origin: Size2D, - anchor: Size2D, - factor: i32, - - boxes: Vec<(Size, Size, Layout)>, - - usable: Size2D, - dimensions: Size2D, - - space: SpaceState, -} - -impl Subspace { - fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace { - Subspace { - origin, - anchor: axes.anchor(usable), - factor: axes.secondary.axis.factor(), - boxes: vec![], - usable: axes.generalize(usable), - dimensions: Size2D::zero(), - space: SpaceState::Forbidden, - } - } -} - -/// The context for stack layouting. -/// -/// See [`LayoutContext`] for details about the fields. -#[derive(Debug, Clone)] -pub struct StackContext { - pub spaces: LayoutSpaces, - pub axes: LayoutAxes, - pub expand: bool, -} - -impl StackLayouter { - /// Create a new stack layouter. - pub fn new(ctx: StackContext) -> StackLayouter { - let axes = ctx.axes; - let space = ctx.spaces[0]; - - StackLayouter { - ctx, - layouts: MultiLayout::new(), - space: Space::new(0, true), - sub: Subspace::new(space.start(), space.usable(), axes), - } - } - - pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { - if let SpaceState::Soft(space) = self.sub.space { - self.add_space(space, SpaceKind::Hard); - } - - let size = self.ctx.axes.generalize(layout.dimensions); - - let mut new_dimensions = Size2D { - x: crate::size::max(self.sub.dimensions.x, size.x), - y: self.sub.dimensions.y + size.y - }; - - while !self.sub.usable.fits(new_dimensions) { - if self.space_is_last() && self.space_is_empty() { - Err(LayoutError::NotEnoughSpace("failed to add box to stack"))?; - } - - self.finish_space(true); - new_dimensions = size; - } - - let offset = self.sub.dimensions.y; - let anchor = self.ctx.axes.primary.anchor(size.x); - - self.sub.boxes.push((offset, anchor, layout)); - self.sub.dimensions = new_dimensions; - self.sub.space = SpaceState::Allowed; - - Ok(()) - } - - pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> { - for layout in layouts { - self.add(layout)?; - } - Ok(()) - } - - pub fn add_space(&mut self, space: Size, kind: SpaceKind) { - if kind == SpaceKind::Soft { - if self.sub.space != SpaceState::Forbidden { - self.sub.space = SpaceState::Soft(space); - } - } else { - if self.sub.dimensions.y + space > self.sub.usable.y { - self.sub.dimensions.y = self.sub.usable.y; - } else { - self.sub.dimensions.y += space; - } - - if kind == SpaceKind::Hard { - self.sub.space = SpaceState::Forbidden; - } - } - } - - pub fn set_axes(&mut self, axes: LayoutAxes) { - if axes != self.ctx.axes { - self.finish_subspace(); - let (origin, usable) = self.remaining_subspace(); - self.ctx.axes = axes; - self.sub = Subspace::new(origin, usable, axes); - } - } - - pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { - if replace_empty && self.space_is_empty() { - self.ctx.spaces = spaces; - self.start_space(0, self.space.hard); - } else { - self.ctx.spaces.truncate(self.space.index + 1); - self.ctx.spaces.extend(spaces); - } - } - - pub fn remaining(&self) -> LayoutSpaces { - let mut spaces = smallvec![LayoutSpace { - dimensions: self.remaining_subspace().1, - padding: SizeBox::zero(), - }]; - - for space in &self.ctx.spaces[self.next_space()..] { - spaces.push(space.usable_space()); - } - - spaces - } - - pub fn primary_usable(&self) -> Size { - self.sub.usable.x - } - - pub fn space_is_empty(&self) -> bool { - self.space.combined_dimensions == Size2D::zero() - && self.space.actions.is_empty() - && self.sub.dimensions == Size2D::zero() - } - - pub fn space_is_last(&self) -> bool { - self.space.index == self.ctx.spaces.len() - 1 - } - - pub fn finish(mut self) -> MultiLayout { - if self.space.hard || !self.space_is_empty() { - self.finish_space(false); - } - self.layouts - } - - pub fn finish_space(&mut self, hard: bool) { - self.finish_subspace(); - - let space = self.ctx.spaces[self.space.index]; - - self.layouts.add(Layout { - dimensions: match self.ctx.expand { - true => space.dimensions, - false => self.space.combined_dimensions.padded(space.padding), - }, - actions: self.space.actions.to_vec(), - debug_render: true, - }); - - self.start_space(self.next_space(), hard); - } - - fn start_space(&mut self, space: usize, hard: bool) { - self.space = Space::new(space, hard); - - let space = self.ctx.spaces[space]; - self.sub = Subspace::new(space.start(), space.usable(), self.ctx.axes); - } - - fn next_space(&self) -> usize { - (self.space.index + 1).min(self.ctx.spaces.len() - 1) - } - - fn finish_subspace(&mut self) { - let factor = self.ctx.axes.secondary.axis.factor(); - let anchor = - self.ctx.axes.anchor(self.sub.usable) - - self.ctx.axes.anchor(Size2D::with_y(self.sub.dimensions.y)); - - for (offset, layout_anchor, layout) in self.sub.boxes.drain(..) { - let pos = self.sub.origin - + self.ctx.axes.specialize( - anchor + Size2D::new(-layout_anchor, factor * offset) - ); - - self.space.actions.add_layout(pos, layout); - } - - if self.ctx.axes.primary.needs_expansion() { - self.sub.dimensions.x = self.sub.usable.x; - } - - if self.ctx.axes.secondary.needs_expansion() { - self.sub.dimensions.y = self.sub.usable.y; - } - - let space = self.ctx.spaces[self.space.index]; - let origin = self.sub.origin; - let dimensions = self.ctx.axes.specialize(self.sub.dimensions); - self.space.combined_dimensions.max_eq(origin - space.start() + dimensions); - } - - fn remaining_subspace(&self) -> (Size2D, Size2D) { - let new_origin = self.sub.origin + match self.ctx.axes.secondary.axis.is_positive() { - true => self.ctx.axes.specialize(Size2D::with_y(self.sub.dimensions.y)), - false => Size2D::zero(), - }; - - let new_usable = self.ctx.axes.specialize(Size2D { - x: self.sub.usable.x, - y: self.sub.usable.y - self.sub.dimensions.y - self.sub.space.soft_or_zero(), - }); - - (new_origin, new_usable) - } -} diff --git a/src/layout/text.rs b/src/layout/text.rs index fc7cd385..66ff75fd 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -114,6 +114,6 @@ impl<'a, 'p> TextLayouter<'a, 'p> { self.classes.pop(); } - Err(LayoutError::NoSuitableFont(c)) + lerr!("no suitable font for character `{}`", c); } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index f5ae2435..56fb120c 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -66,34 +66,20 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { - let (first, second) = self.flex.remaining()?; + let spaces = self.flex.remaining(); let mut axes = self.ctx.axes.expanding(false); axes.secondary.alignment = Alignment::Origin; - let ctx = |spaces| { - LayoutContext { - loader: self.ctx.loader, - top_level: false, - text_style: &self.style, - page_style: self.ctx.page_style, - spaces, - axes, - expand: false, - } - }; - - let commands = match func.body.val.layout(ctx(first)) { - Ok(c) => c, - Err(e) => { - match (e, second) { - (LayoutError::NotEnoughSpace(_), Some(space)) => { - func.body.val.layout(ctx(space))? - } - (e, _) => Err(e)?, - } - } - }; + let commands = func.body.val.layout(LayoutContext { + loader: self.ctx.loader, + top_level: false, + text_style: &self.style, + page_style: self.ctx.page_style, + spaces, + axes, + expand: false, + })?; for command in commands { self.execute(command)?; @@ -123,7 +109,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::SetTextStyle(style) => self.style = style, Command::SetPageStyle(style) => { if !self.ctx.top_level { - Err(LayoutError::Unallowed("can only set page style from top level"))?; + lerr!("page style cannot only be altered in the top-level context"); } self.ctx.page_style = style; -- cgit v1.2.3