summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/func.rs72
-rw-r--r--src/layout/boxed.rs32
-rw-r--r--src/layout/flex.rs78
-rw-r--r--src/layout/mod.rs28
-rw-r--r--src/lib.rs5
-rw-r--r--src/library/align.rs51
-rw-r--r--src/library/mod.rs34
-rw-r--r--src/library/styles.rs64
8 files changed, 247 insertions, 117 deletions
diff --git a/src/func.rs b/src/func.rs
index c8e464f8..776f0e98 100644
--- a/src/func.rs
+++ b/src/func.rs
@@ -3,12 +3,10 @@
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
-use toddle::query::FontClass;
-use crate::layout::{layout, Layout, LayoutContext, LayoutResult};
-use crate::layout::flex::FlexLayout;
-use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
-use crate::syntax::{SyntaxTree, FuncHeader};
+use crate::layout::{Layout, LayoutContext, LayoutResult};
+use crate::parsing::{ParseContext, ParseResult};
+use crate::syntax::FuncHeader;
/// Typesetting function types.
@@ -79,11 +77,7 @@ impl Scope {
/// Create a new scope with the standard functions contained.
pub fn with_std() -> Scope {
- let mut std = Scope::new();
- std.add::<BoldFunc>("bold");
- std.add::<ItalicFunc>("italic");
- std.add::<MonospaceFunc>("mono");
- std
+ crate::library::std()
}
/// Add a function type to the scope giving it a name.
@@ -108,61 +102,3 @@ impl Debug for Scope {
write!(f, "{:?}", self.parsers.keys())
}
}
-
-/// Creates style functions like bold and italic.
-macro_rules! style_func {
- ($(#[$outer:meta])* pub struct $struct:ident { $name:expr },
- $style:ident => $style_change:block) => {
- $(#[$outer])*
- #[derive(Debug, PartialEq)]
- pub struct $struct { body: SyntaxTree }
- impl Function for $struct {
- fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
- -> ParseResult<Self> where Self: Sized {
- // Accept only invocations without arguments and with body.
- if header.args.is_empty() && header.kwargs.is_empty() {
- if let Some(body) = body {
- Ok($struct { body: parse(body, ctx)? })
- } else {
- Err(ParseError::new(format!("expected body for function `{}`", $name)))
- }
- } else {
- Err(ParseError::new(format!("unexpected arguments to function `{}`", $name)))
- }
- }
-
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
- // Change the context.
- let mut $style = ctx.style.clone();
- $style_change
-
- // Create a box and put it into a flex layout.
- let boxed = layout(&self.body, LayoutContext {
- style: &$style,
- .. ctx
- })?;
- let flex = FlexLayout::from_box(boxed);
-
- Ok(Some(Layout::Flex(flex)))
- }
- }
- };
-}
-
-style_func! {
- /// Typesets text in bold.
- pub struct BoldFunc { "bold" },
- style => { style.toggle_class(FontClass::Bold) }
-}
-
-style_func! {
- /// Typesets text in italics.
- pub struct ItalicFunc { "italic" },
- style => { style.toggle_class(FontClass::Italic) }
-}
-
-style_func! {
- /// Typesets text in monospace.
- pub struct MonospaceFunc { "mono" },
- style => { style.toggle_class(FontClass::Monospace) }
-}
diff --git a/src/layout/boxed.rs b/src/layout/boxed.rs
index a860b267..afcd5278 100644
--- a/src/layout/boxed.rs
+++ b/src/layout/boxed.rs
@@ -2,7 +2,7 @@
use crate::doc::{Document, Page, LayoutAction};
use crate::size::{Size, Size2D};
-use super::{ActionList, LayoutSpace, LayoutResult, LayoutError};
+use super::{ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
/// A box layout has a fixed width and height and composes of actions.
@@ -37,7 +37,7 @@ pub struct BoxContext {
/// Layouts boxes block-style.
#[derive(Debug)]
pub struct BoxLayouter {
- ctx: BoxContext,
+ pub ctx: BoxContext,
actions: ActionList,
dimensions: Size2D,
usable: Size2D,
@@ -51,9 +51,15 @@ impl BoxLayouter {
BoxLayouter {
ctx,
actions: ActionList::new(),
- dimensions: Size2D::zero(),
+ 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.right),
+ cursor: Size2D::new(match ctx.space.alignment {
+ Alignment::Left => space.padding.left,
+ Alignment::Right => space.dimensions.x - space.padding.right,
+ }, space.padding.top),
}
}
@@ -71,12 +77,18 @@ impl BoxLayouter {
return Err(LayoutError::NotEnoughSpace);
}
- // Apply the dimensions as they fit.
- let height = layout.dimensions.y;
+ // Apply the dimensions if they fit.
self.dimensions = new;
+ let width = layout.dimensions.x;
+ let height = layout.dimensions.y;
+
+ let position = match self.ctx.space.alignment {
+ Alignment::Left => self.cursor,
+ Alignment::Right => self.cursor - Size2D::with_x(width),
+ };
// Add the box.
- self.add_box_absolute(self.cursor, layout);
+ self.add_box_absolute(position, layout);
// Adjust the cursor.
self.cursor.y += height;
@@ -86,11 +98,7 @@ impl BoxLayouter {
/// Add a sublayout at an absolute position.
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
- // Move all actions into this layout and translate absolute positions.
- self.actions.reset_origin();
- self.actions.add(LayoutAction::MoveAbsolute(position));
- self.actions.set_origin(position);
- self.actions.extend(layout.actions);
+ self.actions.add_box_absolute(position, layout);
}
/// Add some space in between two boxes.
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 730b876e..8b692691 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -1,8 +1,7 @@
//! Flexible and lazy layouting of boxes.
-use crate::doc::LayoutAction;
use crate::size::{Size, Size2D};
-use super::{BoxLayout, ActionList, LayoutSpace, LayoutResult, LayoutError};
+use super::{BoxLayout, ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
/// A flex layout consists of a yet unarranged list of boxes.
@@ -81,7 +80,9 @@ struct FlexFinisher {
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
- line: Size2D,
+ line_metrics: Size2D,
+ line_content: Vec<(Size2D, BoxLayout)>,
+ glue: Option<BoxLayout>,
}
impl FlexFinisher {
@@ -92,10 +93,15 @@ impl FlexFinisher {
units: layout.units,
ctx,
actions: ActionList::new(),
- dimensions: Size2D::zero(),
+ 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: Size2D::zero(),
+ line_metrics: Size2D::zero(),
+ line_content: vec![],
+ glue: None,
}
}
@@ -128,14 +134,20 @@ impl FlexFinisher {
/// Layout the box.
fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
+ let last_glue_x = self.glue.as_ref()
+ .map(|g| g.dimensions.x)
+ .unwrap_or(Size::zero());
+
// Move to the next line if necessary.
- if self.line.x + boxed.dimensions.x > self.usable.x {
+ if self.line_metrics.x + boxed.dimensions.x + last_glue_x > self.usable.x {
// If it still does not fit, we stand no chance.
if boxed.dimensions.x > self.usable.x {
return Err(LayoutError::NotEnoughSpace);
}
self.newline();
+ } else if let Some(glue) = self.glue.take() {
+ self.append(glue);
}
self.append(boxed);
@@ -145,37 +157,51 @@ impl FlexFinisher {
/// Layout the glue.
fn glue(&mut self, glue: BoxLayout) {
- // Only add the glue if it fits on the line, otherwise move to the next line.
- if self.line.x + glue.dimensions.x > self.usable.x {
- self.newline();
- } else {
- self.append(glue);
- }
+ self.glue = Some(glue);
}
/// 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.reset_origin();
- self.actions.add(LayoutAction::MoveAbsolute(self.cursor));
- self.actions.set_origin(self.cursor);
- self.actions.extend(layout.actions);
-
- // Adjust the sizes.
- self.line.x += layout.dimensions.x;
- self.line.y = crate::size::max(self.line.y, layout.dimensions.y);
- self.cursor.x += layout.dimensions.x;
+ let dim = layout.dimensions;
+ self.line_content.push((self.cursor, layout));
+
+ self.line_metrics.x += dim.x;
+ self.line_metrics.y = crate::size::max(self.line_metrics.y, dim.y);
+ self.cursor.x += dim.x;
}
/// Move to the next line.
fn newline(&mut self) {
- self.dimensions.x = crate::size::max(self.dimensions.x, self.line.x);
+ // Move all actions into this layout and translate absolute positions.
+ let remaining_space = Size2D::with_x(self.ctx.space.usable().x - self.line_metrics.x);
+ for (cursor, layout) in self.line_content.drain(..) {
+ let position = match self.ctx.space.alignment {
+ Alignment::Left => cursor,
+ Alignment::Right => {
+ // Right align everything by shifting it right by the
+ // amount of space left to the right of the line.
+ cursor + remaining_space
+ },
+ };
+
+ self.actions.add_box_absolute(position, layout);
+ }
+
+ // Stretch the dimensions to at least the line width.
+ self.dimensions.x = crate::size::max(self.dimensions.x, self.line_metrics.x);
+
+ // If we wrote a line previously add the inter-line spacing.
if self.dimensions.y > Size::zero() {
self.dimensions.y += self.ctx.flex_spacing;
}
- self.dimensions.y += self.line.y;
+
+ self.dimensions.y += self.line_metrics.y;
+
+ // Reset the cursor the left and move down by the line and the inter-line spacing.
self.cursor.x = self.ctx.space.padding.left;
- self.cursor.y += self.line.y + self.ctx.flex_spacing;
- self.line = Size2D::zero();
+ self.cursor.y += self.line_metrics.y + self.ctx.flex_spacing;
+
+ // Reset the current line metrics.
+ self.line_metrics = Size2D::zero();
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 6b040089..e5fdc42d 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -52,11 +52,20 @@ pub struct LayoutSpace {
pub dimensions: Size2D,
/// Padding that should be respected on each side.
pub padding: SizeBox,
+ /// 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.
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 {
@@ -157,6 +166,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
space: LayoutSpace {
dimensions: self.box_layouter.remaining(),
padding: SizeBox::zero(),
+ alignment: self.box_layouter.ctx.space.alignment,
shrink_to_fit: true,
},
flex_spacing: (self.style.line_spacing - 1.0) * Size::pt(self.style.font_size),
@@ -173,6 +183,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
space: LayoutSpace {
dimensions: self.box_layouter.remaining(),
padding: SizeBox::zero(),
+ alignment: self.box_layouter.ctx.space.alignment,
shrink_to_fit: true,
},
})?;
@@ -196,8 +207,8 @@ impl<'a, 'p> Layouter<'a, 'p> {
/// Manipulates and optimizes a list of actions.
#[derive(Debug, Clone)]
pub struct ActionList {
+ pub origin: Size2D,
actions: Vec<LayoutAction>,
- origin: Size2D,
active_font: (usize, f32),
}
@@ -232,15 +243,12 @@ impl ActionList {
}
}
- /// 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();
+ /// 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_absolute(&mut self, position: Size2D, layout: BoxLayout) {
+ self.actions.push(LayoutAction::MoveAbsolute(position));
+ self.origin = position;
+ self.extend(layout.actions);
}
/// Whether there are any actions in this list.
diff --git a/src/lib.rs b/src/lib.rs
index 1a97c560..cb4be8b4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,7 +20,7 @@ use toddle::query::{FontLoader, SharedFontLoader, FontProvider};
use crate::doc::Document;
use crate::func::Scope;
use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
-use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult};
+use crate::layout::{layout, LayoutContext, Alignment, LayoutSpace, LayoutError, LayoutResult};
use crate::layout::boxed::BoxLayout;
use crate::style::{PageStyle, TextStyle};
use crate::syntax::SyntaxTree;
@@ -35,6 +35,7 @@ pub mod parsing;
pub mod size;
pub mod style;
pub mod syntax;
+pub mod library;
/// Transforms source code into typesetted documents.
@@ -92,6 +93,7 @@ impl<'p> Typesetter<'p> {
space: LayoutSpace {
dimensions: self.page_style.dimensions,
padding: self.page_style.margins,
+ alignment: Alignment::Left,
shrink_to_fit: false,
},
})?;
@@ -184,5 +186,6 @@ mod test {
#[test]
fn shakespeare() {
test("shakespeare", include_str!("../test/shakespeare.tps"));
+ test("shakespeare-right", &format!("[align:right][{}]", include_str!("../test/shakespeare.tps")));
}
}
diff --git a/src/library/align.rs b/src/library/align.rs
new file mode 100644
index 00000000..4b2ee8c3
--- /dev/null
+++ b/src/library/align.rs
@@ -0,0 +1,51 @@
+//! Alignment function.
+
+use super::prelude::*;
+use crate::layout::Alignment;
+
+
+/// Allows to align content in different ways.
+#[derive(Debug, PartialEq)]
+pub struct AlignFunc {
+ alignment: Alignment,
+ body: Option<SyntaxTree>,
+}
+
+impl Function for AlignFunc {
+ fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
+ -> ParseResult<Self> where Self: Sized {
+
+ if header.args.len() != 1 || !header.kwargs.is_empty() {
+ return err("expected exactly one positional argument specifying the alignment");
+ }
+
+ let alignment = if let Expression::Ident(ident) = &header.args[0] {
+ match ident.as_str() {
+ "left" => Alignment::Left,
+ "right" => Alignment::Right,
+ s => return err(format!("invalid alignment specifier: '{}'", s)),
+ }
+ } else {
+ return err(format!("expected alignment specifier, found: '{}'", header.args[0]));
+ };
+
+ let body = if let Some(body) = body {
+ Some(parse(body, ctx)?)
+ } else {
+ None
+ };
+
+ Ok(AlignFunc { alignment, body })
+ }
+
+ fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
+ if let Some(body) = &self.body {
+ // Override the previous alignment and do the layouting.
+ ctx.space.alignment = self.alignment;
+ layout(body, ctx)
+ .map(|l| Some(Layout::Boxed(l)))
+ } else {
+ unimplemented!("context-modifying align func")
+ }
+ }
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
new file mode 100644
index 00000000..9323c4c0
--- /dev/null
+++ b/src/library/mod.rs
@@ -0,0 +1,34 @@
+//! The standard library for the _Typst_ language.
+
+use crate::func::Scope;
+
+mod align;
+mod styles;
+
+/// Useful imports for creating your own functions.
+pub mod prelude {
+ pub use crate::syntax::{SyntaxTree, FuncHeader, Expression};
+ pub use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
+ pub use crate::layout::{layout, Layout, LayoutContext, LayoutResult, LayoutError};
+ pub use crate::layout::flex::FlexLayout;
+ pub use crate::layout::boxed::BoxLayout;
+ pub use crate::func::Function;
+
+ pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
+ Err(ParseError::new(message))
+ }
+}
+
+pub use align::AlignFunc;
+pub use styles::{ItalicFunc, BoldFunc, MonospaceFunc};
+
+
+/// Create a scope with all standard functions.
+pub fn std() -> Scope {
+ let mut std = Scope::new();
+ std.add::<BoldFunc>("bold");
+ std.add::<ItalicFunc>("italic");
+ std.add::<MonospaceFunc>("mono");
+ std.add::<AlignFunc>("align");
+ std
+}
diff --git a/src/library/styles.rs b/src/library/styles.rs
new file mode 100644
index 00000000..c84b9fed
--- /dev/null
+++ b/src/library/styles.rs
@@ -0,0 +1,64 @@
+//! Basic style functions: bold, italic, monospace.
+
+use super::prelude::*;
+use toddle::query::FontClass;
+
+
+
+macro_rules! style_func {
+ ($(#[$outer:meta])* pub struct $struct:ident { $name:expr },
+ $style:ident => $style_change:block) => {
+ $(#[$outer])*
+ #[derive(Debug, PartialEq)]
+ pub struct $struct { body: SyntaxTree }
+
+ impl Function for $struct {
+ fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
+ -> ParseResult<Self> where Self: Sized {
+ // Accept only invocations without arguments and with body.
+ if header.args.is_empty() && header.kwargs.is_empty() {
+ if let Some(body) = body {
+ Ok($struct { body: parse(body, ctx)? })
+ } else {
+ Err(ParseError::new(format!("expected body for function `{}`", $name)))
+ }
+ } else {
+ Err(ParseError::new(format!("unexpected arguments to function `{}`", $name)))
+ }
+ }
+
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
+ // Change the context.
+ let mut $style = ctx.style.clone();
+ $style_change
+
+ // Create a box and put it into a flex layout.
+ let boxed = layout(&self.body, LayoutContext {
+ style: &$style,
+ .. ctx
+ })?;
+ let flex = FlexLayout::from_box(boxed);
+
+ Ok(Some(Layout::Flex(flex)))
+ }
+ }
+ };
+}
+
+style_func! {
+ /// Typesets text in bold.
+ pub struct BoldFunc { "bold" },
+ style => { style.toggle_class(FontClass::Bold) }
+}
+
+style_func! {
+ /// Typesets text in italics.
+ pub struct ItalicFunc { "italic" },
+ style => { style.toggle_class(FontClass::Italic) }
+}
+
+style_func! {
+ /// Typesets text in monospace.
+ pub struct MonospaceFunc { "mono" },
+ style => { style.toggle_class(FontClass::Monospace) }
+}