summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-06-21 22:12:36 +0200
committerLaurenz <laurmaedje@gmail.com>2019-06-21 22:12:36 +0200
commite39a6efccff7ba2b5dd546ddf86f23d2714161e7 (patch)
treecd342336fae0380464d1d2d0db5ddd60de61a615 /src
parent968e121697a96a2e3b05a560176c34f4bb6693c3 (diff)
Deduplicate action lists ✂
Diffstat (limited to 'src')
-rw-r--r--src/layout/boxed.rs38
-rw-r--r--src/layout/flex.rs30
-rw-r--r--src/layout/mod.rs90
3 files changed, 106 insertions, 52 deletions
diff --git a/src/layout/boxed.rs b/src/layout/boxed.rs
index 2ee2dbdc..89c641bc 100644
--- a/src/layout/boxed.rs
+++ b/src/layout/boxed.rs
@@ -3,7 +3,7 @@
use crate::doc::{Document, Page, TextAction};
use crate::font::Font;
use crate::size::{Size, Size2D};
-use super::LayoutSpace;
+use super::{ActionList, LayoutSpace, LayoutResult, LayoutError};
/// A box layout has a fixed width and height and composes of actions.
@@ -40,7 +40,7 @@ pub struct BoxContext {
#[derive(Debug)]
pub struct BoxLayouter {
ctx: BoxContext,
- actions: Vec<TextAction>,
+ actions: ActionList,
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
@@ -52,7 +52,7 @@ impl BoxLayouter {
let space = ctx.space;
BoxLayouter {
ctx,
- actions: vec![],
+ actions: ActionList::new(),
dimensions: Size2D::zero(),
usable: space.usable(),
cursor: Size2D::new(space.padding.left, space.padding.right),
@@ -60,7 +60,7 @@ impl BoxLayouter {
}
/// Add a sublayout.
- pub fn add_box(&mut self, layout: BoxLayout) {
+ pub fn add_box(&mut self, layout: BoxLayout) -> 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 {
@@ -68,34 +68,44 @@ impl BoxLayouter {
y: self.dimensions.y + layout.dimensions.y,
};
+ // Check whether this box fits.
if self.overflows(new) {
- panic!("box layouter: would overflow in add_box");
+ return Err(LayoutError::NotEnoughSpace);
}
- // Apply the dimensions because they fit.
+ // Apply the dimensions as they fit.
+ let height = layout.dimensions.y;
self.dimensions = new;
- // Move all actions into this layout and translate absolute positions.
- self.actions.push(TextAction::MoveAbsolute(self.cursor));
- self.actions.extend(super::translate_actions(self.cursor, layout.actions));
+ // Add the box.
+ self.add_box_absolute(self.cursor, layout);
// Adjust the cursor.
- self.cursor.y += layout.dimensions.y;
+ self.cursor.y += height;
+
+ Ok(())
}
/// Add some space in between two boxes.
- pub fn add_space(&mut self, space: Size) {
+ pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
+ // Check whether this space fits.
if self.overflows(self.dimensions + Size2D::with_y(space)) {
- panic!("box layouter: would overflow in add_space");
+ return Err(LayoutError::NotEnoughSpace);
}
+ // Adjust the sizes.
self.cursor.y += space;
self.dimensions.y += space;
+
+ Ok(())
}
/// Add a sublayout at an absolute position.
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
- self.actions.push(TextAction::MoveAbsolute(position));
+ // Move all actions into this layout and translate absolute positions.
+ self.actions.reset_origin();
+ self.actions.add(TextAction::MoveAbsolute(position));
+ self.actions.set_origin(position);
self.actions.extend(layout.actions);
}
@@ -120,7 +130,7 @@ impl BoxLayouter {
} else {
self.ctx.space.dimensions
},
- actions: self.actions,
+ actions: self.actions.into_vec(),
}
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 924ebec5..5fd7b157 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -2,7 +2,7 @@
use crate::doc::TextAction;
use crate::size::Size2D;
-use super::{LayoutSpace, BoxLayout};
+use super::{BoxLayout, ActionList, LayoutSpace, LayoutResult, LayoutError};
/// A flex layout consists of a yet unarranged list of boxes.
@@ -54,7 +54,7 @@ impl FlexLayout {
}
/// Compute the justified layout.
- pub fn into_box(self) -> BoxLayout {
+ pub fn into_box(self) -> LayoutResult<BoxLayout> {
FlexFinisher::new(self).finish()
}
}
@@ -73,7 +73,7 @@ pub struct FlexContext {
struct FlexFinisher {
units: Vec<FlexUnit>,
ctx: FlexContext,
- actions: Vec<TextAction>,
+ actions: ActionList,
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
@@ -87,7 +87,7 @@ impl FlexFinisher {
FlexFinisher {
units: layout.units,
ctx: layout.ctx,
- actions: vec![],
+ actions: ActionList::new(),
dimensions: Size2D::zero(),
usable: space.usable(),
cursor: Size2D::new(space.padding.left, space.padding.top),
@@ -96,7 +96,7 @@ impl FlexFinisher {
}
/// Finish the flex layout into the justified box layout.
- fn finish(mut self) -> BoxLayout {
+ fn finish(mut self) -> LayoutResult<BoxLayout> {
// Move the units out of the layout.
let units = self.units;
self.units = vec![];
@@ -104,7 +104,7 @@ impl FlexFinisher {
// Arrange the units.
for unit in units {
match unit {
- FlexUnit::Boxed(boxed) => self.boxed(boxed),
+ FlexUnit::Boxed(boxed) => self.boxed(boxed)?,
FlexUnit::Glue(glue) => self.glue(glue),
}
}
@@ -112,29 +112,31 @@ impl FlexFinisher {
// Flush everything to get the correct dimensions.
self.newline();
- BoxLayout {
+ Ok(BoxLayout {
dimensions: if self.ctx.space.shrink_to_fit {
self.dimensions.padded(self.ctx.space.padding)
} else {
self.ctx.space.dimensions
},
- actions: self.actions,
- }
+ actions: self.actions.into_vec(),
+ })
}
/// Layout the box.
- fn boxed(&mut self, boxed: BoxLayout) {
+ fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
// Move to the next line if necessary.
if self.line.x + boxed.dimensions.x > self.usable.x {
// If it still does not fit, we stand no chance.
if boxed.dimensions.x > self.usable.x {
- panic!("flex layouter: box is to wide");
+ return Err(LayoutError::NotEnoughSpace);
}
self.newline();
}
self.append(boxed);
+
+ Ok(())
}
/// Layout the glue.
@@ -150,8 +152,10 @@ impl FlexFinisher {
/// Append a box to the layout without checking anything.
fn append(&mut self, layout: BoxLayout) {
// Move all actions into this layout and translate absolute positions.
- self.actions.push(TextAction::MoveAbsolute(self.cursor));
- self.actions.extend(super::translate_actions(self.cursor, layout.actions));
+ self.actions.reset_origin();
+ self.actions.add(TextAction::MoveAbsolute(self.cursor));
+ self.actions.set_origin(self.cursor);
+ self.actions.extend(layout.actions);
// Adjust the sizes.
self.line.x += layout.dimensions.x;
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 64eeb00a..f8819d0b 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -124,9 +124,9 @@ impl<'a, 'p> Layouter<'a, 'p> {
// Then start a new flex layouting process.
Node::Newline => {
// Finish the current paragraph into a box and add it.
- self.add_paragraph_spacing();
- let boxed = self.flex_layout.into_box();
- self.box_layouter.add_box(boxed);
+ self.add_paragraph_spacing()?;
+ let boxed = self.flex_layout.into_box()?;
+ self.box_layouter.add_box(boxed)?;
// Create a fresh flex layout for the next paragraph.
self.flex_ctx.space.dimensions = self.box_layouter.remaining();
@@ -144,48 +144,87 @@ impl<'a, 'p> Layouter<'a, 'p> {
// If there are remainings, add them to the layout.
if !self.flex_layout.is_empty() {
- self.add_paragraph_spacing();
- let boxed = self.flex_layout.into_box();
- self.box_layouter.add_box(boxed);
+ self.add_paragraph_spacing()?;
+ let boxed = self.flex_layout.into_box()?;
+ self.box_layouter.add_box(boxed)?;
}
Ok(self.box_layouter.finish())
}
/// Add the spacing between two paragraphs.
- fn add_paragraph_spacing(&mut self) {
+ fn add_paragraph_spacing(&mut self) -> LayoutResult<()> {
let size = Size::points(self.text_ctx.style.font_size)
* (self.text_ctx.style.line_spacing * self.text_ctx.style.paragraph_spacing - 1.0);
- self.box_layouter.add_space(size);
+ self.box_layouter.add_space(size)
}
}
-/// Translate a stream of text actions by an offset.
-pub fn translate_actions<I>(offset: Size2D, actions: I) -> TranslatedActions<I::IntoIter>
- where I: IntoIterator<Item=TextAction> {
- TranslatedActions { offset, iter: actions.into_iter() }
-}
-
-/// An iterator over the translated text actions, created by [`translate_actions`].
-pub struct TranslatedActions<I> where I: Iterator<Item=TextAction> {
- offset: Size2D,
- iter: I,
+/// Manipulates and optimizes a list of actions.
+#[derive(Debug, Clone)]
+pub struct ActionList {
+ actions: Vec<TextAction>,
+ origin: Size2D,
+ active_font: (usize, f32),
}
-impl<I> Iterator for TranslatedActions<I> where I: Iterator<Item=TextAction> {
- type Item = TextAction;
+impl ActionList {
+ /// Create a new action list.
+ pub fn new() -> ActionList {
+ ActionList {
+ actions: vec![],
+ origin: Size2D::zero(),
+ active_font: (std::usize::MAX, 0.0),
+ }
+ }
- fn next(&mut self) -> Option<TextAction> {
+ /// Add an action to the list if it is not useless
+ /// (like changing to a font that is already active).
+ pub fn add(&mut self, action: TextAction) {
use TextAction::*;
- self.iter.next().map(|action| match action {
- MoveAbsolute(pos) => MoveAbsolute(pos + self.offset),
- a => a,
- })
+ match action {
+ MoveAbsolute(pos) => self.actions.push(MoveAbsolute(self.origin + pos)),
+ SetFont(index, size) => if (index, size) != self.active_font {
+ self.active_font = (index, size);
+ self.actions.push(action);
+ },
+ _ => self.actions.push(action),
+ }
+ }
+
+ /// Add a series of actions.
+ pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=TextAction> {
+ for action in actions.into_iter() {
+ self.add(action);
+ }
+ }
+
+ /// Move the origin for the upcomming actions. Absolute moves will be
+ /// changed by that origin.
+ pub fn set_origin(&mut self, origin: Size2D) {
+ self.origin = origin;
+ }
+
+ /// Reset the origin to zero.
+ pub fn reset_origin(&mut self) {
+ self.origin = Size2D::zero();
+ }
+
+ /// Whether there are any actions in this list.
+ pub fn is_empty(&self) -> bool {
+ self.actions.is_empty()
+ }
+
+ /// Return the list of actions as a vector.
+ pub fn into_vec(self) -> Vec<TextAction> {
+ self.actions
}
}
/// The error type for layouting.
pub enum LayoutError {
+ /// There is not enough space to add an item.
+ NotEnoughSpace,
/// There was no suitable font for the given character.
NoSuitableFont(char),
/// An error occured while gathering font data.
@@ -198,6 +237,7 @@ pub type LayoutResult<T> = Result<T, LayoutError>;
error_type! {
err: LayoutError,
show: f => match err {
+ LayoutError::NotEnoughSpace => write!(f, "not enough space"),
LayoutError::NoSuitableFont(c) => write!(f, "no suitable font for '{}'", c),
LayoutError::Font(err) => write!(f, "font error: {}", err),
},