diff --git a/src/layout/actions.rs b/src/layout/actions.rs index ed3bc1826..c668cf753 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -133,9 +133,10 @@ impl LayoutActionList { self.actions.is_empty() } - /// Return the list of actions as a vector. - pub fn into_vec(self) -> Vec { - self.actions + /// Return the list of actions as a vector, leaving an empty + /// vector in its position. + pub fn to_vec(&mut self) -> Vec { + std::mem::replace(&mut self.actions, vec![]) } /// Append a cached move action if one is cached. diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 64d48c7d8..2f219640f 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -2,13 +2,13 @@ use super::*; #[derive(Debug, Clone)] pub struct FlexLayouter { - stack: StackLayouter, - axes: LayoutAxes, flex_spacing: Size, + stack: StackLayouter, units: Vec, line: FlexLine, + part: PartialLine, } #[derive(Debug, Clone)] @@ -24,7 +24,6 @@ struct FlexLine { usable: Size, actions: LayoutActionList, combined_dimensions: Size2D, - part: PartialLine, } impl FlexLine { @@ -33,7 +32,6 @@ impl FlexLine { usable, actions: LayoutActionList::new(), combined_dimensions: Size2D::zero(), - part: PartialLine::new(usable), } } } @@ -44,6 +42,7 @@ struct PartialLine { content: Vec<(Size, Layout)>, dimensions: Size2D, space: Option, + last_was_space: bool, } impl PartialLine { @@ -53,6 +52,7 @@ impl PartialLine { content: vec![], dimensions: Size2D::zero(), space: None, + last_was_space: false, } } } @@ -80,13 +80,13 @@ impl FlexLayouter { let usable = stack.primary_usable(); FlexLayouter { - stack, - axes: ctx.axes, flex_spacing: ctx.flex_spacing, + stack, units: vec![], - line: FlexLine::new(usable) + line: FlexLine::new(usable), + part: PartialLine::new(usable), } } @@ -105,7 +105,7 @@ impl FlexLayouter { } pub fn add_primary_space(&mut self, space: Size, soft: bool) { - self.units.push(FlexUnit::Space(space, soft)); + self.units.push(FlexUnit::Space(space, soft)) } pub fn add_secondary_space(&mut self, space: Size, soft: bool) -> LayoutResult<()> { @@ -122,7 +122,7 @@ impl FlexLayouter { 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_run(); + self.start_line(); } else { self.stack.set_spaces(spaces, false); } @@ -142,7 +142,6 @@ impl FlexLayouter { Ok((flex_spaces, Some(stack_spaces))) } - } pub fn run_is_empty(&self) -> bool { @@ -162,7 +161,9 @@ impl FlexLayouter { if !self.run_is_empty() { self.finish_run()?; } - Ok(self.stack.finish_space(hard)) + + self.stack.finish_space(hard); + Ok(self.start_line()) } pub fn finish_run(&mut self) -> LayoutResult { @@ -172,7 +173,7 @@ impl FlexLayouter { FlexUnit::Boxed(boxed) => self.layout_box(boxed)?, FlexUnit::Space(space, soft) => self.layout_space(space, soft), FlexUnit::SetAxes(axes) => self.layout_set_axes(axes), - FlexUnit::Break => self.layout_break(), + FlexUnit::Break => { self.finish_line()?; }, } } @@ -183,77 +184,91 @@ impl FlexLayouter { self.finish_partial_line(); self.stack.add(Layout { - dimensions: self.axes.specialize(self.line.combined_dimensions), - actions: self.line.actions.into_vec(), + dimensions: self.axes.specialize(Size2D { + x: self.line.usable, + y: self.line.combined_dimensions.y + self.flex_spacing, + }), + actions: self.line.actions.to_vec(), debug_render: false, })?; let remaining = self.axes.specialize(Size2D { - x: self.line.usable - self.line.combined_dimensions.x, + x: self.part.usable + - self.part.dimensions.x + - self.part.space.unwrap_or(Size::zero()), y: self.line.combined_dimensions.y, }); - self.line = FlexLine::new(self.stack.primary_usable()); + self.start_line(); Ok(remaining) } - fn finish_partial_line(&mut self) { - let part = self.line.part; + fn start_line(&mut self) { + let usable = self.stack.primary_usable(); + self.line = FlexLine::new(usable); + self.part = PartialLine::new(usable); + } + fn finish_partial_line(&mut self) { let factor = self.axes.primary.axis.factor(); let anchor = self.axes.primary.anchor(self.line.usable) - - self.axes.primary.anchor(part.dimensions.x); + - self.axes.primary.anchor(self.part.dimensions.x); - for (offset, layout) in part.content { + for (offset, layout) in self.part.content.drain(..) { let pos = self.axes.specialize(Size2D::with_x(anchor + factor * offset)); self.line.actions.add_layout(pos, layout); } - self.line.combined_dimensions.x.max_eq(part.dimensions.x); - self.line.part = PartialLine::new(self.line.usable - part.dimensions.x); - } - - fn start_run(&mut self) { - let usable = self.stack.primary_usable(); - self.line = FlexLine::new(usable); + self.line.combined_dimensions.x = anchor + factor * self.part.dimensions.x; + self.line.combined_dimensions.y.max_eq(self.part.dimensions.x); } fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> { let size = self.axes.generalize(boxed.dimensions); - if size.x > self.size_left() { - self.space = None; + let new_dimension = self.part.dimensions.x + + self.part.space.unwrap_or(Size::zero()); + + if new_dimension > self.part.usable { self.finish_line()?; - while size.x > self.usable { + while size.x > self.line.usable { if self.stack.space_is_last() { - Err(LayoutError::NotEnoughSpace("cannot fix box into flex run"))?; + Err(LayoutError::NotEnoughSpace("failed to add box to flex run"))?; } - self.finish_space(true); - self.total_usable = self.stack.primary_usable(); - self.usable = self.total_usable; + self.stack.finish_space(true); } } - self.layout_space(); + if let Some(space) = self.part.space.take() { + self.layout_space(space, false); + } - let offset = self.run.size.x; - self.run.content.push((offset, boxed)); + let offset = self.part.dimensions.x; + self.part.content.push((offset, boxed)); - self.run.size.x += size.x; - self.run.size.y = crate::size::max(self.run.size.y, size.y); + self.part.dimensions.x += size.x; + self.part.dimensions.y.max_eq(size.y); + self.part.last_was_space = false; Ok(()) } fn layout_space(&mut self, space: Size, soft: bool) { - 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; + if soft { + if !self.part.last_was_space { + self.part.space = Some(space); } + } else { + if self.part.dimensions.x + space > self.part.usable { + self.part.dimensions.x = self.part.usable; + } else { + self.part.dimensions.x += space; + } + self.part.last_was_space = true; } } @@ -261,19 +276,17 @@ impl FlexLayouter { if axes.primary != self.axes.primary { self.finish_partial_line(); - // self.usable = match axes.primary.alignment { - // Alignment::Origin => - // if self.max_extent == Size::zero() { - // self.total_usable - // } else { - // Size::zero() - // }, - // Alignment::Center => crate::size::max( - // self.total_usable - 2 * self.max_extent, - // Size::zero() - // ), - // Alignment::End => self.total_usable - self.max_extent, - // }; + let extent = self.line.combined_dimensions.x; + let usable = self.line.usable; + + let new_usable = match axes.primary.alignment { + Alignment::Origin if extent == Size::zero() => usable, + Alignment::Center if extent < usable / 2 => usable - 2 * extent, + Alignment::End => usable - extent, + _ => Size::zero(), + }; + + self.part = PartialLine::new(new_usable); } if axes.secondary != self.axes.secondary { @@ -282,13 +295,4 @@ impl FlexLayouter { self.axes = axes; } - - fn layout_break(&mut self) { - - } - - fn size_left(&self) -> Size { - let space = self.space.unwrap_or(Size::zero()); - self.usable - (self.run.size.x + space) - } } diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index cdc355800..a7473b6f0 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -6,22 +6,38 @@ pub struct StackLayouter { ctx: StackContext, layouts: MultiLayout, - space: usize, - hard: bool, - actions: LayoutActionList, - combined_dimensions: Size2D, // <- specialized - + 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, // <- specialized + origin: Size2D, usable: Size2D, - anchor: Size2D, // <- generic + anchor: Size2D, factor: i32, - dimensions: Size2D, // <- generic + dimensions: Size2D, space: Option, + last_was_space: bool, } impl Subspace { @@ -33,6 +49,7 @@ impl Subspace { factor: axes.secondary.axis.factor(), dimensions: Size2D::zero(), space: None, + last_was_space: false, } } } @@ -56,29 +73,30 @@ impl StackLayouter { StackLayouter { ctx, layouts: MultiLayout::new(), - - space: 0, - hard: true, - actions: LayoutActionList::new(), - combined_dimensions: Size2D::zero(), - + space: Space::new(0, true), sub: Subspace::new(space.start(), space.usable(), axes), } } pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { - self.layout_space(); + if let Some(space) = self.sub.space.take() { + self.add_space(space, false); + } let size = self.ctx.axes.generalize(layout.dimensions); - let mut new_dimensions = merge(self.sub.dimensions, size); + + 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_empty() { - Err(LayoutError::NotEnoughSpace("cannot fit box into stack"))?; + if self.space_is_last() && self.space_is_empty() { + Err(LayoutError::NotEnoughSpace("failed to add box to stack"))?; } self.finish_space(true); - new_dimensions = merge(self.sub.dimensions, size); + new_dimensions = size; } let offset = self.sub.dimensions.y; @@ -86,11 +104,12 @@ impl StackLayouter { let pos = self.sub.origin + self.ctx.axes.specialize( (self.sub.anchor - anchor) - + Size2D::with_y(self.combined_dimensions.y + self.sub.factor * offset) + + Size2D::with_y(self.space.combined_dimensions.y + self.sub.factor * offset) ); - self.actions.add_layout(pos, layout); + self.space.actions.add_layout(pos, layout); self.sub.dimensions = new_dimensions; + self.sub.last_was_space = false; Ok(()) } @@ -103,24 +122,36 @@ impl StackLayouter { } pub fn add_space(&mut self, space: Size, soft: bool) { - self.sub.space = Some(space); - if !soft { - self.layout_space(); + if soft { + if !self.sub.last_was_space { + self.sub.space = Some(space); + } + } else { + if self.sub.dimensions.y + space > self.sub.usable.y { + self.sub.dimensions.y = self.sub.usable.y; + self.finish_space(false); + } else { + self.sub.dimensions.y += space; + } + self.sub.last_was_space = true; } } pub fn set_axes(&mut self, axes: LayoutAxes) { if axes != self.ctx.axes { - self.finish_subspace(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.hard); + self.start_space(0, self.space.hard); } else { - self.ctx.spaces.truncate(self.space + 1); + self.ctx.spaces.truncate(self.space.index + 1); self.ctx.spaces.extend(spaces); } } @@ -143,34 +174,33 @@ impl StackLayouter { } pub fn space_is_empty(&self) -> bool { - self.combined_dimensions == Size2D::zero() + self.space.combined_dimensions == Size2D::zero() + && self.space.actions.is_empty() && self.sub.dimensions == Size2D::zero() - && self.actions.is_empty() } pub fn space_is_last(&self) -> bool { - self.space == self.ctx.spaces.len() - 1 + self.space.index == self.ctx.spaces.len() - 1 } pub fn finish(mut self) -> MultiLayout { - if self.hard || !self.space_is_empty() { + 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(self.ctx.axes); + self.finish_subspace(); - let space = self.ctx.spaces[self.space]; - let actions = std::mem::replace(&mut self.actions, LayoutActionList::new()); + let space = self.ctx.spaces[self.space.index]; self.layouts.add(Layout { dimensions: match self.ctx.expand { - true => self.combined_dimensions.padded(space.padding), + true => self.space.combined_dimensions.padded(space.padding), false => space.dimensions, }, - actions: actions.into_vec(), + actions: self.space.actions.to_vec(), debug_render: true, }); @@ -178,19 +208,17 @@ impl StackLayouter { } fn start_space(&mut self, space: usize, hard: bool) { - self.space = space; - let space = self.ctx.spaces[space]; + self.space = Space::new(space, hard); - self.hard = hard; - self.combined_dimensions = Size2D::zero(); + let space = self.ctx.spaces[space]; self.sub = Subspace::new(space.start(), space.usable(), self.ctx.axes); } fn next_space(&self) -> usize { - (self.space + 1).min(self.ctx.spaces.len() - 1) + (self.space.index + 1).min(self.ctx.spaces.len() - 1) } - fn finish_subspace(&mut self, new_axes: LayoutAxes) { + fn finish_subspace(&mut self) { if self.ctx.axes.primary.needs_expansion() { self.sub.dimensions.x = self.sub.usable.x; } @@ -199,46 +227,23 @@ impl StackLayouter { self.sub.dimensions.y = self.sub.usable.y; } - let (new_origin, new_usable) = self.remaining_subspace(); - + let space = self.ctx.spaces[self.space.index]; let origin = self.sub.origin; let dimensions = self.ctx.axes.specialize(self.sub.dimensions); - let space = self.ctx.spaces[self.space]; - self.combined_dimensions.max_eq(origin - space.start() + dimensions); - - self.ctx.axes = new_axes; - self.sub = Subspace::new(new_origin, new_usable, new_axes); + self.space.combined_dimensions.max_eq(origin - space.start() + dimensions); } fn remaining_subspace(&self) -> (Size2D, Size2D) { - let used = self.ctx.axes.specialize(self.sub.usable); - let dimensions = self.ctx.axes.specialize(self.sub.dimensions); + 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, }); - let new_origin = self.sub.origin - + Size2D::with_y(self.ctx.axes.specialize(self.sub.dimensions).y); - (new_origin, new_usable) } - - fn layout_space(&mut self) { - if let Some(space) = self.sub.space.take() { - if self.sub.dimensions.y + space > self.sub.usable.y { - self.finish_space(false); - } else { - self.sub.dimensions.y += space; - } - } - } -} - -fn merge(a: Size2D, b: Size2D) -> Size2D { - Size2D { - x: crate::size::max(a.x, b.x), - y: a.y + b.y - } } diff --git a/src/layout/text.rs b/src/layout/text.rs index 88d83ab71..fc7cd3859 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -72,7 +72,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { Ok(Layout { dimensions: Size2D::new(self.width, self.ctx.style.font_size), - actions: self.actions.into_vec(), + actions: self.actions.to_vec(), debug_render: false, }) } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index d9f156184..2edec6316 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,7 +1,6 @@ use super::*; use smallvec::smallvec; -/// Layouts syntax trees into boxes. pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { let mut layouter = TreeLayouter::new(ctx); layouter.layout(tree)?; @@ -16,7 +15,7 @@ struct TreeLayouter<'a, 'p> { } impl<'a, 'p> TreeLayouter<'a, 'p> { - /// Create a new layouter. + /// Create a new syntax tree layouter. fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { flex: FlexLayouter::new(FlexContext { @@ -30,28 +29,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } } - /// Layout a syntax tree. fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> { for node in &tree.nodes { match &node.val { - Node::Text(text) => { - self.flex.add(layout_text(text, TextContext { - loader: &self.ctx.loader, - style: &self.style, - })?); - } + Node::Text(text) => self.layout_text(text)?, - Node::Space => { - if !self.flex.run_is_empty() && !self.flex.run_last_is_space() { - let space = self.style.word_spacing * self.style.font_size; - self.flex.add_primary_space(space, true); - } - } - Node::Newline => { - if !self.flex.run_is_empty() { - self.break_paragraph()?; - } - } + Node::Space => self.layout_space(), + Node::Newline => self.layout_paragraph()?, Node::ToggleItalics => self.style.toggle_class(FontClass::Italic), Node::ToggleBold => self.style.toggle_class(FontClass::Bold), @@ -64,27 +48,53 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Ok(()) } - /// Layout a function. + fn layout_text(&mut self, text: &str) -> LayoutResult<()> { + let layout = layout_text(text, TextContext { + loader: &self.ctx.loader, + style: &self.style, + })?; + + Ok(self.flex.add(layout)) + } + + fn layout_space(&mut self) { + if !self.flex.run_is_empty() { + self.flex.add_primary_space(word_spacing(&self.style), true); + } + } + + fn layout_paragraph(&mut self) -> LayoutResult<()> { + if !self.flex.run_is_empty() { + self.flex.add_secondary_space(paragraph_spacing(&self.style), true)?; + } + Ok(()) + } + fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let (first, second) = self.flex.remaining()?; - let ctx = |spaces| LayoutContext { - loader: self.ctx.loader, - top_level: false, - text_style: &self.style, - page_style: self.ctx.page_style, - spaces, - axes: self.ctx.axes.expanding(false), - expand: false, + let ctx = |spaces| { + LayoutContext { + loader: self.ctx.loader, + top_level: false, + text_style: &self.style, + page_style: self.ctx.page_style, + spaces, + axes: self.ctx.axes.expanding(false), + 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))?, - _ => Err(e)?, - }, + Err(e) => { + match (e, second) { + (LayoutError::NotEnoughSpace(_), Some(space)) => { + func.body.val.layout(ctx(space))? + } + (e, _) => Err(e)?, + } + } }; for command in commands { @@ -108,7 +118,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::FinishRun => { self.flex.finish_run()?; }, Command::FinishSpace => self.flex.finish_space(true)?, - Command::BreakParagraph => self.break_paragraph()?, + Command::BreakParagraph => self.layout_paragraph()?, Command::SetTextStyle(style) => self.style = style, Command::SetPageStyle(style) => { @@ -134,15 +144,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Ok(()) } - /// Finish the layout. fn finish(self) -> LayoutResult { self.flex.finish() } +} - /// Finish the current flex layout and add space after it. - fn break_paragraph(&mut self) -> LayoutResult<()> { - self.flex.add_secondary_space(paragraph_spacing(&self.style), true) - } +fn word_spacing(style: &TextStyle) -> Size { + style.word_spacing * style.font_size } fn flex_spacing(style: &TextStyle) -> Size {