diff options
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/flex.rs | 50 | ||||
| -rw-r--r-- | src/layout/mod.rs | 13 | ||||
| -rw-r--r-- | src/layout/stacked.rs | 27 | ||||
| -rw-r--r-- | src/layout/tree.rs | 24 |
4 files changed, 82 insertions, 32 deletions
diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 0ad31521..142530a6 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -19,7 +19,9 @@ use super::*; /// However, it can be any layout. #[derive(Debug, Clone)] pub struct FlexLayouter { - ctx: FlexContext, + axes: LayoutAxes, + flex_spacing: Size, + stack: StackLayouter, units: Vec<FlexUnit>, @@ -69,14 +71,16 @@ impl FlexLayouter { /// Create a new flex layouter. pub fn new(ctx: FlexContext) -> FlexLayouter { let stack = StackLayouter::new(StackContext { - spaces: ctx.spaces.clone(), + spaces: ctx.spaces, axes: ctx.axes, shrink_to_fit: ctx.shrink_to_fit, }); let usable = stack.usable().x; FlexLayouter { - ctx, + axes: ctx.axes, + flex_spacing: ctx.flex_spacing, + units: vec![], stack, @@ -126,6 +130,18 @@ impl FlexLayouter { self.units.push(FlexUnit::SetAxes(axes)); } + /// Update the followup space to be used by this flex layouter. + pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { + if replace_empty && self.box_is_empty() && self.stack.space_is_empty() { + self.stack.set_spaces(spaces, true); + self.total_usable = self.stack.usable().x; + self.usable = self.total_usable; + self.space = None; + } else { + self.stack.set_spaces(spaces, false); + } + } + /// Compute the justified layout. /// /// The layouter is not consumed by this to prevent ownership problems @@ -176,7 +192,7 @@ impl FlexLayouter { /// 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 size = self.ctx.axes.generalize(boxed.dimensions); + let size = self.axes.generalize(boxed.dimensions); if size.x > self.size_left() { self.space = None; @@ -213,7 +229,7 @@ impl FlexLayouter { } fn layout_set_axes(&mut self, axes: LayoutAxes) { - if axes.primary != self.ctx.axes.primary { + if axes.primary != self.axes.primary { self.finish_aligned_run(); self.usable = match axes.primary.alignment { @@ -231,11 +247,11 @@ impl FlexLayouter { }; } - if axes.secondary != self.ctx.axes.secondary { + if axes.secondary != self.axes.secondary { self.stack.set_axes(axes); } - self.ctx.axes = axes; + self.axes = axes; } /// Finish the current flex run. @@ -248,7 +264,7 @@ impl FlexLayouter { let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new()); self.stack.add(Layout { - dimensions: self.ctx.axes.specialize(self.merged_dimensions), + dimensions: self.axes.specialize(self.merged_dimensions), actions: actions.into_vec(), debug_render: false, })?; @@ -265,38 +281,33 @@ impl FlexLayouter { return; } - let factor = if self.ctx.axes.primary.axis.is_positive() { 1 } else { -1 }; - let anchor = self.ctx.axes.primary.anchor(self.total_usable) - - self.ctx.axes.primary.anchor(self.run.size.x); + let factor = if self.axes.primary.axis.is_positive() { 1 } else { -1 }; + let anchor = self.axes.primary.anchor(self.total_usable) + - self.axes.primary.anchor(self.run.size.x); self.max_extent = crate::size::max(self.max_extent, anchor + factor * self.run.size.x); for (offset, layout) in self.run.content.drain(..) { let general_position = Size2D::with_x(anchor + factor * offset); - let position = self.ctx.axes.specialize(general_position); + let position = self.axes.specialize(general_position); self.merged_actions.add_layout(position, layout); } - self.merged_dimensions.x = match self.ctx.axes.primary.alignment { + self.merged_dimensions.x = match self.axes.primary.alignment { Alignment::Origin => self.run.size.x, Alignment::Center | Alignment::End => self.total_usable, }; self.merged_dimensions.y = crate::size::max( self.merged_dimensions.y, - self.run.size.y + self.ctx.flex_spacing, + self.run.size.y + self.flex_spacing, ); self.last_run_remaining = Size2D::new(self.size_left(), self.merged_dimensions.y); self.run.size = Size2D::zero(); } - /// This layouter's context. - pub fn ctx(&self) -> &FlexContext { - &self.ctx - } - pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> { let mut future = self.clone(); future.finish_box()?; @@ -310,7 +321,6 @@ impl FlexLayouter { Ok((flex_spaces, stack_spaces)) } - /// Whether this layouter contains any items. pub fn box_is_empty(&self) -> bool { !self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_))) } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index a4514704..426a51ec 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -9,7 +9,7 @@ use toddle::Error as FontError; use crate::func::Command; use crate::size::{Size, Size2D, SizeBox}; -use crate::style::TextStyle; +use crate::style::{PageStyle, TextStyle}; use crate::syntax::{FuncCall, Node, SyntaxTree}; mod actions; @@ -131,9 +131,15 @@ pub struct LayoutContext<'a, 'p> { /// 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 style: &'a TextStyle, + 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, @@ -281,6 +287,8 @@ pub enum Alignment { /// The error type for layouting. pub enum LayoutError { + /// An action is unallowed in the active context. + Unallowed(&'static str), /// There is not enough space to add an item. NotEnoughSpace(&'static str), /// There was no suitable font for the given character. @@ -295,6 +303,7 @@ pub type LayoutResult<T> = Result<T, LayoutError>; error_type! { err: LayoutError, show: f => match err { + LayoutError::Unallowed(desc) => write!(f, "unallowed: {}", 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), diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index a7585046..a5db5638 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -96,9 +96,21 @@ impl StackLayouter { pub fn set_axes(&mut self, axes: LayoutAxes) { if axes != self.ctx.axes { self.finish_boxes(); + self.ctx.axes = axes; self.usable = self.remains(); self.dimensions = Size2D::zero(); - self.ctx.axes = axes; + } + } + + /// Update the followup space to be used by this flex layouter. + pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { + if replace_empty && self.space_is_empty() { + self.usable = self.ctx.axes.generalize(spaces[0].usable()); + self.active_space = 0; + self.ctx.spaces = spaces; + } else { + self.ctx.spaces.truncate(self.active_space + 1); + self.ctx.spaces.extend(spaces); } } @@ -143,6 +155,10 @@ impl StackLayouter { /// Compose all cached boxes into a layout. fn finish_boxes(&mut self) { + if self.boxes.is_empty() { + return; + } + let space = self.ctx.spaces[self.active_space]; let start = space.start() + Size2D::with_y(self.merged_dimensions.y); @@ -170,11 +186,6 @@ impl StackLayouter { self.merged_dimensions = merge_sizes(self.merged_dimensions, dimensions); } - /// 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 @@ -198,6 +209,10 @@ impl StackLayouter { Size2D::new(self.usable.x, self.usable.y - self.dimensions.y) } + pub fn space_is_empty(&self) -> bool { + self.boxes.is_empty() && self.merged_dimensions == Size2D::zero() + } + /// Whether this layouter is in its last space. pub fn in_last_space(&self) -> bool { self.active_space == self.ctx.spaces.len() - 1 diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 177a6308..ae6a15c8 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,4 +1,5 @@ use super::*; +use smallvec::smallvec; /// Layouts syntax trees into boxes. pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> { @@ -19,12 +20,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { flex: FlexLayouter::new(FlexContext { - flex_spacing: flex_spacing(&ctx.style), + flex_spacing: flex_spacing(&ctx.text_style), spaces: ctx.spaces.clone(), axes: ctx.axes, shrink_to_fit: ctx.shrink_to_fit, }), - style: ctx.style.clone(), + style: ctx.text_style.clone(), ctx, } } @@ -68,7 +69,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { let (flex_spaces, stack_spaces) = self.flex.remaining()?; let ctx = |spaces| LayoutContext { - style: &self.style, + top_level: false, + text_style: &self.style, spaces: spaces, shrink_to_fit: true, .. self.ctx @@ -107,7 +109,21 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::BreakParagraph => self.break_paragraph()?, - Command::SetStyle(style) => self.style = style, + 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"))?; + } + + self.ctx.page_style = style; + self.flex.set_spaces(smallvec![ + LayoutSpace { + dimensions: style.dimensions, + padding: style.margins, + } + ], true); + }, + Command::SetAxes(axes) => { self.flex.set_axes(axes); self.ctx.axes = axes; |
