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.
///
/// 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<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 {
@ -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 {

View File

@ -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<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.
@ -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.

View File

@ -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<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 {
@ -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),

View File

@ -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-

View File

@ -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)
}

View File

@ -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,
},
)?;

View File

@ -40,10 +40,7 @@ impl Function for AlignFunc {
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<FuncCommands> {
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)?;

View File

@ -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}]