diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 4985fed23..64d48c7d8 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -43,7 +43,7 @@ struct PartialLine { usable: Size, content: Vec<(Size, Layout)>, dimensions: Size2D, - space: Option<(Size, bool)>, + space: Option, } impl PartialLine { @@ -64,7 +64,7 @@ impl PartialLine { pub struct FlexContext { pub spaces: LayoutSpaces, pub axes: LayoutAxes, - pub shrink_to_fit: bool, + pub expand: bool, pub flex_spacing: Size, } @@ -74,10 +74,11 @@ impl FlexLayouter { let stack = StackLayouter::new(StackContext { spaces: ctx.spaces, axes: ctx.axes, - shrink_to_fit: ctx.shrink_to_fit, + expand: ctx.expand, }); let usable = stack.primary_usable(); + FlexLayouter { stack, @@ -107,9 +108,11 @@ impl FlexLayouter { self.units.push(FlexUnit::Space(space, soft)); } - pub fn add_secondary_space(&mut self, space: Size) -> LayoutResult<()> { - self.finish_run()?; - Ok(self.stack.add_space(space)) + pub fn add_secondary_space(&mut self, space: Size, soft: bool) -> LayoutResult<()> { + if !self.run_is_empty() { + self.finish_run()?; + } + Ok(self.stack.add_space(space, soft)) } pub fn set_axes(&mut self, axes: LayoutAxes) { @@ -120,28 +123,26 @@ impl FlexLayouter { if replace_empty && self.run_is_empty() && self.stack.space_is_empty() { self.stack.set_spaces(spaces, true); self.start_run(); - - // let usable = self.stack.primary_usable(); - // self.line = FlexLine::new(usable); - - // // self.total_usable = self.stack.primary_usable(); - // // self.usable = self.total_usable; - // // self.space = None; } else { self.stack.set_spaces(spaces, false); } } - pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> { - let mut future = self.clone(); - future.finish_run()?; + 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 = future.last_run_remaining.x; - flex_spaces[0].dimensions.y += future.last_run_remaining.y; + 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))) + } - Ok((flex_spaces, stack_spaces)) } pub fn run_is_empty(&self) -> bool { @@ -149,7 +150,7 @@ impl FlexLayouter { } pub fn run_last_is_space(&self) -> bool { - matches!(self.units.last(), Some(FlexUnit::Space(_))) + matches!(self.units.last(), Some(FlexUnit::Space(_, _))) } pub fn finish(mut self) -> LayoutResult { @@ -158,32 +159,65 @@ impl FlexLayouter { } pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { - self.finish_run()?; + if !self.run_is_empty() { + self.finish_run()?; + } Ok(self.stack.finish_space(hard)) } - pub fn finish_run(&mut self) -> LayoutResult<()> { + pub fn finish_run(&mut self) -> LayoutResult { let units = std::mem::replace(&mut self.units, vec![]); for unit in units { match unit { FlexUnit::Boxed(boxed) => self.layout_box(boxed)?, - FlexUnit::Space(space, soft) => { - self.layout_space(); - self.space = Some(space); - } - - FlexUnit::Break => { - self.space = None; - self.finish_line()?; - }, - + FlexUnit::Space(space, soft) => self.layout_space(space, soft), FlexUnit::SetAxes(axes) => self.layout_set_axes(axes), + FlexUnit::Break => self.layout_break(), } } - self.finish_line()?; + self.finish_line() + } - Ok(()) + fn finish_line(&mut self) -> LayoutResult { + self.finish_partial_line(); + + self.stack.add(Layout { + dimensions: self.axes.specialize(self.line.combined_dimensions), + actions: self.line.actions.into_vec(), + debug_render: false, + })?; + + let remaining = self.axes.specialize(Size2D { + x: self.line.usable - self.line.combined_dimensions.x, + y: self.line.combined_dimensions.y, + }); + + self.line = FlexLine::new(self.stack.primary_usable()); + + Ok(remaining) + } + + fn finish_partial_line(&mut self) { + let part = self.line.part; + + let factor = self.axes.primary.axis.factor(); + let anchor = + self.axes.primary.anchor(self.line.usable) + - self.axes.primary.anchor(part.dimensions.x); + + for (offset, layout) in part.content { + 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); } fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> { @@ -215,7 +249,7 @@ impl FlexLayouter { Ok(()) } - fn layout_space(&mut self) { + 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; @@ -227,19 +261,19 @@ 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, - }; + // 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, + // }; } if axes.secondary != self.axes.secondary { @@ -249,57 +283,8 @@ impl FlexLayouter { self.axes = axes; } - fn finish_line(&mut self) -> LayoutResult<()> { - self.finish_partial_line(); + fn layout_break(&mut self) { - if self.merged_dimensions.y == Size::zero() { - return Ok(()); - } - - let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new()); - self.stack.add(Layout { - dimensions: self.axes.specialize(self.merged_dimensions), - actions: actions.into_vec(), - debug_render: false, - })?; - - self.merged_dimensions = Size2D::zero(); - self.max_extent = Size::zero(); - self.usable = self.total_usable; - - Ok(()) - } - - fn finish_partial_line(&mut self) { - if self.run.content.is_empty() { - return; - } - - 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.axes.specialize(general_position); - - self.merged_actions.add_layout(position, layout); - } - - 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.flex_spacing, - ); - - self.last_run_remaining = Size2D::new(self.size_left(), self.merged_dimensions.y); - self.run.size = Size2D::zero(); } fn size_left(&self) -> Size { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 13b635681..2c725e1d8 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -147,9 +147,9 @@ 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, + /// Whether layouts should expand to the full dimensions of the space + /// they lie on or whether should tightly fit the content. + pub expand: bool, } /// A possibly stack-allocated vector of layout spaces. @@ -219,6 +219,14 @@ impl LayoutAxes { pub fn anchor(&self, space: Size2D) -> Size2D { Size2D::new(self.primary.anchor(space.x), self.secondary.anchor(space.y)) } + + /// This axes with `expand` set to the given value for both axes. + pub fn expanding(&self, expand: bool) -> LayoutAxes { + LayoutAxes { + primary: self.primary.expanding(expand), + secondary: self.secondary.expanding(expand), + } + } } /// An axis with an alignment. @@ -226,17 +234,13 @@ impl LayoutAxes { pub struct AlignedAxis { pub axis: Axis, pub alignment: Alignment, + pub expand: bool, } impl AlignedAxis { - /// Returns an aligned axis if the alignment is compatible with the axis. - pub fn new(axis: Axis, alignment: Alignment) -> AlignedAxis { - AlignedAxis { axis, alignment } - } - - /// The pair of axis and alignment. - pub fn pair(&self) -> (Axis, Alignment) { - (self.axis, self.alignment) + /// Creates an aligned axis from its three components. + pub fn new(axis: Axis, alignment: Alignment, expand: bool) -> AlignedAxis { + AlignedAxis { axis, alignment, expand } } /// The position of the anchor specified by this axis on the given line. @@ -248,6 +252,16 @@ impl AlignedAxis { (true, End) | (false, Origin) => line, } } + + /// This axis with `expand` set to the given value. + pub fn expanding(&self, expand: bool) -> AlignedAxis { + AlignedAxis { expand, ..*self } + } + + /// Whether this axis needs expansion. + pub fn needs_expansion(&self) -> bool { + self.expand || self.alignment != Alignment::Origin + } } /// Where to put content. diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index b2d073828..cdc355800 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -8,28 +8,31 @@ pub struct StackLayouter { space: usize, hard: bool, - start: Size2D, actions: LayoutActionList, - combined_dimensions: Size2D, + combined_dimensions: Size2D, // <- specialized sub: Subspace, } #[derive(Debug, Clone)] struct Subspace { + origin: Size2D, // <- specialized usable: Size2D, - anchor: Size2D, + anchor: Size2D, // <- generic factor: i32, - dimensions: Size2D, + dimensions: Size2D, // <- generic + space: Option, } impl Subspace { - fn new(usable: Size2D, axes: LayoutAxes) -> Subspace { + fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace { Subspace { - usable, + origin, + usable: axes.generalize(usable), anchor: axes.anchor(usable), factor: axes.secondary.axis.factor(), dimensions: Size2D::zero(), + space: None, } } } @@ -41,7 +44,7 @@ impl Subspace { pub struct StackContext { pub spaces: LayoutSpaces, pub axes: LayoutAxes, - pub shrink_to_fit: bool, + pub expand: bool, } impl StackLayouter { @@ -49,7 +52,6 @@ impl StackLayouter { pub fn new(ctx: StackContext) -> StackLayouter { let axes = ctx.axes; let space = ctx.spaces[0]; - let usable = ctx.axes.generalize(space.usable()); StackLayouter { ctx, @@ -57,15 +59,16 @@ impl StackLayouter { space: 0, hard: true, - start: space.start(), actions: LayoutActionList::new(), combined_dimensions: Size2D::zero(), - sub: Subspace::new(usable, axes), + sub: Subspace::new(space.start(), space.usable(), axes), } } pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { + self.layout_space(); + let size = self.ctx.axes.generalize(layout.dimensions); let mut new_dimensions = merge(self.sub.dimensions, size); @@ -81,10 +84,9 @@ impl StackLayouter { let offset = self.sub.dimensions.y; let anchor = self.ctx.axes.anchor(size); - let pos = self.ctx.axes.specialize( - self.start - + (self.sub.anchor - anchor) - + Size2D::with_y(self.combined_dimensions.y + self.sub.factor * offset) + let pos = self.sub.origin + self.ctx.axes.specialize( + (self.sub.anchor - anchor) + + Size2D::with_y(self.combined_dimensions.y + self.sub.factor * offset) ); self.actions.add_layout(pos, layout); @@ -100,19 +102,16 @@ impl StackLayouter { Ok(()) } - pub fn add_space(&mut self, space: Size) { - if self.sub.dimensions.y + space > self.sub.usable.y { - self.finish_space(false); - } else { - self.sub.dimensions.y += space; + pub fn add_space(&mut self, space: Size, soft: bool) { + self.sub.space = Some(space); + if !soft { + self.layout_space(); } } pub fn set_axes(&mut self, axes: LayoutAxes) { if axes != self.ctx.axes { - self.finish_subspace(); - self.ctx.axes = axes; - self.sub = Subspace::new(self.remaining_subspace(), axes); + self.finish_subspace(axes); } } @@ -128,7 +127,7 @@ impl StackLayouter { pub fn remaining(&self) -> LayoutSpaces { let mut spaces = smallvec![LayoutSpace { - dimensions: self.ctx.axes.specialize(self.remaining_subspace()), + dimensions: self.remaining_subspace().1, padding: SizeBox::zero(), }]; @@ -161,13 +160,13 @@ impl StackLayouter { } pub fn finish_space(&mut self, hard: bool) { - self.finish_subspace(); + self.finish_subspace(self.ctx.axes); let space = self.ctx.spaces[self.space]; let actions = std::mem::replace(&mut self.actions, LayoutActionList::new()); self.layouts.add(Layout { - dimensions: match self.ctx.shrink_to_fit { + dimensions: match self.ctx.expand { true => self.combined_dimensions.padded(space.padding), false => space.dimensions, }, @@ -180,27 +179,60 @@ impl StackLayouter { fn start_space(&mut self, space: usize, hard: bool) { self.space = space; - let space = self.ctx.spaces[space]; - let usable = self.ctx.axes.generalize(space.usable()); self.hard = hard; - self.start = space.start(); self.combined_dimensions = Size2D::zero(); - self.sub = Subspace::new(usable, self.ctx.axes); + 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) } - fn finish_subspace(&mut self) { - let dims = self.ctx.axes.specialize(self.sub.dimensions); - self.combined_dimensions = merge(self.combined_dimensions, dims); + fn finish_subspace(&mut self, new_axes: LayoutAxes) { + 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 (new_origin, new_usable) = self.remaining_subspace(); + + 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); } - fn remaining_subspace(&self) -> Size2D { - Size2D::new(self.sub.usable.x, self.sub.usable.y - self.sub.dimensions.y) + 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_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; + } + } } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index f75265005..d9f156184 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -23,7 +23,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { flex_spacing: flex_spacing(&ctx.text_style), spaces: ctx.spaces.clone(), axes: ctx.axes, - shrink_to_fit: ctx.shrink_to_fit, + expand: ctx.expand, }), style: ctx.text_style.clone(), ctx, @@ -66,24 +66,25 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Layout a function. fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { - let (flex_spaces, stack_spaces) = self.flex.remaining()?; + 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, - shrink_to_fit: true, - .. self.ctx + axes: self.ctx.axes.expanding(false), + expand: false, }; - // Try putting it in the flex space first, but if that is not enough - // space, use the other space. - let commands = match func.body.val.layout(ctx(flex_spaces)) { + let commands = match func.body.val.layout(ctx(first)) { Ok(c) => c, - Err(LayoutError::NotEnoughSpace(_)) => { - func.body.val.layout(ctx(stack_spaces))? + Err(e) => match (e, second) { + (LayoutError::NotEnoughSpace(_), Some(space)) + => func.body.val.layout(ctx(space))?, + _ => Err(e)?, }, - e => e?, }; for command in commands { @@ -101,10 +102,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::AddMultiple(layouts) => self.flex.add_multiple(layouts), Command::AddPrimarySpace(space) => self.flex.add_primary_space(space, false), - Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space)?, + Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space, false)?, Command::FinishLine => self.flex.add_break(), - Command::FinishRun => self.flex.finish_run()?, + Command::FinishRun => { self.flex.finish_run()?; }, Command::FinishSpace => self.flex.finish_space(true)?, Command::BreakParagraph => self.break_paragraph()?, @@ -140,7 +141,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// 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)) + self.flex.add_secondary_space(paragraph_spacing(&self.style), true) } } diff --git a/src/lib.rs b/src/lib.rs index c6a21c511..a5551c778 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,10 +106,10 @@ impl<'p> Typesetter<'p> { padding: self.page_style.margins, }], axes: LayoutAxes { - primary: AlignedAxis::new(Axis::LeftToRight, Alignment::Origin), - secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Origin), + primary: AlignedAxis::new(Axis::LeftToRight, Alignment::Origin, false), + secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Origin, false), }, - shrink_to_fit: false, + expand: true, }, )?) } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 21b927b8f..010594847 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -19,7 +19,7 @@ pub struct ParagraphBreak; function! { data: ParagraphBreak, parse: plain, - layout(_, _) { Ok(commands![FinishRun]) } + layout(_, _) { Ok(commands![BreakParagraph]) } } macro_rules! space_func { diff --git a/src/size.rs b/src/size.rs index 5c87ad370..fac316259 100644 --- a/src/size.rs +++ b/src/size.rs @@ -95,6 +95,12 @@ impl Size { pub fn to_inches(&self) -> f32 { self.points * 0.0138889 } + + /// Set this size to the maximum of itself and the other size. + #[inline] + pub fn max_eq(&mut self, other: Size) { + *self = max(*self, other); + } } impl Size2D { @@ -146,6 +152,14 @@ impl Size2D { pub fn fits(&self, other: Size2D) -> bool { self.x >= other.x && self.y >= other.y } + + /// Set this size to the maximum of itself and the other size + /// (for both dimensions). + #[inline] + pub fn max_eq(&mut self, other: Size2D) { + self.x.max_eq(other.x); + self.y.max_eq(other.y); + } } impl SizeBox {