From e87a34a4d0bf967427e2443f9f48026d09ccd5db Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 16 Oct 2019 22:32:40 +0200 Subject: [PATCH] =?UTF-8?q?Rearrange=20layouting=20contexts=20=E2=99=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/flex.rs | 39 +++++++++++++++++++++++------ src/layout/mod.rs | 27 +++++++++++++++----- src/layout/stacked.rs | 55 ++++++++++++++++++++++++++++++----------- src/layout/text.rs | 14 +++++++++-- src/layout/tree.rs | 29 ++++++---------------- src/lib.rs | 6 ++--- src/library/align.rs | 5 +--- tests/layouts/align.typ | 3 +-- 8 files changed, 119 insertions(+), 59 deletions(-) diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 80cc2074b..877e0bf37 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -28,12 +28,40 @@ pub struct FlexLayouter { } /// The context for flex layouting. +/// +/// See [`LayoutContext`] for details about the fields. #[derive(Debug, Copy, Clone)] pub struct FlexContext { - pub space: LayoutSpace, /// The spacing between two lines of boxes. pub flex_spacing: Size, - pub extra_space: Option, + pub alignment: Alignment, + pub space: LayoutSpace, + pub followup_spaces: Option, + pub shrink_to_fit: bool, +} + +macro_rules! reuse { + ($ctx:expr, $flex_spacing:expr) => { + FlexContext { + flex_spacing: $flex_spacing, + alignment: $ctx.alignment, + space: $ctx.space, + followup_spaces: $ctx.followup_spaces, + shrink_to_fit: $ctx.shrink_to_fit, + } + }; +} + +impl FlexContext { + /// Create a flex context from a generic layout context. + pub fn from_layout_ctx(ctx: LayoutContext, flex_spacing: Size) -> FlexContext { + reuse!(ctx, flex_spacing) + } + + /// Create a flex context from a stack context. + pub fn from_stack_ctx(ctx: StackContext, flex_spacing: Size) -> FlexContext { + reuse!(ctx, flex_spacing) + } } enum FlexUnit { @@ -57,10 +85,7 @@ impl FlexLayouter { ctx, units: vec![], - stack: StackLayouter::new(StackContext { - space: ctx.space, - extra_space: ctx.extra_space, - }), + stack: StackLayouter::new(StackContext::from_flex_ctx(ctx)), usable_width: ctx.space.usable().x, run: FlexRun { @@ -125,7 +150,7 @@ impl FlexLayouter { // If the box does not even fit on its own line, then we try // it in the next space, or we have to give up if there is none. if self.overflows_line(boxed.dimensions.x) { - if self.ctx.extra_space.is_some() { + if self.ctx.followup_spaces.is_some() { self.stack.finish_layout(true)?; return self.layout_box(boxed); } else { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index a5dfa0ad7..fccbe8c8e 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -126,10 +126,22 @@ impl<'a> IntoIterator for &'a MultiLayout { /// The general context for layouting. #[derive(Debug, Copy, Clone)] pub struct LayoutContext<'a, 'p> { + /// The font loader to retrieve fonts from when typesetting text + /// using [`layout_text`]. pub loader: &'a SharedFontLoader<'p>, + /// 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, + /// The alignment to use for the content. + pub alignment: Alignment, + /// The primary space to layout in. pub space: LayoutSpace, - pub extra_space: Option, + /// The additional spaces which are used when the primary space + /// cannot fit the whole content. + pub followup_spaces: Option, + /// Whether to shrink the dimensions to fit the content or the keep the + /// dimensions from the layout spaces. + pub shrink_to_fit: bool, } /// Spacial layouting constraints. @@ -139,11 +151,6 @@ pub struct LayoutSpace { pub dimensions: Size2D, /// Padding that should be respected on each side. pub padding: SizeBox, - /// The alignment to use for the content. - pub alignment: Alignment, - /// Whether to shrink the dimensions to fit the content or the keep the - /// dimensions from the layout space. - pub shrink_to_fit: bool, } impl LayoutSpace { @@ -151,6 +158,14 @@ impl LayoutSpace { pub fn usable(&self) -> Size2D { self.dimensions.unpadded(self.padding) } + + /// A layout without padding and dimensions reduced by the padding. + pub fn usable_space(&self) -> LayoutSpace { + LayoutSpace { + dimensions: self.usable(), + padding: SizeBox::zero(), + } + } } /// Where to align content. diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index 3a29f7229..0b7bfd4f4 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -17,10 +17,37 @@ pub struct StackLayouter { } /// The context for stack layouting. +/// +/// See [`LayoutContext`] for details about the fields. #[derive(Debug, Copy, Clone)] pub struct StackContext { + pub alignment: Alignment, pub space: LayoutSpace, - pub extra_space: Option, + pub followup_spaces: Option, + pub shrink_to_fit: bool, +} + +macro_rules! reuse { + ($ctx:expr) => { + StackContext { + alignment: $ctx.alignment, + space: $ctx.space, + followup_spaces: $ctx.followup_spaces, + shrink_to_fit: $ctx.shrink_to_fit + } + }; +} + +impl StackContext { + /// Create a stack context from a generic layout context. + pub fn from_layout_ctx(ctx: LayoutContext) -> StackContext { + reuse!(ctx) + } + + /// Create a stack context from a flex context. + pub fn from_flex_ctx(ctx: FlexContext) -> StackContext { + reuse!(ctx) + } } impl StackLayouter { @@ -33,8 +60,8 @@ impl StackLayouter { space: ctx.space, usable: ctx.space.usable(), - dimensions: start_dimensions(ctx.space), - cursor: start_cursor(ctx.space), + dimensions: start_dimensions(ctx.alignment, ctx.space), + cursor: start_cursor(ctx.alignment, ctx.space), in_extra_space: false, started: true, } @@ -57,7 +84,7 @@ impl StackLayouter { }; if self.overflows(new_dimensions) { - if self.ctx.extra_space.is_some() && + if self.ctx.followup_spaces.is_some() && !(self.in_extra_space && self.overflows(layout.dimensions)) { self.finish_layout(true)?; @@ -70,7 +97,7 @@ impl StackLayouter { // Determine where to put the box. When we right-align it, we want the // cursor to point to the top-right corner of the box. Therefore, the // position has to be moved to the left by the width of the box. - let position = match self.space.alignment { + let position = match self.ctx.alignment { Alignment::Left => self.cursor, Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x), Alignment::Center => self.cursor - Size2D::with_x(layout.dimensions.x / 2), @@ -101,7 +128,7 @@ impl StackLayouter { let new_dimensions = self.dimensions + Size2D::with_y(space); if self.overflows(new_dimensions) { - if self.ctx.extra_space.is_some() { + if self.ctx.followup_spaces.is_some() { self.finish_layout(false)?; } else { return Err(LayoutError::NotEnoughSpace("cannot fit space into stack")); @@ -133,7 +160,7 @@ impl StackLayouter { pub fn finish_layout(&mut self, start_new_empty: bool) -> LayoutResult<()> { let actions = std::mem::replace(&mut self.actions, LayoutActionList::new()); self.layouts.add(Layout { - dimensions: if self.space.shrink_to_fit { + dimensions: if self.ctx.shrink_to_fit { self.dimensions.padded(self.space.padding) } else { self.space.dimensions @@ -152,12 +179,12 @@ impl StackLayouter { } pub fn start_new_space(&mut self) -> LayoutResult<()> { - if let Some(space) = self.ctx.extra_space { + if let Some(space) = self.ctx.followup_spaces { self.started = true; self.space = space; self.usable = space.usable(); - self.dimensions = start_dimensions(space); - self.cursor = start_cursor(space); + self.dimensions = start_dimensions(self.ctx.alignment, space); + self.cursor = start_cursor(self.ctx.alignment, space); self.in_extra_space = true; Ok(()) } else { @@ -183,19 +210,19 @@ impl StackLayouter { } } -fn start_dimensions(space: LayoutSpace) -> Size2D { - match space.alignment { +fn start_dimensions(alignment: Alignment, space: LayoutSpace) -> Size2D { + match alignment { Alignment::Left => Size2D::zero(), Alignment::Right | Alignment::Center => Size2D::with_x(space.usable().x), } } -fn start_cursor(space: LayoutSpace) -> Size2D { +fn start_cursor(alignment: Alignment, space: LayoutSpace) -> Size2D { Size2D { // If left-align, the cursor points to the top-left corner of // each box. If we right-align, it points to the top-right // corner. - x: match space.alignment { + x: match alignment { Alignment::Left => space.padding.left, Alignment::Right => space.dimensions.x - space.padding.right, Alignment::Center => space.padding.left + (space.usable().x / 2), diff --git a/src/layout/text.rs b/src/layout/text.rs index 3a064df4a..79ace0405 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -5,14 +5,24 @@ use super::*; use crate::size::{Size, Size2D}; /// The context for text layouting. +/// +/// See [`LayoutContext`] for details about the fields. #[derive(Copy, Clone)] pub struct TextContext<'a, 'p> { - /// Loads fonts matching queries. pub loader: &'a SharedFontLoader<'p>, - /// Base style to set text with. pub style: &'a TextStyle, } +impl<'a, 'p> TextContext<'a, 'p> { + /// Create a text context from a generic layout context. + pub fn from_layout_ctx(ctx: LayoutContext<'a, 'p>) -> TextContext<'a, 'p> { + TextContext { + loader: ctx.loader, + style: ctx.style, + } + } +} + /// Layouts text into a box. /// /// There is no complex layout involved. The text is simply laid out left- diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 7feed6106..a1918deda 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -19,14 +19,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { ctx, - stack: StackLayouter::new(StackContext { - space: ctx.space, - extra_space: ctx.extra_space - }), + stack: StackLayouter::new(StackContext::from_layout_ctx(ctx)), flex: FlexLayouter::new(FlexContext { - space: flex_space(ctx.space), - extra_space: ctx.extra_space.map(|s| flex_space(s)), - flex_spacing: flex_spacing(&ctx.style), + space: ctx.space.usable_space(), + followup_spaces: ctx.followup_spaces.map(|s| s.usable_space()), + shrink_to_fit: true, + .. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style)) }), style: Cow::Borrowed(ctx.style), } @@ -119,15 +117,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let mut ctx = self.ctx; ctx.style = &self.style; + ctx.shrink_to_fit = true; ctx.space.dimensions = self.stack.remaining(); ctx.space.padding = SizeBox::zero(); - ctx.space.shrink_to_fit = true; - if let Some(space) = ctx.extra_space.as_mut() { - space.dimensions = space.usable(); - space.padding = SizeBox::zero(); - space.shrink_to_fit = true; + if let Some(space) = ctx.followup_spaces.as_mut() { + *space = space.usable_space(); } let commands = func.body.layout(ctx)?; @@ -145,15 +141,6 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } } -fn flex_space(space: LayoutSpace) -> LayoutSpace { - LayoutSpace { - dimensions: space.usable(), - padding: SizeBox::zero(), - alignment: space.alignment, - shrink_to_fit: true, - } -} - fn flex_spacing(style: &TextStyle) -> Size { (style.line_spacing - 1.0) * Size::pt(style.font_size) } diff --git a/src/lib.rs b/src/lib.rs index fe9d32a60..424d8dbf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,8 +96,6 @@ impl<'p> Typesetter<'p> { let space = LayoutSpace { dimensions: self.page_style.dimensions, padding: self.page_style.margins, - alignment: Alignment::Left, - shrink_to_fit: false, }; let pages = layout_tree( @@ -105,8 +103,10 @@ impl<'p> Typesetter<'p> { LayoutContext { loader: &self.loader, style: &self.text_style, + alignment: Alignment::Left, space, - extra_space: Some(space), + followup_spaces: Some(space), + shrink_to_fit: false, }, )?; diff --git a/src/library/align.rs b/src/library/align.rs index f81bae317..be564c1bf 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -40,10 +40,7 @@ impl Function for AlignFunc { fn layout(&self, mut ctx: LayoutContext) -> LayoutResult { if let Some(body) = &self.body { - ctx.space.alignment = self.alignment; - if let Some(space) = ctx.extra_space.as_mut() { - space.alignment = self.alignment; - } + ctx.alignment = self.alignment; let layouts = layout_tree(body, ctx)?; diff --git a/tests/layouts/align.typ b/tests/layouts/align.typ index 8cad240ce..7a512f472 100644 --- a/tests/layouts/align.typ +++ b/tests/layouts/align.typ @@ -1,7 +1,6 @@ -{size:150pt*206pt} +{size:150pt*208pt} [align: left][Left: {lorem:20}] - [align: right][Right: {lorem:20}] [align: center][Center: {lorem:80}]