diff --git a/src/func/mod.rs b/src/func/mod.rs index e921966a7..f59c1ec06 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -93,9 +93,11 @@ pub enum Command<'a> { Add(Layout), AddMultiple(MultiLayout), - BreakFlex, - FinishFlex, - BreakStack, + FinishRun, + FinishBox, + FinishLayout, + + BreakParagraph, SetStyle(TextStyle), SetAxes(LayoutAxes), diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 01086c32e..dfe38bba5 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -20,8 +20,8 @@ use super::*; #[derive(Debug, Clone)] pub struct FlexLayouter { ctx: FlexContext, - units: Vec, stack: StackLayouter, + units: Vec, usable: Size, run: FlexRun, @@ -35,6 +35,7 @@ pub struct FlexLayouter { pub struct FlexContext { pub spaces: LayoutSpaces, pub axes: LayoutAxes, + pub shrink_to_fit: bool, /// The spacing between two lines of boxes. pub flex_spacing: Size, } @@ -63,6 +64,7 @@ impl FlexLayouter { let stack = StackLayouter::new(StackContext { spaces: ctx.spaces, axes: ctx.axes, + shrink_to_fit: ctx.shrink_to_fit, }); FlexLayouter { @@ -88,14 +90,20 @@ impl FlexLayouter { } } + /// Add a forced run break. + pub fn add_run_break(&mut self) { + self.units.push(FlexUnit::Break); + } + /// Add a space box which can be replaced by a run break. - pub fn add_space(&mut self, space: Size) { + pub fn add_primary_space(&mut self, space: Size) { self.units.push(FlexUnit::Space(space)); } - /// Add a forced run break. - pub fn add_break(&mut self) { - self.units.push(FlexUnit::Break); + pub fn add_secondary_space(&mut self, space: Size) -> LayoutResult<()> { + self.finish_box()?; + self.stack.add_space(space); + Ok(()) } /// Update the axes in use by this flex layouter. @@ -109,6 +117,21 @@ impl FlexLayouter { /// with borrowed layouters. The state of the layouter is not reset. /// Therefore, it should not be further used after calling `finish`. pub fn finish(&mut self) -> LayoutResult { + self.finish_box()?; + Ok(self.stack.finish()) + } + + pub fn finish_layout(&mut self, hard: bool) -> LayoutResult<()> { + self.finish_box()?; + self.stack.finish_layout(hard); + Ok(()) + } + + pub fn finish_box(&mut self) -> LayoutResult<()> { + if self.box_is_empty() { + return Ok(()); + } + // Move the units out of the layout because otherwise, we run into // ownership problems. let units = std::mem::replace(&mut self.units, vec![]); @@ -131,49 +154,9 @@ impl FlexLayouter { // Finish the last flex run. self.finish_run()?; - 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 size = self.ctx.axes.generalize(boxed.dimensions); - - let space = self.space.unwrap_or(Size::zero()); - let new_run_size = self.run.size.x + space + size.x; - - 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.add_break(true); - self.usable = self.stack.usable().x; - } - - self.finish_run()?; - } - - 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; - } - } - - self.run.content.push((self.run.size.x, boxed)); - self.run.size.x += size.x; - self.run.size.y = crate::size::max(self.run.size.y, size.y); - Ok(()) } - fn layout_set_axes(&mut self, axes: LayoutAxes) { - // TODO - } - /// Finish the current flex run. fn finish_run(&mut self) -> LayoutResult<()> { let mut actions = LayoutActionList::new(); @@ -195,13 +178,59 @@ impl FlexLayouter { Ok(()) } + /// 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 space = self.space.unwrap_or(Size::zero()); + let new_run_size = self.run.size.x + space + size.x; + + 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()?; + } + + 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; + } + } + + self.run.content.push((self.run.size.x, boxed)); + self.run.size.x += size.x; + self.run.size.y = crate::size::max(self.run.size.y, size.y); + + Ok(()) + } + + fn layout_set_axes(&mut self, axes: LayoutAxes) { + // TODO + } + /// This layouter's context. pub fn ctx(&self) -> FlexContext { self.ctx } + pub fn remaining(&self) -> LayoutResult { + let mut future = self.clone(); + future.finish_box()?; + Ok(future.stack.remaining()) + } + /// Whether this layouter contains any items. - pub fn is_empty(&self) -> bool { + pub fn box_is_empty(&self) -> bool { self.units.is_empty() } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 442747b5f..9bb919567 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -150,6 +150,10 @@ pub struct LayoutContext<'a, 'p> { /// The axes to flow on. pub axes: LayoutAxes, + + /// Whether to shrink the spaces to fit the content or to keep + /// the original dimensions. + pub shrink_to_fit: bool, } /// A possibly stack-allocated vector of layout spaces. @@ -163,10 +167,6 @@ pub struct LayoutSpace { /// Padding that should be respected on each side. pub padding: SizeBox, - - /// Whether to shrink the space to fit the content or to keep - /// the original dimensions. - pub shrink_to_fit: bool, } impl LayoutSpace { @@ -182,11 +182,10 @@ impl LayoutSpace { } /// A layout space without padding and dimensions reduced by the padding. - pub fn usable_space(&self, shrink_to_fit: bool) -> LayoutSpace { + pub fn usable_space(&self) -> LayoutSpace { LayoutSpace { dimensions: self.usable(), padding: SizeBox::zero(), - shrink_to_fit, } } } diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index d80ebf7af..764b8325b 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -27,6 +27,7 @@ pub struct StackLayouter { pub struct StackContext { pub spaces: LayoutSpaces, pub axes: LayoutAxes, + pub shrink_to_fit: bool, } impl StackLayouter { @@ -61,7 +62,7 @@ impl StackLayouter { Err(LayoutError::NotEnoughSpace("cannot fit box into stack"))?; } - self.add_break(true); + self.finish_layout(true); new_dimensions = merge_sizes(self.dimensions, size); } @@ -85,16 +86,20 @@ impl StackLayouter { /// Add space after the last layout. pub fn add_space(&mut self, space: Size) { if self.dimensions.y + space > self.usable.y { - self.add_break(false); + self.finish_layout(false); } else { self.dimensions.y += space; } } - /// Finish the current layout and start a new one in a new space. - pub fn add_break(&mut self, hard: bool) { - self.finish_layout(); - self.start_new_space(hard); + /// Update the axes in use by this stack layouter. + pub fn set_axes(&self, axes: LayoutAxes) { + if axes != self.ctx.axes { + self.finish_boxes(); + self.usable = self.remains(); + self.dimensions = Size2D::zero(); + self.ctx.axes = axes; + } } /// Finish the layouting. @@ -103,19 +108,20 @@ impl StackLayouter { /// Nevertheless, it should not be used further. pub fn finish(&mut self) -> MultiLayout { if self.hard || !self.boxes.is_empty() { - self.finish_layout(); + self.finish_layout(false); } std::mem::replace(&mut self.layouts, MultiLayout::new()) } - fn finish_layout(&mut self) { + /// Finish the current layout and start a new one in a new space. + pub fn finish_layout(&mut self, hard: bool) { self.finish_boxes(); let space = self.ctx.spaces[self.active_space]; let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new()); self.layouts.add(Layout { - dimensions: if space.shrink_to_fit { + dimensions: if self.ctx.shrink_to_fit { self.merged_dimensions.padded(space.padding) } else { space.dimensions @@ -123,6 +129,16 @@ impl StackLayouter { actions: actions.into_vec(), debug_render: true, }); + + let next_space = self.next_space(); + let space = self.ctx.spaces[next_space]; + + self.merged_dimensions = Size2D::zero(); + + self.usable = self.ctx.axes.generalize(space.usable()); + self.dimensions = Size2D::zero(); + self.active_space = next_space; + self.hard = hard; } /// Compose all cached boxes into a layout. @@ -154,29 +170,6 @@ impl StackLayouter { self.merged_dimensions = merge_sizes(self.merged_dimensions, dimensions); } - /// Set up layouting in the next space. - fn start_new_space(&mut self, hard: bool) { - let next_space = self.next_space(); - let space = self.ctx.spaces[next_space]; - - self.merged_dimensions = Size2D::zero(); - - self.usable = self.ctx.axes.generalize(space.usable()); - self.dimensions = Size2D::zero(); - self.active_space = next_space; - self.hard = hard; - } - - /// Update the axes in use by this stack layouter. - pub fn set_axes(&self, axes: LayoutAxes) { - if axes != self.ctx.axes { - self.finish_boxes(); - self.usable = self.remains(); - self.dimensions = Size2D::zero(); - self.ctx.axes = axes; - } - } - /// This layouter's context. pub fn ctx(&self) -> StackContext { self.ctx @@ -187,16 +180,15 @@ impl StackLayouter { self.usable } - /// The remaining spaces for new layouts in the current space. - pub fn remaining(&self, shrink_to_fit: bool) -> LayoutSpaces { + /// The remaining usable spaces for new layouts. + pub fn remaining(&self) -> LayoutSpaces { let mut spaces = smallvec![LayoutSpace { dimensions: self.ctx.axes.specialize(self.remains()), padding: SizeBox::zero(), - shrink_to_fit, }]; for space in &self.ctx.spaces[self.next_space()..] { - spaces.push(space.usable_space(shrink_to_fit)); + spaces.push(space.usable_space()); } spaces diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 89e47c919..4742de23c 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -10,7 +10,6 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { ctx: LayoutContext<'a, 'p>, - stack: StackLayouter, flex: FlexLayouter, style: Cow<'a, TextStyle>, } @@ -20,14 +19,11 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { ctx, - stack: StackLayouter::new(StackContext { - spaces: ctx.spaces, - axes: ctx.axes, - }), flex: FlexLayouter::new(FlexContext { flex_spacing: flex_spacing(&ctx.style), - spaces: ctx.spaces.iter().map(|space| space.usable_space(true)).collect(), + spaces: ctx.spaces, axes: ctx.axes, + shrink_to_fit: ctx.shrink_to_fit, }), style: Cow::Borrowed(ctx.style), } @@ -45,13 +41,14 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } Node::Space => { - if !self.flex.is_empty() { - self.flex.add_space(self.style.word_spacing * self.style.font_size); + if !self.flex.box_is_empty() { + let space = self.style.word_spacing * self.style.font_size; + self.flex.add_primary_space(space); } } Node::Newline => { - if !self.flex.is_empty() { - self.finish_paragraph()?; + if !self.flex.box_is_empty() { + self.break_paragraph()?; } } @@ -68,15 +65,9 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Layout a function. fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { - // Finish the current flex layout on a copy to find out how - // much space would be remaining if we finished. - let mut lookahead = self.stack.clone(); - lookahead.add_multiple(self.flex.clone().finish()?)?; - let spaces = lookahead.remaining(true); - let commands = func.body.val.layout(LayoutContext { style: &self.style, - spaces, + spaces: self.flex.remaining()?, .. self.ctx })?; @@ -94,16 +85,15 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::Add(layout) => self.flex.add(layout), Command::AddMultiple(layouts) => self.flex.add_multiple(layouts), - Command::BreakFlex => self.flex.add_break(), - Command::FinishFlex => self.finish_paragraph()?, - Command::BreakStack => self.finish_layout()?, + Command::FinishRun => self.flex.add_run_break(), + Command::FinishBox => self.flex.finish_box()?, + Command::FinishLayout => self.flex.finish_layout(true)?, + + Command::BreakParagraph => self.break_paragraph()?, Command::SetStyle(style) => *self.style.to_mut() = style, Command::SetAxes(axes) => { self.flex.set_axes(axes); - if axes.secondary != self.ctx.axes.secondary { - self.stack.set_axes(axes); - } self.ctx.axes = axes; } } @@ -113,43 +103,15 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Finish the layout. fn finish(mut self) -> LayoutResult { - self.finish_flex()?; - Ok(self.stack.finish()) - } - - /// Finish the current stack layout with a hard break. - fn finish_layout(&mut self) -> LayoutResult<()> { - self.finish_flex()?; - self.stack.add_break(true); - self.start_new_flex(); - Ok(()) + self.flex.finish() } /// Finish the current flex layout and add space after it. - fn finish_paragraph(&mut self) -> LayoutResult<()> { - self.finish_flex()?; - self.stack.add_space(paragraph_spacing(&self.style)); - self.start_new_flex(); + fn break_paragraph(&mut self) -> LayoutResult<()> { + self.flex.finish_box()?; + self.flex.add_secondary_space(paragraph_spacing(&self.style))?; Ok(()) } - - /// Finish the current flex layout and add it the stack. - fn finish_flex(&mut self) -> LayoutResult<()> { - if !self.flex.is_empty() { - let layouts = self.flex.finish()?; - self.stack.add_multiple(layouts)?; - } - Ok(()) - } - - /// Start a new flex layout. - fn start_new_flex(&mut self) { - self.flex = FlexLayouter::new(FlexContext { - flex_spacing: flex_spacing(&self.style), - spaces: self.stack.remaining(true), - axes: self.ctx.axes, - }); - } } fn flex_spacing(style: &TextStyle) -> Size { diff --git a/src/lib.rs b/src/lib.rs index 43f35511c..d24941665 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,12 +102,12 @@ impl<'p> Typesetter<'p> { 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::Origin), secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Origin), }, + shrink_to_fit: false, }, )?) }