Deduplicate action lists ✂

This commit is contained in:
Laurenz 2019-06-21 22:12:36 +02:00
parent 968e121697
commit e39a6efccf
4 changed files with 108 additions and 54 deletions

View File

@ -7,9 +7,9 @@ edition = "2018"
[dependencies] [dependencies]
pdf = { path = "../pdf" } pdf = { path = "../pdf" }
opentype = { path = "../opentype" } opentype = { path = "../opentype" }
unicode-xid = "0.1.0"
byteorder = "1" byteorder = "1"
smallvec = "0.6.9" smallvec = "0.6.10"
unicode-xid = "0.1.0"
[[bin]] [[bin]]
name = "typst" name = "typst"

View File

@ -3,7 +3,7 @@
use crate::doc::{Document, Page, TextAction}; use crate::doc::{Document, Page, TextAction};
use crate::font::Font; use crate::font::Font;
use crate::size::{Size, Size2D}; 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. /// A box layout has a fixed width and height and composes of actions.
@ -40,7 +40,7 @@ pub struct BoxContext {
#[derive(Debug)] #[derive(Debug)]
pub struct BoxLayouter { pub struct BoxLayouter {
ctx: BoxContext, ctx: BoxContext,
actions: Vec<TextAction>, actions: ActionList,
dimensions: Size2D, dimensions: Size2D,
usable: Size2D, usable: Size2D,
cursor: Size2D, cursor: Size2D,
@ -52,7 +52,7 @@ impl BoxLayouter {
let space = ctx.space; let space = ctx.space;
BoxLayouter { BoxLayouter {
ctx, ctx,
actions: vec![], actions: ActionList::new(),
dimensions: Size2D::zero(), dimensions: Size2D::zero(),
usable: space.usable(), usable: space.usable(),
cursor: Size2D::new(space.padding.left, space.padding.right), cursor: Size2D::new(space.padding.left, space.padding.right),
@ -60,7 +60,7 @@ impl BoxLayouter {
} }
/// Add a sublayout. /// 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 // In the flow direction (vertical) add the layout and in the second
// direction just consider the maximal size of any child layout. // direction just consider the maximal size of any child layout.
let new = Size2D { let new = Size2D {
@ -68,34 +68,44 @@ impl BoxLayouter {
y: self.dimensions.y + layout.dimensions.y, y: self.dimensions.y + layout.dimensions.y,
}; };
// Check whether this box fits.
if self.overflows(new) { 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; self.dimensions = new;
// Move all actions into this layout and translate absolute positions. // Add the box.
self.actions.push(TextAction::MoveAbsolute(self.cursor)); self.add_box_absolute(self.cursor, layout);
self.actions.extend(super::translate_actions(self.cursor, layout.actions));
// Adjust the cursor. // Adjust the cursor.
self.cursor.y += layout.dimensions.y; self.cursor.y += height;
Ok(())
} }
/// Add some space in between two boxes. /// 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)) { 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.cursor.y += space;
self.dimensions.y += space; self.dimensions.y += space;
Ok(())
} }
/// Add a sublayout at an absolute position. /// Add a sublayout at an absolute position.
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) { 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); self.actions.extend(layout.actions);
} }
@ -120,7 +130,7 @@ impl BoxLayouter {
} else { } else {
self.ctx.space.dimensions self.ctx.space.dimensions
}, },
actions: self.actions, actions: self.actions.into_vec(),
} }
} }

View File

