Refactor the main layouter ♻

This commit is contained in:
Laurenz 2019-10-15 18:15:36 +02:00
parent 0727713424
commit a3c667895e
6 changed files with 177 additions and 176 deletions

View File

@ -19,7 +19,6 @@ use crate::layout::{Layout, LayoutAction, MultiLayout};
use crate::size::Size;
/// Exports layouts into _PDFs_.
#[derive(Debug)]
pub struct PdfExporter {}
impl PdfExporter {

View File

@ -1,6 +1,6 @@
use super::*;
/// Flex-layouting of boxes.
/// Layouts boxes flex-like.
///
/// The boxes are arranged in "lines", each line having the height of its
/// biggest box. When a box does not fit on a line anymore horizontally,
@ -74,9 +74,9 @@ impl FlexLayouter {
}
}
/// Get a reference to this layouter's context.
pub fn ctx(&self) -> &FlexContext {
&self.ctx
/// This layouter's context.
pub fn ctx(&self) -> FlexContext {
self.ctx
}
/// Add a sublayout.

View File

@ -2,7 +2,6 @@
use std::borrow::Cow;
use std::io::{self, Write};
use std::mem;
use toddle::query::{FontClass, SharedFontLoader};
use toddle::Error as FontError;
@ -13,16 +12,23 @@ use crate::style::TextStyle;
use crate::syntax::{FuncCall, Node, SyntaxTree};
mod actions;
mod tree;
mod flex;
mod stacked;
mod text;
pub use actions::{LayoutAction, LayoutActionList};
pub use flex::{FlexContext, FlexLayouter};
pub use stacked::{StackContext, StackLayouter};
pub use text::{layout_text, TextContext};
/// Different kinds of layouters (fully re-exported).
pub mod layouters {
pub use super::tree::layout_tree;
pub use super::flex::{FlexLayouter, FlexContext};
pub use super::stacked::{StackLayouter, StackContext};
pub use super::text::{layout_text, TextContext};
}
/// A box layout has a fixed width and height and composes of actions.
pub use actions::{LayoutAction, LayoutActionList};
pub use layouters::*;
/// A sequence of layouting actions inside a box.
#[derive(Debug, Clone)]
pub struct Layout {
/// The size of the box.
@ -50,19 +56,19 @@ impl Layout {
}
}
/// A collection of box layouts.
/// A collection of layouts.
#[derive(Debug, Clone)]
pub struct MultiLayout {
pub layouts: Vec<Layout>,
}
impl MultiLayout {
/// Create an empty multibox layout.
/// Create an empty multi-layout.
pub fn new() -> MultiLayout {
MultiLayout { layouts: vec![] }
}
/// Extract a single sublayout and panic if this layout does not have
/// Extract the single sublayout. This panics if the layout does not have
/// exactly one child.
pub fn into_single(mut self) -> Layout {
if self.layouts.len() != 1 {
@ -105,8 +111,7 @@ impl<'a> IntoIterator for &'a MultiLayout {
}
}
/// The context for layouting.
/// The general context for layouting.
#[derive(Copy, Clone)]
pub struct LayoutContext<'a, 'p> {
pub loader: &'a SharedFontLoader<'p>,
@ -115,7 +120,7 @@ pub struct LayoutContext<'a, 'p> {
pub extra_space: Option<LayoutSpace>,
}
/// Spacial constraints for layouting.
/// Spacial layouting constraints.
#[derive(Debug, Copy, Clone)]
pub struct LayoutSpace {
/// The maximum size of the box to layout in.
@ -125,12 +130,12 @@ pub struct LayoutSpace {
/// The alignment to use for the content.
pub alignment: Alignment,
/// Whether to shrink the dimensions to fit the content or the keep the
/// original ones.
/// dimensions from the layout space.
pub shrink_to_fit: bool,
}
impl LayoutSpace {
/// The actually usable area.
/// The actually usable area (dimensions minus padding).
pub fn usable(&self) -> Size2D {
Size2D {
x: self.dimensions.x - self.padding.left - self.padding.right,
@ -146,157 +151,6 @@ pub enum Alignment {
Right,
}
pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
let mut layouter = Layouter::new(ctx);
layouter.layout(tree)?;
layouter.finish()
}
/// Transforms a syntax tree into a box layout.
struct Layouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
stack_layouter: StackLayouter,
flex_layouter: FlexLayouter,
style: Cow<'a, TextStyle>,
}
impl<'a, 'p> Layouter<'a, 'p> {
/// Create a new layouter.
fn new(ctx: LayoutContext<'a, 'p>) -> Layouter<'a, 'p> {
Layouter {
ctx,
stack_layouter: StackLayouter::new(StackContext { space: ctx.space }),
flex_layouter: FlexLayouter::new(FlexContext {
space: LayoutSpace {
dimensions: ctx.space.usable(),
padding: SizeBox::zero(),
alignment: ctx.space.alignment,
shrink_to_fit: true,
},
flex_spacing: (ctx.style.line_spacing - 1.0) * Size::pt(ctx.style.font_size),
}),
style: Cow::Borrowed(ctx.style),
}
}
/// Layout the tree into a box.
fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
// Walk all nodes and layout them.
for node in &tree.nodes {
match node {
// Layout a single piece of text.
Node::Text(text) => self.layout_text(text, false)?,
// Add a space.
Node::Space => {
if !self.flex_layouter.is_empty() {
self.layout_text(" ", true)?;
}
}
// Finish the current flex layout and add it to the box layouter.
Node::Newline => {
// Finish the current paragraph into a box and add it.
self.layout_flex()?;
// Add some paragraph spacing.
let size = Size::pt(self.style.font_size)
* (self.style.line_spacing * self.style.paragraph_spacing - 1.0);
self.stack_layouter.add_space(size)?;
}
// Toggle the text styles.
Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic),
Node::ToggleBold => self.style.to_mut().toggle_class(FontClass::Bold),
Node::ToggleMonospace => self.style.to_mut().toggle_class(FontClass::Monospace),
// Execute a function.
Node::Func(func) => self.layout_func(func)?,
}
}
Ok(())
}
fn finish(mut self) -> LayoutResult<MultiLayout> {
// If there are remainings, add them to the layout.
if !self.flex_layouter.is_empty() {
self.layout_flex()?;
}
Ok(MultiLayout {
layouts: vec![self.stack_layouter.finish()],
})
}
/// Layout a piece of text into a box.
fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> {
let boxed = layout_text(
text,
TextContext {
loader: &self.ctx.loader,
style: &self.style,
},
)?;
if glue {
self.flex_layouter.add_glue(boxed);
} else {
self.flex_layouter.add(boxed);
}
Ok(())
}
/// Finish the current flex run and return the resulting box.
fn layout_flex(&mut self) -> LayoutResult<()> {
if self.flex_layouter.is_empty() {
return Ok(());
}
let mut layout = FlexLayouter::new(FlexContext {
space: LayoutSpace {
dimensions: self.stack_layouter.ctx().space.usable(),
padding: SizeBox::zero(),
alignment: self.ctx.space.alignment,
shrink_to_fit: true,
},
flex_spacing: (self.style.line_spacing - 1.0) * Size::pt(self.style.font_size),
});
mem::swap(&mut layout, &mut self.flex_layouter);
let boxed = layout.finish()?;
self.stack_layouter.add(boxed)
}
/// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
let commands = func.body.layout(LayoutContext {
loader: &self.ctx.loader,
style: &self.style,
space: LayoutSpace {
dimensions: self.stack_layouter.remaining(),
padding: SizeBox::zero(),
alignment: self.ctx.space.alignment,
shrink_to_fit: true,
},
extra_space: self.ctx.extra_space,
})?;
for command in commands {
match command {
Command::Layout(tree) => self.layout(tree)?,
Command::Add(layout) => self.stack_layouter.add(layout)?,
Command::AddMany(layouts) => self.stack_layouter.add_many(layouts)?,
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
}
}
Ok(())
}
}
/// The error type for layouting.
pub enum LayoutError {
/// There is not enough space to add an item.

View File

@ -1,6 +1,6 @@
use super::*;
/// Stack-like layouting of boxes.
/// Layouts boxes stack-like.
///
/// The boxes are arranged vertically, each layout gettings it's own "line".
pub struct StackLayouter {
@ -11,7 +11,7 @@ pub struct StackLayouter {
cursor: Size2D,
}
/// The context for the [`StackLayouter`].
/// The context for stack layouting.
#[derive(Debug, Copy, Clone)]
pub struct StackContext {
/// The space to layout the boxes in.
@ -46,9 +46,9 @@ impl StackLayouter {
}
}
/// Get a reference to this layouter's context.
pub fn ctx(&self) -> &StackContext {
&self.ctx
/// This layouter's context.
pub fn ctx(&self) -> StackContext {
self.ctx
}
/// Add a sublayout to the bottom.

148
src/layout/tree.rs Normal file
View File

@ -0,0 +1,148 @@
use super::*;
/// Layouts syntax trees into boxes.
pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
let mut layouter = TreeLayouter::new(ctx);
layouter.layout(tree)?;
layouter.finish()
}
struct TreeLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
stack: StackLayouter,
flex: FlexLayouter,
style: Cow<'a, TextStyle>,
}
impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Create a new layouter.
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
TreeLayouter {
ctx,
stack: StackLayouter::new(StackContext { space: ctx.space }),
flex: FlexLayouter::new(FlexContext {
space: LayoutSpace {
dimensions: ctx.space.usable(),
padding: SizeBox::zero(),
alignment: ctx.space.alignment,
shrink_to_fit: true,
},
flex_spacing: flex_spacing(&ctx.style),
}),
style: Cow::Borrowed(ctx.style),
}
}
/// Layout the tree into a box.
fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
for node in &tree.nodes {
match node {
Node::Text(text) => self.layout_text(text, false)?,
Node::Space => {
// Only add a space if there was any content before.
if !self.flex.is_empty() {
self.layout_text(" ", true)?;
}
}
// Finish the current flex layouting process.
Node::Newline => {
self.layout_flex()?;
let space = paragraph_spacing(&self.style);
self.stack.add_space(space)?;
}
// Toggle the text styles.
Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic),
Node::ToggleBold => self.style.to_mut().toggle_class(FontClass::Bold),
Node::ToggleMonospace => self.style.to_mut().toggle_class(FontClass::Monospace),
Node::Func(func) => self.layout_func(func)?,
}
}
Ok(())
}
/// Finish the layout.
fn finish(mut self) -> LayoutResult<MultiLayout> {
// If there are remainings, add them to the layout.
if !self.flex.is_empty() {
self.layout_flex()?;
}
Ok(MultiLayout {
layouts: vec![self.stack.finish()],
})
}
/// Add text to the flex layout. If `glue` is true, the text will be a glue
/// part in the flex layouter. For details, see [`FlexLayouter`].
fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> {
let ctx = TextContext {
loader: &self.ctx.loader,
style: &self.style,
};
let layout = layout_text(text, ctx)?;
if glue {
self.flex.add_glue(layout);
} else {
self.flex.add(layout);
}
Ok(())
}
/// Finish the current flex layout and add it the stack.
fn layout_flex(&mut self) -> LayoutResult<()> {
if self.flex.is_empty() {
return Ok(());
}
let mut ctx = self.flex.ctx();
ctx.space.dimensions = self.stack.remaining();
ctx.flex_spacing = flex_spacing(&self.style);
let next = FlexLayouter::new(ctx);
let flex = std::mem::replace(&mut self.flex, next);
let boxed = flex.finish()?;
self.stack.add(boxed)
}
/// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
let mut ctx = self.ctx;
ctx.style = &self.style;
ctx.space.dimensions = self.stack.remaining();
ctx.space.padding = SizeBox::zero();
ctx.space.shrink_to_fit = true;
let commands = func.body.layout(ctx)?;
for command in commands {
match command {
Command::Layout(tree) => self.layout(tree)?,
Command::Add(layout) => self.stack.add(layout)?,
Command::AddMany(layouts) => self.stack.add_many(layouts)?,
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
}
}
Ok(())
}
}
fn flex_spacing(style: &TextStyle) -> Size {
(style.line_spacing - 1.0) * Size::pt(style.font_size)
}
fn paragraph_spacing(style: &TextStyle) -> Size {
let line_height = Size::pt(style.font_size);
let space_factor = style.line_spacing * style.paragraph_spacing - 1.0;
line_height * space_factor
}

View File

@ -153,7 +153,7 @@ impl<'s> Parser<'s> {
/// Parse the arguments to a function.
fn parse_func_args(&mut self) -> ParseResult<(Vec<Expression>, HashMap<String, Expression>)> {
let mut args = vec![];
let mut args = Vec::new();
let kwargs = HashMap::new();
let mut comma = false;