Rearrange layouting contexts ♻

This commit is contained in:
Laurenz 2019-10-16 22:32:40 +02:00
parent 58693486f9
commit e87a34a4d0
8 changed files with 119 additions and 59 deletions

View File

@ -28,12 +28,40 @@ pub struct FlexLayouter {
} }
/// The context for flex layouting. /// The context for flex layouting.
///
/// See [`LayoutContext`] for details about the fields.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct FlexContext { pub struct FlexContext {
pub space: LayoutSpace,
/// The spacing between two lines of boxes. /// The spacing between two lines of boxes.
pub flex_spacing: Size, pub flex_spacing: Size,
pub extra_space: Option<LayoutSpace>, pub alignment: Alignment,
pub space: LayoutSpace,
pub followup_spaces: Option<LayoutSpace>,
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 { enum FlexUnit {
@ -57,10 +85,7 @@ impl FlexLayouter {
ctx, ctx,
units: vec![], units: vec![],
stack: StackLayouter::new(StackContext { stack: StackLayouter::new(StackContext::from_flex_ctx(ctx)),
space: ctx.space,
extra_space: ctx.extra_space,
}),
usable_width: ctx.space.usable().x, usable_width: ctx.space.usable().x,
run: FlexRun { run: FlexRun {
@ -125,7 +150,7 @@ impl FlexLayouter {
// If the box does not even fit on its own line, then we try // 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. // it in the next space, or we have to give up if there is none.
if self.overflows_line(boxed.dimensions.x) { 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)?; self.stack.finish_layout(true)?;
return self.layout_box(boxed); return self.layout_box(boxed);
} else { } else {

View File

@ -126,10 +126,22 @@ impl<'a> IntoIterator for &'a MultiLayout {
/// The general context for layouting. /// The general context for layouting.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct LayoutContext<'a, 'p> { pub struct LayoutContext<'a, 'p> {
/// The font loader to retrieve fonts from when typesetting text
/// using [`layout_text`].
pub loader: &'a SharedFontLoader<'p>, 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, pub style: &'a TextStyle,
/// The alignment to use for the content.
pub alignment: Alignment,
/// The primary space to layout in.
pub space: LayoutSpace, pub space: LayoutSpace,
pub extra_space: Option<LayoutSpace>, /// The additional spaces which are used when the primary space
/// cannot fit the whole content.
pub followup_spaces: Option<LayoutSpace>,
/// 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. /// Spacial layouting constraints.
@ -139,11 +151,6 @@ pub struct LayoutSpace {
pub dimensions: Size2D, pub dimensions: Size2D,
/// Padding that should be respected on each side. /// Padding that should be respected on each side.
pub padding: SizeBox, 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 { impl LayoutSpace {
@ -151,6 +158,14 @@ impl LayoutSpace {
pub fn usable(&self) -> Size2D { pub fn usable(&self) -> Size2D {
self.dimensions.unpadded(self.padding) 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. /// Where to align content.

View File

@ -17,10 +17,37 @@ pub struct StackLayouter {
} }
/// The context for stack layouting. /// The context for stack layouting.
///
/// See [`LayoutContext`] for details about the fields.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct StackContext { pub struct StackContext {
pub alignment: Alignment,
pub space: LayoutSpace, pub space: LayoutSpace,
pub extra_space: Option<LayoutSpace>, pub followup_spaces: Option<LayoutSpace>,
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 { impl StackLayouter {
@ -33,8 +60,8 @@ impl StackLayouter {
space: ctx.space, space: ctx.space,
usable: ctx.space.usable(), usable: ctx.space.usable(),
dimensions: start_dimensions(ctx.space), dimensions: start_dimensions(ctx.alignment, ctx.space),
cursor: start_cursor(ctx.space), cursor: start_cursor(ctx.alignment, ctx.space),
in_extra_space: false, in_extra_space: false,
started: true, started: true,
} }
@ -57,7 +84,7 @@ impl StackLayouter {
}; };
if self.overflows(new_dimensions) { 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.in_extra_space && self.overflows(layout.dimensions))
{ {
self.finish_layout(true)?; self.finish_layout(true)?;
@ -70,7 +97,7 @@ impl StackLayouter {
// Determine where to put the box. When we right-align it, we want the // 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 // 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. // 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::Left => self.cursor,
Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x), Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x),
Alignment::Center => self.cursor - Size2D::with_x(layout.dimensions.x / 2), 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); let new_dimensions = self.dimensions + Size2D::with_y(space);
if self.overflows(new_dimensions) { if self.overflows(new_dimensions) {
if self.ctx.extra_space.is_some() { if self.ctx.followup_spaces.is_some() {
self.finish_layout(false)?; self.finish_layout(false)?;
} else { } else {
return Err(LayoutError::NotEnoughSpace("cannot fit space into stack")); 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<()> { pub fn finish_layout(&mut self, start_new_empty: bool) -> LayoutResult<()> {
let actions = std::mem::replace(&mut self.actions, LayoutActionList::new()); let actions = std::mem::replace(&mut self.actions, LayoutActionList::new());
self.layouts.add(Layout { self.layouts.add(Layout {
dimensions: if self.space.shrink_to_fit { dimensions: if self.ctx.shrink_to_fit {
self.dimensions.padded(self.space.padding) self.dimensions.padded(self.space.padding)
} else { } else {
self.space.dimensions self.space.dimensions
@ -152,12 +179,12 @@ impl StackLayouter {
} }
pub fn start_new_space(&mut self) -> LayoutResult<()> { 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.started = true;
self.space = space; self.space = space;
self.usable = space.usable(); self.usable = space.usable();
self.dimensions = start_dimensions(space); self.dimensions = start_dimensions(self.ctx.alignment, space);
self.cursor = start_cursor(space); self.cursor = start_cursor(self.ctx.alignment, space);
self.in_extra_space = true; self.in_extra_space = true;
Ok(()) Ok(())
} else { } else {
@ -183,19 +210,19 @@ impl StackLayouter {
} }
} }
fn start_dimensions(space: LayoutSpace) -> Size2D { fn start_dimensions(alignment: Alignment, space: LayoutSpace) -> Size2D {
match space.alignment { match alignment {
Alignment::Left => Size2D::zero(), Alignment::Left => Size2D::zero(),
Alignment::Right | Alignment::Center => Size2D::with_x(space.usable().x), 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 { Size2D {
// If left-align, the cursor points to the top-left corner of // If left-align, the cursor points to the top-left corner of
// each box. If we right-align, it points to the top-right // each box. If we right-align, it points to the top-right
// corner. // corner.
x: match space.alignment { x: match alignment {
Alignment::Left => space.padding.left, Alignment::Left => space.padding.left,
Alignment::Right => space.dimensions.x - space.padding.right, Alignment::Right => space.dimensions.x - space.padding.right,
Alignment::Center => space.padding.left + (space.usable().x / 2), Alignment::Center => space.padding.left + (space.usable().x / 2),

View File

@ -5,14 +5,24 @@ use super::*;
use crate::size::{Size, Size2D}; use crate::size::{Size, Size2D};
/// The context for text layouting. /// The context for text layouting.
///
/// See [`LayoutContext`] for details about the fields.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct TextContext<'a, 'p> { pub struct TextContext<'a, 'p> {
/// Loads fonts matching queries.
pub loader: &'a SharedFontLoader<'p>, pub loader: &'a SharedFontLoader<'p>,
/// Base style to set text with.
pub style: &'a TextStyle, 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. /// Layouts text into a box.
/// ///
/// There is no complex layout involved. The text is simply laid out left- /// There is no complex layout involved. The text is simply laid out left-

View File

@ -19,14 +19,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
TreeLayouter { TreeLayouter {
ctx, ctx,
stack: StackLayouter::new(StackContext { stack: StackLayouter::new(StackContext::from_layout_ctx(ctx)),
space: ctx.space,
extra_space: ctx.extra_space
}),
flex: FlexLayouter::new(FlexContext { flex: FlexLayouter::new(FlexContext {
space: flex_space(ctx.space), space: ctx.space.usable_space(),
extra_space: ctx.extra_space.map(|s| flex_space(s)), followup_spaces: ctx.followup_spaces.map(|s| s.usable_space()),
flex_spacing: flex_spacing(&ctx.style), shrink_to_fit: true,
.. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style))
}), }),
style: Cow::Borrowed(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<()> { fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
let mut ctx = self.ctx; let mut ctx = self.ctx;
ctx.style = &self.style; ctx.style = &self.style;
ctx.shrink_to_fit = true;
ctx.space.dimensions = self.stack.remaining(); ctx.space.dimensions = self.stack.remaining();
ctx.space.padding = SizeBox::zero(); ctx.space.padding = SizeBox::zero();
ctx.space.shrink_to_fit = true;
if let Some(space) = ctx.extra_space.as_mut() { if let Some(space) = ctx.followup_spaces.as_mut() {
space.dimensions = space.usable(); *space = space.usable_space();
space.padding = SizeBox::zero();
space.shrink_to_fit = true;
} }
let commands = func.body.layout(ctx)?; 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 { fn flex_spacing(style: &TextStyle) -> Size {
(style.line_spacing - 1.0) * Size::pt(style.font_size) (style.line_spacing - 1.0) * Size::pt(style.font_size)
} }

View File

@ -96,8 +96,6 @@ impl<'p> Typesetter<'p> {
let space = LayoutSpace { let space = LayoutSpace {
dimensions: self.page_style.dimensions, dimensions: self.page_style.dimensions,
padding: self.page_style.margins, padding: self.page_style.margins,
alignment: Alignment::Left,
shrink_to_fit: false,
}; };
let pages = layout_tree( let pages = layout_tree(
@ -105,8 +103,10 @@ impl<'p> Typesetter<'p> {
LayoutContext { LayoutContext {
loader: &self.loader, loader: &self.loader,
style: &self.text_style, style: &self.text_style,
alignment: Alignment::Left,
space, space,
extra_space: Some(space), followup_spaces: Some(space),
shrink_to_fit: false,
}, },
)?; )?;

View File

@ -40,10 +40,7 @@ impl Function for AlignFunc {
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<FuncCommands> { fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<FuncCommands> {
if let Some(body) = &self.body { if let Some(body) = &self.body {
ctx.space.alignment = self.alignment; ctx.alignment = self.alignment;
if let Some(space) = ctx.extra_space.as_mut() {
space.alignment = self.alignment;
}
let layouts = layout_tree(body, ctx)?; let layouts = layout_tree(body, ctx)?;

View File

@ -1,7 +1,6 @@
{size:150pt*206pt} {size:150pt*208pt}
[align: left][Left: {lorem:20}] [align: left][Left: {lorem:20}]
[align: right][Right: {lorem:20}] [align: right][Right: {lorem:20}]
[align: center][Center: {lorem:80}] [align: center][Center: {lorem:80}]