@ -2,7 +2,7 @@
use crate::doc::TextAction; use crate::doc::TextAction;
use crate::size::Size2D; 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. /// A flex layout consists of a yet unarranged list of boxes.
@ -54,7 +54,7 @@ impl FlexLayout {
} }
/// Compute the justified layout. /// Compute the justified layout.
pub fn into_box(self) -> BoxLayout { pub fn into_box(self) -> LayoutResult<BoxLayout> {
FlexFinisher::new(self).finish() FlexFinisher::new(self).finish()
} }
} }
@ -73,7 +73,7 @@ pub struct FlexContext {
struct FlexFinisher { struct FlexFinisher {
units: Vec<FlexUnit>, units: Vec<FlexUnit>,
ctx: FlexContext, ctx: FlexContext,
actions: Vec<TextAction>, actions: ActionList,
dimensions: Size2D, dimensions: Size2D,
usable: Size2D, usable: Size2D,
cursor: Size2D, cursor: Size2D,
@ -87,7 +87,7 @@ impl FlexFinisher {
FlexFinisher { FlexFinisher {
units: layout.units, units: layout.units,
ctx: layout.ctx, ctx: layout.ctx,
actions: vec![], actions: ActionList::new(),
dimensions: Size2D::zero(), dimensions: Size2D::zero(),
usable: space.usable(), usable: space.usable(),
cursor: Size2D::new(space.padding.left, space.padding.top), cursor: Size2D::new(space.padding.left, space.padding.top),
@ -96,7 +96,7 @@ impl FlexFinisher {
} }
/// Finish the flex layout into the justified box layout. /// 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. // Move the units out of the layout.
let units = self.units; let units = self.units;
self.units = vec![]; self.units = vec![];
@ -104,7 +104,7 @@ impl FlexFinisher {
// Arrange the units. // Arrange the units.
for unit in units { for unit in units {
match unit { match unit {
FlexUnit::Boxed(boxed) => self.boxed(boxed), FlexUnit::Boxed(boxed) => self.boxed(boxed)?,
FlexUnit::Glue(glue) => self.glue(glue), FlexUnit::Glue(glue) => self.glue(glue),
} }
} }
@ -112,29 +112,31 @@ impl FlexFinisher {
// Flush everything to get the correct dimensions. // Flush everything to get the correct dimensions.
self.newline(); self.newline();
BoxLayout { Ok(BoxLayout {
dimensions: if self.ctx.space.shrink_to_fit { dimensions: if self.ctx.space.shrink_to_fit {
self.dimensions.padded(self.ctx.space.padding) self.dimensions.padded(self.ctx.space.padding)
} else { } else {
self.ctx.space.dimensions self.ctx.space.dimensions
}, },
actions: self.actions, actions: self.actions.into_vec(),
} })
} }
/// Layout the box. /// Layout the box.
fn boxed(&mut self, boxed: BoxLayout) { fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
// Move to the next line if necessary. // Move to the next line if necessary.
if self.line.x + boxed.dimensions.x > self.usable.x { if self.line.x + boxed.dimensions.x > self.usable.x {
// If it still does not fit, we stand no chance. // If it still does not fit, we stand no chance.
if boxed.dimensions.x > self.usable.x { if boxed.dimensions.x > self.usable.x {
panic!("flex layouter: box is to wide"); return Err(LayoutError::NotEnoughSpace);
} }
self.newline(); self.newline();
} }
self.append(boxed); self.append(boxed);
Ok(())
} }
/// Layout the glue. /// Layout the glue.
@ -150,8 +152,10 @@ impl FlexFinisher {
/// Append a box to the layout without checking anything. /// Append a box to the layout without checking anything.
fn append(&mut self, layout: BoxLayout) { fn append(&mut self, layout: BoxLayout) {
// Move all actions into this layout and translate absolute positions. // Move all actions into this layout and translate absolute positions.
self.actions.push(TextAction::MoveAbsolute(self.cursor)); self.actions.reset_origin();
self.actions.extend(super::translate_actions(self.cursor, layout.actions)); self.actions.add(TextAction::MoveAbsolute(self.cursor));
self.actions.set_origin(self.cursor);
self.actions.extend(layout.actions);
// Adjust the sizes. // Adjust the sizes.
self.line.x += layout.dimensions.x; self.line.x += layout.dimensions.x;

View File

@ -124,9 +124,9 @@ impl<'a, 'p> Layouter<'a, 'p> {
// Then start a new flex layouting process. // Then start a new flex layouting process.
Node::Newline => { Node::Newline => {
// Finish the current paragraph into a box and add it. // Finish the current paragraph into a box and add it.
self.add_paragraph_spacing(); self.add_paragraph_spacing()?;
let boxed = self.flex_layout.into_box(); let boxed = self.flex_layout.into_box()?;
self.box_layouter.add_box(boxed); self.box_layouter.add_box(boxed)?;
// Create a fresh flex layout for the next paragraph. // Create a fresh flex layout for the next paragraph.
self.flex_ctx.space.dimensions = self.box_layouter.remaining(); 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 there are remainings, add them to the layout.
if !self.flex_layout.is_empty() { if !self.flex_layout.is_empty() {
self.add_paragraph_spacing(); self.add_paragraph_spacing()?;
let boxed = self.flex_layout.into_box(); let boxed = self.flex_layout.into_box()?;
self.box_layouter.add_box(boxed); self.box_layouter.add_box(boxed)?;
} }
Ok(self.box_layouter.finish()) Ok(self.box_layouter.finish())
} }
/// Add the spacing between two paragraphs. /// 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) 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.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. /// Manipulates and optimizes a list of actions.
pub fn translate_actions<I>(offset: Size2D, actions: I) -> TranslatedActions<I::IntoIter> #[derive(Debug, Clone)]
where I: IntoIterator<Item=TextAction> { pub struct ActionList {
TranslatedActions { offset, iter: actions.into_iter() } actions: Vec<TextAction>,
origin: Size2D,
active_font: (usize, f32),
} }
/// An iterator over the translated text actions, created by [`translate_actions`]. impl ActionList {
pub struct TranslatedActions<I> where I: Iterator<Item=TextAction> { /// Create a new action list.
offset: Size2D, pub fn new() -> ActionList {
iter: I, ActionList {
} actions: vec![],
origin: Size2D::zero(),
active_font: (std::usize::MAX, 0.0),
}
}
impl<I> Iterator for TranslatedActions<I> where I: Iterator<Item=TextAction> { /// Add an action to the list if it is not useless
type Item = TextAction; /// (like changing to a font that is already active).
pub fn add(&mut self, action: TextAction) {
fn next(&mut self) -> Option<TextAction> {
use TextAction::*; use TextAction::*;
self.iter.next().map(|action| match action { match action {
MoveAbsolute(pos) => MoveAbsolute(pos + self.offset), MoveAbsolute(pos) => self.actions.push(MoveAbsolute(self.origin + pos)),
a => a, 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. /// The error type for layouting.
pub enum LayoutError { pub enum LayoutError {
/// There is not enough space to add an item.
NotEnoughSpace,
/// There was no suitable font for the given character. /// There was no suitable font for the given character.
NoSuitableFont(char), NoSuitableFont(char),
/// An error occured while gathering font data. /// An error occured while gathering font data.
@ -198,6 +237,7 @@ pub type LayoutResult<T> = Result<T, LayoutError>;
error_type! { error_type! {
err: LayoutError, err: LayoutError,
show: f => match err { show: f => match err {
LayoutError::NotEnoughSpace => write!(f, "not enough space"),
LayoutError::NoSuitableFont(c) => write!(f, "no suitable font for '{}'", c), LayoutError::NoSuitableFont(c) => write!(f, "no suitable font for '{}'", c),
LayoutError::Font(err) => write!(f, "font error: {}", err), LayoutError::Font(err) => write!(f, "font error: {}", err),
}, },