summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-10-13 12:08:07 +0200
committerLaurenz <laurmaedje@gmail.com>2019-10-13 12:08:07 +0200
commit463e4ebd8234da5e28700e9b22b6ef5f0dfef56f (patch)
tree01f3961d6996de3ee8f9819c2792f6ee8c2a3c3d /src/layout
parent6f22e4f13c42f06b686a01fbdd28a0163e88ae77 (diff)
Refactor layout types 🚧
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/actions.rs4
-rw-r--r--src/layout/flex.rs140
-rw-r--r--src/layout/mod.rs179
-rw-r--r--src/layout/stacked.rs (renamed from src/layout/boxed.rs)99
-rw-r--r--src/layout/text.rs4
5 files changed, 209 insertions, 217 deletions
diff --git a/src/layout/actions.rs b/src/layout/actions.rs
index f76b61c3..3eb85e50 100644
--- a/src/layout/actions.rs
+++ b/src/layout/actions.rs
@@ -3,7 +3,7 @@
use std::fmt::{self, Display, Formatter};
use std::io::{self, Write};
use crate::size::Size2D;
-use super::boxed::BoxLayout;
+use super::Layout;
use LayoutAction::*;
@@ -104,7 +104,7 @@ impl LayoutActionList {
/// Add all actions from a box layout at a position. A move to the position
/// is generated and all moves inside the box layout are translated as necessary.
- pub fn add_box(&mut self, position: Size2D, layout: BoxLayout) {
+ pub fn add_box(&mut self, position: Size2D, layout: Layout) {
if let Some(target) = self.next_pos.take() {
self.actions.push(MoveAbsolute(target));
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 75674e8f..68d39004 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -4,109 +4,85 @@ use crate::size::{Size, Size2D};
use super::*;
-/// A flex layout consists of a yet unarranged list of boxes.
-#[derive(Debug, Clone)]
-pub struct FlexLayout {
- /// The sublayouts composing this layout.
- pub units: Vec<FlexUnit>,
+
+/// Finishes a flex layout by justifying the positions of the individual boxes.
+#[derive(Debug)]
+pub struct FlexLayouter {
+ ctx: FlexContext,
+ units: Vec<FlexUnit>,
+
+ actions: LayoutActionList,
+ dimensions: Size2D,
+ usable: Size2D,
+ cursor: Size2D,
+
+ line_content: Vec<(Size2D, Layout)>,
+ line_metrics: Size2D,
+ last_glue: Option<Layout>,
+}
+
+/// The context for flex layouting.
+#[derive(Debug, Copy, Clone)]
+pub struct FlexContext {
+ /// The space to layout the boxes in.
+ pub space: LayoutSpace,
+ /// The flex spacing between two lines of boxes.
+ pub flex_spacing: Size,
}
/// A unit in a flex layout.
#[derive(Debug, Clone)]
-pub enum FlexUnit {
+enum FlexUnit {
/// A content unit to be arranged flexibly.
- Boxed(BoxLayout),
+ Boxed(Layout),
/// A unit which acts as glue between two [`FlexUnit::Boxed`] units and
/// is only present if there was no flow break in between the two surrounding boxes.
- Glue(BoxLayout),
+ Glue(Layout),
}
-impl FlexLayout {
- /// Create a new flex layout.
- pub fn new() -> FlexLayout {
- FlexLayout {
+impl FlexLayouter {
+ /// Create a new flex layouter.
+ pub fn new(ctx: FlexContext) -> FlexLayouter {
+ FlexLayouter {
+ ctx,
units: vec![],
+
+ actions: LayoutActionList::new(),
+ dimensions: match ctx.space.alignment {
+ Alignment::Left => Size2D::zero(),
+ Alignment::Right => Size2D::with_x(ctx.space.usable().x),
+ },
+ usable: ctx.space.usable(),
+ cursor: Size2D::new(ctx.space.padding.left, ctx.space.padding.top),
+
+ line_content: vec![],
+ line_metrics: Size2D::zero(),
+ last_glue: None,
}
}
- /// Create a new flex layout containing just one box.
- pub fn from_box(boxed: BoxLayout) -> FlexLayout {
- FlexLayout {
- units: vec![FlexUnit::Boxed(boxed)],
- }
+ /// Get a reference to this layouter's context.
+ pub fn ctx(&self) -> &FlexContext {
+ &self.ctx
}
/// Add a sublayout.
- pub fn add_box(&mut self, layout: BoxLayout) {
+ pub fn add(&mut self, layout: Layout) {
self.units.push(FlexUnit::Boxed(layout));
}
/// Add a glue layout which can be replaced by a line break.
- pub fn add_glue(&mut self, glue: BoxLayout) {
+ pub fn add_glue(&mut self, glue: Layout) {
self.units.push(FlexUnit::Glue(glue));
}
- /// Add all sublayouts of another flex layout.
- pub fn add_flexible(&mut self, layout: FlexLayout) {
- self.units.extend(layout.units);
- }
-
/// Whether this layouter contains any items.
pub fn is_empty(&self) -> bool {
self.units.is_empty()
}
/// Compute the justified layout.
- pub fn finish(self, ctx: FlexContext) -> LayoutResult<BoxLayout> {
- FlexFinisher::new(self, ctx).finish()
- }
-}
-
-/// The context for flex layouting.
-#[derive(Debug, Copy, Clone)]
-pub struct FlexContext {
- /// The space to layout the boxes in.
- pub space: LayoutSpace,
- /// The flex spacing between two lines of boxes.
- pub flex_spacing: Size,
-}
-
-/// Finishes a flex layout by justifying the positions of the individual boxes.
-#[derive(Debug)]
-struct FlexFinisher {
- units: Vec<FlexUnit>,
- ctx: FlexContext,
- actions: LayoutActionList,
- dimensions: Size2D,
- usable: Size2D,
- cursor: Size2D,
- line_metrics: Size2D,
- line_content: Vec<(Size2D, BoxLayout)>,
- glue: Option<BoxLayout>,
-}
-
-impl FlexFinisher {
- /// Create the finisher from the layout.
- fn new(layout: FlexLayout, ctx: FlexContext) -> FlexFinisher {
- let space = ctx.space;
- FlexFinisher {
- units: layout.units,
- ctx,
- actions: LayoutActionList::new(),
- dimensions: match ctx.space.alignment {
- Alignment::Left => Size2D::zero(),
- Alignment::Right => Size2D::with_x(space.usable().x),
- },
- usable: space.usable(),
- cursor: Size2D::new(space.padding.left, space.padding.top),
- line_metrics: Size2D::zero(),
- line_content: vec![],
- glue: None,
- }
- }
-
- /// Finish the flex layout into the justified box layout.
- fn finish(mut self) -> LayoutResult<BoxLayout> {
+ pub fn finish(mut self) -> LayoutResult<Layout> {
// Move the units out of the layout.
let units = self.units;
self.units = vec![];
@@ -122,7 +98,7 @@ impl FlexFinisher {
// Flush everything to get the correct dimensions.
self.newline();
- Ok(BoxLayout {
+ Ok(Layout {
dimensions: if self.ctx.space.shrink_to_fit {
self.dimensions.padded(self.ctx.space.padding)
} else {
@@ -134,8 +110,8 @@ impl FlexFinisher {
}
/// Layout the box.
- fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
- let last_glue_x = self.glue.as_ref()
+ fn boxed(&mut self, boxed: Layout) -> LayoutResult<()> {
+ let last_glue_x = self.last_glue.as_ref()
.map(|g| g.dimensions.x)
.unwrap_or(Size::zero());
@@ -147,7 +123,7 @@ impl FlexFinisher {
}
self.newline();
- } else if let Some(glue) = self.glue.take() {
+ } else if let Some(glue) = self.last_glue.take() {
self.append(glue);
}
@@ -157,15 +133,15 @@ impl FlexFinisher {
}
/// Layout the glue.
- fn glue(&mut self, glue: BoxLayout) {
- if let Some(glue) = self.glue.take() {
+ fn glue(&mut self, glue: Layout) {
+ if let Some(glue) = self.last_glue.take() {
self.append(glue);
}
- self.glue = Some(glue);
+ self.last_glue = Some(glue);
}
/// Append a box to the layout without checking anything.
- fn append(&mut self, layout: BoxLayout) {
+ fn append(&mut self, layout: Layout) {
let dim = layout.dimensions;
self.line_content.push((self.cursor, layout));
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 5cc8d8f2..19543389 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,50 +1,93 @@
//! The layouting engine.
use std::borrow::Cow;
+use std::io::{self, Write};
use std::mem;
use toddle::query::{SharedFontLoader, FontClass};
use toddle::Error as FontError;
+use crate::func::Command;
use crate::size::{Size, Size2D, SizeBox};
use crate::syntax::{SyntaxTree, Node, FuncCall};
use crate::style::TextStyle;
-use self::flex::{FlexLayout, FlexContext};
-use self::boxed::{BoxLayout, BoxContext, BoxLayouter};
-use self::text::TextContext;
-pub mod text;
-pub mod boxed;
-pub mod flex;
+mod text;
+mod stacked;
+mod flex;
mod actions;
pub use actions::{LayoutAction, LayoutActionList};
+pub use text::{layout_text, TextContext};
+pub use flex::{FlexLayouter, FlexContext};
+pub use stacked::{StackLayouter, StackContext};
-/// A collection of layouted content.
+/// A box layout has a fixed width and height and composes of actions.
#[derive(Debug, Clone)]
-pub enum Layout {
- /// A box layout.
- Boxed(BoxLayout),
- /// A flexible layout.
- Flex(FlexLayout),
+pub struct Layout {
+ /// The size of the box.
+ pub dimensions: Size2D,
+ /// The actions composing this layout.
+ pub actions: Vec<LayoutAction>,
+ /// Whether to debug-render this box.
+ pub debug_render: bool,
}
-/// Layout a syntax tree in a given context.
-pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<BoxLayout> {
- Layouter::new(tree, ctx).layout()
+impl Layout {
+ /// Serialize this layout into an output buffer.
+ pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
+ writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
+ for action in &self.actions {
+ action.serialize(f)?;
+ writeln!(f)?;
+ }
+ Ok(())
+ }
+}
+
+/// A collection of box layouts.
+#[derive(Debug, Clone)]
+pub struct MultiLayout {
+ pub layouts: Vec<Layout>,
+}
+
+impl MultiLayout {
+ /// Create an empty multibox layout.
+ pub fn new() -> MultiLayout {
+ MultiLayout {
+ layouts: vec![],
+ }
+ }
+
+ /// Extract a single sublayout and panic if this layout does not have
+ /// exactly one child.
+ pub fn into_single(mut self) -> Layout {
+ if self.layouts.len() != 1 {
+ panic!("into_single: contains not exactly one layout");
+ }
+ self.layouts.pop().unwrap()
+ }
+
+ /// Add a sublayout.
+ pub fn add(&mut self, layout: Layout) {
+ self.layouts.push(layout);
+ }
+
+ /// Whether this layout contains any sublayouts.
+ pub fn is_empty(&self) -> bool {
+ self.layouts.is_empty()
+ }
}
/// The context for layouting.
#[derive(Copy, Clone)]
pub struct LayoutContext<'a, 'p> {
- /// Loads fonts matching queries.
pub loader: &'a SharedFontLoader<'p>,
- /// Base style to set text with.
pub style: &'a TextStyle,
- /// The space to layout in.
pub space: LayoutSpace,
+ pub extra_space: Option<LayoutSpace>,
}
/// Spacial constraints for layouting.
@@ -61,13 +104,6 @@ pub struct LayoutSpace {
pub shrink_to_fit: bool,
}
-/// Where to align content.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum Alignment {
- Left,
- Right,
-}
-
impl LayoutSpace {
/// The actually usable area.
pub fn usable(&self) -> Size2D {
@@ -78,12 +114,23 @@ impl LayoutSpace {
}
}
+/// Where to align content.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Alignment {
+ Left,
+ Right,
+}
+
+pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
+ Layouter::new(tree, ctx).layout()
+}
+
/// Transforms a syntax tree into a box layout.
struct Layouter<'a, 'p> {
+ ctx: LayoutContext<'a, 'p>,
tree: &'a SyntaxTree,
- box_layouter: BoxLayouter,
- flex_layout: FlexLayout,
- loader: &'a SharedFontLoader<'p>,
+ stack_layouter: StackLayouter,
+ flex_layouter: FlexLayouter,
style: Cow<'a, TextStyle>,
}
@@ -91,16 +138,24 @@ impl<'a, 'p> Layouter<'a, 'p> {
/// Create a new layouter.
fn new(tree: &'a SyntaxTree, ctx: LayoutContext<'a, 'p>) -> Layouter<'a, 'p> {
Layouter {
+ ctx,
tree,
- box_layouter: BoxLayouter::new(BoxContext { space: ctx.space }),
- flex_layout: FlexLayout::new(),
- loader: ctx.loader,
+ 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) -> LayoutResult<BoxLayout> {
+ fn layout(mut self) -> LayoutResult<MultiLayout> {
// Walk all nodes and layout them.
for node in &self.tree.nodes {
match node {
@@ -109,7 +164,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
// Add a space.
Node::Space => {
- if !self.flex_layout.is_empty() {
+ if !self.flex_layouter.is_empty() {
self.layout_text(" ", true)?;
}
},
@@ -122,7 +177,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
// Add some paragraph spacing.
let size = Size::pt(self.style.font_size)
* (self.style.line_spacing * self.style.paragraph_spacing - 1.0);
- self.box_layouter.add_space(size)?;
+ self.stack_layouter.add_space(size)?;
},
// Toggle the text styles.
@@ -136,24 +191,26 @@ impl<'a, 'p> Layouter<'a, 'p> {
}
// If there are remainings, add them to the layout.
- if !self.flex_layout.is_empty() {
+ if !self.flex_layouter.is_empty() {
self.layout_flex()?;
}
- Ok(self.box_layouter.finish())
+ 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 = self::text::layout(text, TextContext {
- loader: &self.loader,
+ let boxed = layout_text(text, TextContext {
+ loader: &self.ctx.loader,
style: &self.style,
})?;
if glue {
- self.flex_layout.add_glue(boxed);
+ self.flex_layouter.add_glue(boxed);
} else {
- self.flex_layout.add_box(boxed);
+ self.flex_layouter.add(boxed);
}
Ok(())
@@ -161,48 +218,46 @@ impl<'a, 'p> Layouter<'a, 'p> {
/// Finish the current flex run and return the resulting box.
fn layout_flex(&mut self) -> LayoutResult<()> {
- if self.flex_layout.is_empty() {
+ if self.flex_layouter.is_empty() {
return Ok(());
}
- let mut layout = FlexLayout::new();
- mem::swap(&mut layout, &mut self.flex_layout);
-
- let boxed = layout.finish(FlexContext {
+ let mut layout = FlexLayouter::new(FlexContext {
space: LayoutSpace {
- dimensions: self.box_layouter.remaining(),
+ dimensions: self.stack_layouter.ctx().space.usable(),
padding: SizeBox::zero(),
- alignment: self.box_layouter.ctx.space.alignment,
+ 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.box_layouter.add_box(boxed)
+ self.stack_layouter.add_box(boxed)
}
/// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
- let layout = func.body.layout(LayoutContext {
- loader: &self.loader,
+ let commands = func.body.layout(LayoutContext {
+ loader: &self.ctx.loader,
style: &self.style,
space: LayoutSpace {
- dimensions: self.box_layouter.remaining(),
+ dimensions: self.stack_layouter.remaining(),
padding: SizeBox::zero(),
- alignment: self.box_layouter.ctx.space.alignment,
+ alignment: self.ctx.space.alignment,
shrink_to_fit: true,
},
+ extra_space: self.ctx.extra_space,
})?;
- // Add the potential layout.
- if let Some(layout) = layout {
- match layout {
- Layout::Boxed(boxed) => {
- // Finish the previous flex run before adding the box.
- self.layout_flex()?;
- self.box_layouter.add_box(boxed)?;
- },
- Layout::Flex(flex) => self.flex_layout.add_flexible(flex),
+ for command in commands {
+ match command {
+ Command::Layout(tree) => unimplemented!(),
+ Command::Add(layout) => unimplemented!(),
+ Command::AddMany(layouts) => unimplemented!(),
+ Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
}
}
diff --git a/src/layout/boxed.rs b/src/layout/stacked.rs
index 7ea9e4fb..78eb0058 100644
--- a/src/layout/boxed.rs
+++ b/src/layout/stacked.rs
@@ -1,68 +1,27 @@
-//! Block-style layouting of boxes.
-
-use std::io::{self, Write};
-use crate::doc::{Document, Page};
-use crate::size::{Size, Size2D};
use super::*;
-/// A box layout has a fixed width and height and composes of actions.
-#[derive(Debug, Clone)]
-pub struct BoxLayout {
- /// The size of the box.
- pub dimensions: Size2D,
- /// The actions composing this layout.
- pub actions: Vec<LayoutAction>,
- /// Whether to debug-render this box.
- pub debug_render: bool,
-}
-
-impl BoxLayout {
- /// Convert this layout into a document.
- pub fn into_doc(self) -> Document {
- Document {
- pages: vec![Page {
- width: self.dimensions.x,
- height: self.dimensions.y,
- actions: self.actions,
- }],
- }
- }
-
- /// Serialize this layout into a string representation.
- pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
- writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
- for action in &self.actions {
- action.serialize(f)?;
- writeln!(f)?;
- }
- Ok(())
- }
-}
-
-/// The context for layouting boxes.
-#[derive(Debug, Copy, Clone)]
-pub struct BoxContext {
- /// The space to layout the boxes in.
- pub space: LayoutSpace,
-}
-
/// Layouts boxes block-style.
#[derive(Debug)]
-pub struct BoxLayouter {
- pub ctx: BoxContext,
+pub struct StackLayouter {
+ ctx: StackContext,
actions: LayoutActionList,
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
}
-impl BoxLayouter {
+#[derive(Debug, Copy, Clone)]
+pub struct StackContext {
+ pub space: LayoutSpace,
+}
+
+impl StackLayouter {
/// Create a new box layouter.
- pub fn new(ctx: BoxContext) -> BoxLayouter {
+ pub fn new(ctx: StackContext) -> StackLayouter {
let space = ctx.space;
- BoxLayouter {
+ StackLayouter {
ctx,
actions: LayoutActionList::new(),
dimensions: match ctx.space.alignment {
@@ -77,52 +36,54 @@ impl BoxLayouter {
}
}
+ /// Get a reference to this layouter's context.
+ pub fn ctx(&self) -> &StackContext {
+ &self.ctx
+ }
+
/// Add a sublayout.
- pub fn add_box(&mut self, layout: BoxLayout) -> LayoutResult<()> {
+ pub fn add_box(&mut self, layout: Layout) -> LayoutResult<()> {
// In the flow direction (vertical) add the layout and in the second
// direction just consider the maximal size of any child layout.
- let new = Size2D {
+ let new_size = Size2D {
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
y: self.dimensions.y + layout.dimensions.y,
};
// Check whether this box fits.
- if self.overflows(new) {
+ if self.overflows(new_size) {
return Err(LayoutError::NotEnoughSpace);
}
- // Apply the dimensions if they fit.
- self.dimensions = new;
- let width = layout.dimensions.x;
- let height = layout.dimensions.y;
+ self.dimensions = new_size;
+ // 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.ctx.space.alignment {
Alignment::Left => self.cursor,
- Alignment::Right => self.cursor - Size2D::with_x(width),
+ Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x),
};
- // Add the box.
- self.add_box_absolute(position, layout);
+ self.cursor.y += layout.dimensions.y;
- // Adjust the cursor.
- self.cursor.y += height;
+ self.add_box_absolute(position, layout);
Ok(())
}
/// Add a sublayout at an absolute position.
- pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
- self.actions.add_box(position, layout);
+ pub fn add_box_absolute(&mut self, position: Size2D, layout: Layout) -> LayoutResult<()> {
+ Ok(self.actions.add_box(position, layout))
}
- /// Add some space in between two boxes.
+ /// Add space in between two boxes.
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
// Check whether this space fits.
if self.overflows(self.dimensions + Size2D::with_y(space)) {
return Err(LayoutError::NotEnoughSpace);
}
- // Adjust the sizes.
self.cursor.y += space;
self.dimensions.y += space;
@@ -143,8 +104,8 @@ impl BoxLayouter {
}
/// Finish the layouting and create a box layout from this.
- pub fn finish(self) -> BoxLayout {
- BoxLayout {
+ pub fn finish(self) -> Layout {
+ Layout {
dimensions: if self.ctx.space.shrink_to_fit {
self.dimensions.padded(self.ctx.space.padding)
} else {
diff --git a/src/layout/text.rs b/src/layout/text.rs
index ecec0220..6cbe3b8e 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -17,7 +17,7 @@ pub struct TextContext<'a, 'p> {
}
/// Layout one piece of text without any breaks as one continous box.
-pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
+pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> {
let mut loader = ctx.loader.borrow_mut();
let mut actions = Vec::new();
@@ -88,7 +88,7 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
actions.push(LayoutAction::WriteText(buffer));
}
- Ok(BoxLayout {
+ Ok(Layout {
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
actions,
debug_render: false,