mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Deduplicate action lists ✂
This commit is contained in:
parent
968e121697
commit
e39a6efccf
@ -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"
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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),
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user