diff --git a/src/func/mod.rs b/src/func/mod.rs index f59c1ec0..27dceee3 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -20,6 +20,7 @@ pub mod prelude { pub use crate::size::{Size, Size2D, SizeBox}; pub use crate::style::{PageStyle, TextStyle}; pub use super::helpers::*; + pub use Command::*; } /// Typesetting function types. @@ -93,6 +94,9 @@ pub enum Command<'a> { Add(Layout), AddMultiple(MultiLayout), + AddPrimarySpace(Size), + AddSecondarySpace(Size), + FinishRun, FinishBox, FinishLayout, diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 3155a13a..bae700ce 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -67,7 +67,7 @@ impl FlexLayouter { /// Create a new flex layouter. pub fn new(ctx: FlexContext) -> FlexLayouter { let stack = StackLayouter::new(StackContext { - spaces: ctx.spaces, + spaces: ctx.spaces.clone(), axes: ctx.axes, shrink_to_fit: ctx.shrink_to_fit, }); @@ -118,7 +118,7 @@ impl FlexLayouter { } /// Update the axes in use by this flex layouter. - pub fn set_axes(&self, axes: LayoutAxes) { + pub fn set_axes(&mut self, axes: LayoutAxes) { self.units.push(FlexUnit::SetAxes(axes)); } @@ -246,7 +246,7 @@ impl FlexLayouter { Ok(()) } - fn finish_aligned_run(&mut self) -> LayoutResult<()> { + fn finish_aligned_run(&mut self) { let anchor = self.ctx.axes.primary.anchor(self.merged_dimensions.x); let factor = if self.ctx.axes.primary.axis.is_positive() { 1 } else { -1 }; @@ -259,13 +259,11 @@ impl FlexLayouter { self.merged_dimensions.y = crate::size::max(self.merged_dimensions.y, self.run.size.y); self.run.size = Size2D::zero(); - - Ok(()) } /// This layouter's context. - pub fn ctx(&self) -> FlexContext { - self.ctx + pub fn ctx(&self) -> &FlexContext { + &self.ctx } pub fn remaining(&self) -> LayoutResult { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 420c90bc..fb63ff86 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,6 +1,5 @@ //! The core layouting engine. -use std::borrow::Cow; use std::io::{self, Write}; use smallvec::SmallVec; diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index 77af6f38..8113a4b7 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -93,7 +93,7 @@ impl StackLayouter { } /// Update the axes in use by this stack layouter. - pub fn set_axes(&self, axes: LayoutAxes) { + pub fn set_axes(&mut self, axes: LayoutAxes) { if axes != self.ctx.axes { self.finish_boxes(); self.usable = self.remains(); @@ -171,8 +171,8 @@ impl StackLayouter { } /// This layouter's context. - pub fn ctx(&self) -> StackContext { - self.ctx + pub fn ctx(&self) -> &StackContext { + &self.ctx } /// The (generalized) usable area of the current space. diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 4742de23..11e83209 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -11,21 +11,21 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { ctx: LayoutContext<'a, 'p>, flex: FlexLayouter, - style: Cow<'a, TextStyle>, + style: TextStyle, } impl<'a, 'p> TreeLayouter<'a, 'p> { /// Create a new layouter. fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { - ctx, flex: FlexLayouter::new(FlexContext { flex_spacing: flex_spacing(&ctx.style), - spaces: ctx.spaces, + spaces: ctx.spaces.clone(), axes: ctx.axes, shrink_to_fit: ctx.shrink_to_fit, }), - style: Cow::Borrowed(ctx.style), + style: ctx.style.clone(), + ctx, } } @@ -52,9 +52,9 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } } - Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic), - Node::ToggleBold => self.style.to_mut().toggle_class(FontClass::Bold), - Node::ToggleMonospace => self.style.to_mut().toggle_class(FontClass::Monospace), + Node::ToggleItalics => self.style.toggle_class(FontClass::Italic), + Node::ToggleBold => self.style.toggle_class(FontClass::Bold), + Node::ToggleMonospace => self.style.toggle_class(FontClass::Monospace), Node::Func(func) => self.layout_func(func)?, } @@ -85,13 +85,16 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::Add(layout) => self.flex.add(layout), Command::AddMultiple(layouts) => self.flex.add_multiple(layouts), + Command::AddPrimarySpace(space) => self.flex.add_primary_space(space), + Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space)?, + Command::FinishRun => self.flex.add_run_break(), Command::FinishBox => self.flex.finish_box()?, Command::FinishLayout => self.flex.finish_layout(true)?, Command::BreakParagraph => self.break_paragraph()?, - Command::SetStyle(style) => *self.style.to_mut() = style, + Command::SetStyle(style) => self.style = style, Command::SetAxes(axes) => { self.flex.set_axes(axes); self.ctx.axes = axes; diff --git a/src/library/axes.rs b/src/library/axes.rs new file mode 100644 index 00000000..62e0078f --- /dev/null +++ b/src/library/axes.rs @@ -0,0 +1,43 @@ +use crate::func::prelude::*; + +/// 📐 `align`: Aligns content in different ways. +#[derive(Debug, PartialEq)] +pub struct Align { + body: Option, + alignment: Alignment, +} + +function! { + data: Align, + + parse(args, body, ctx) { + let body = parse!(optional: body, ctx); + let arg = args.get_pos::()?; + let alignment = match arg.val { + "left" | "origin" => Alignment::Origin, + "center" => Alignment::Center, + "right" | "end" => Alignment::End, + s => err!("invalid alignment specifier: {}", s), + }; + args.done()?; + + Ok(Align { + body, + alignment, + }) + } + + layout(this, ctx) { + let mut new_axes = ctx.axes; + new_axes.primary.alignment = this.alignment; + + Ok(match &this.body { + Some(body) => commands![ + SetAxes(new_axes), + LayoutTree(body), + SetAxes(ctx.axes), + ], + None => commands![Command::SetAxes(new_axes)] + }) + } +} diff --git a/src/library/boxed.rs b/src/library/boxed.rs new file mode 100644 index 00000000..e8debca4 --- /dev/null +++ b/src/library/boxed.rs @@ -0,0 +1,21 @@ +use crate::func::prelude::*; + +/// `box`: Layouts content into a box. +#[derive(Debug, PartialEq)] +pub struct Boxed { + body: SyntaxTree, +} + +function! { + data: Boxed, + + parse(args, body, ctx) { + args.done()?; + let body = parse!(required: body, ctx); + Ok(Boxed { body }) + } + + layout(this, ctx) { + Ok(commands![AddMultiple(layout_tree(&this.body, ctx)?)]) + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index 74f77204..d795d488 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -2,22 +2,23 @@ use crate::func::Scope; -mod structure; -mod style; - -pub use structure::*; -pub use style::*; +pub_use_mod!(boxed); +pub_use_mod!(axes); +pub_use_mod!(spacing); +pub_use_mod!(style); /// Create a scope with all standard functions. pub fn std() -> Scope { let mut std = Scope::new(); - std.add::("align"); std.add::("box"); - std.add::("line.break"); - std.add::("n"); - std.add::("page.break"); + std.add::("align"); + + std.add::("n"); + std.add::("line.break"); + std.add::("paragraph.break"); + std.add::("page.break"); std.add::("h"); std.add::("v"); diff --git a/src/library/spacing.rs b/src/library/spacing.rs new file mode 100644 index 00000000..afd26d80 --- /dev/null +++ b/src/library/spacing.rs @@ -0,0 +1,81 @@ +use crate::func::prelude::*; + +/// `line.break`, `n`: Ends the current line. +#[derive(Debug, PartialEq)] +pub struct LineBreak; + +function! { + data: LineBreak, + parse: plain, + layout(_, _) { Ok(commands![FinishRun]) } +} + +/// `paragraph.break`: Ends the current paragraph. +/// +/// This has the same effect as two subsequent newlines. +#[derive(Debug, PartialEq)] +pub struct ParagraphBreak; + +function! { + data: ParagraphBreak, + parse: plain, + layout(_, _) { Ok(commands![FinishBox]) } +} + +/// `page.break`: Ends the current page. +#[derive(Debug, PartialEq)] +pub struct PageBreak; + +function! { + data: PageBreak, + parse: plain, + layout(_, _) { Ok(commands![FinishLayout]) } +} + +macro_rules! space_func { + ($ident:ident, $doc:expr, $var:ident => $command:expr) => ( + #[doc = $doc] + #[derive(Debug, PartialEq)] + pub struct $ident(Spacing); + + function! { + data: $ident, + + parse(args, body, _ctx) { + parse!(forbidden: body); + + let arg = args.get_pos::()?; + let spacing = match arg.val { + Expression::Size(s) => Spacing::Absolute(*s), + Expression::Num(f) => Spacing::Relative(*f as f32), + _ => err!("invalid spacing, expected size or number"), + }; + + Ok($ident(spacing)) + } + + layout(this, ctx) { + let $var = match this.0 { + Spacing::Absolute(s) => s, + Spacing::Relative(f) => f * ctx.style.font_size, + }; + + Ok(commands![$command]) + } + } + ); +} + +/// Absolute or font-relative spacing. +#[derive(Debug, PartialEq)] +enum Spacing { + Absolute(Size), + Relative(f32), +} + +// FIXME: h != primary and v != secondary. +space_func!(HorizontalSpace, "📖 `h`: Adds horizontal whitespace.", + space => AddPrimarySpace(space)); + +space_func!(VerticalSpace, "📑 `v`: Adds vertical whitespace.", + space => AddSecondarySpace(space)); diff --git a/src/library/structure.rs b/src/library/structure.rs deleted file mode 100644 index 2dbf33ff..00000000 --- a/src/library/structure.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::func::prelude::*; -use Command::*; - -/// ↩ `line.break`, `n`: Ends the current line. -#[derive(Debug, PartialEq)] -pub struct Linebreak; - -function! { - data: Linebreak, - parse: plain, - layout(_, _) { Ok(commands![BreakFlex]) } -} - -/// ↕ `paragraph.break`: Ends the current paragraph. -/// -/// This has the same effect as two subsequent newlines. -#[derive(Debug, PartialEq)] -pub struct Parbreak; - -function! { - data: Parbreak, - parse: plain, - layout(_, _) { Ok(commands![FinishFlex]) } -} - -/// 📜 `page.break`: Ends the current page. -#[derive(Debug, PartialEq)] -pub struct Pagebreak; - -function! { - data: Pagebreak, - parse: plain, - layout(_, _) { Ok(commands![BreakStack]) } -} - -/// 📐 `align`: Aligns content in different ways. -/// -/// **Positional arguments:** -/// - `left`, `right` or `center` _(required)_. -#[derive(Debug, PartialEq)] -pub struct Align { - body: Option, - alignment: Alignment, -} - -function! { - data: Align, - - parse(args, body, ctx) { - let body = parse!(optional: body, ctx); - let arg = args.get_pos::()?; - let alignment = match arg.val { - "left" => Alignment::Left, - "right" => Alignment::Right, - "center" => Alignment::Center, - s => err!("invalid alignment specifier: {}", s), - }; - args.done()?; - - Ok(Align { - body, - alignment, - }) - } - - layout(this, ctx) { - Ok(commands![match &this.body { - Some(body) => { - AddMany(layout_tree(body, LayoutContext { - alignment: this.alignment, - .. ctx - })?) - } - None => SetAlignment(this.alignment) - }]) - } -} - -/// 📦 `box`: Layouts content into a box. -/// -/// **Positional arguments:** None. -/// -/// **Keyword arguments:** -/// - flow: either `horizontal` or `vertical` _(optional)_. -#[derive(Debug, PartialEq)] -pub struct Boxed { - body: SyntaxTree, - flow: Flow, -} - -function! { - data: Boxed, - - parse(args, body, ctx) { - let body = parse!(required: body, ctx); - - let mut flow = Flow::Vertical; - if let Some(ident) = args.get_key_opt::("flow")? { - flow = match ident.val { - "vertical" => Flow::Vertical, - "horizontal" => Flow::Horizontal, - f => err!("invalid flow specifier: {}", f), - }; - } - args.done()?; - - Ok(Boxed { - body, - flow, - }) - } - - layout(this, ctx) { - Ok(commands![ - AddMany(layout_tree(&this.body, LayoutContext { - flow: this.flow, - .. ctx - })?) - ]) - } -} - -macro_rules! spacefunc { - ($ident:ident, $doc:expr, $var:ident => $command:expr) => ( - #[doc = $doc] - /// - /// **Positional arguments:** - /// - Spacing as a size or number, which is interpreted as a multiple - /// of the font size _(required)_. - #[derive(Debug, PartialEq)] - pub struct $ident(Spacing); - - function! { - data: $ident, - - parse(args, body, _ctx) { - parse!(forbidden: body); - - let arg = args.get_pos::()?; - let spacing = match arg.val { - Expression::Size(s) => Spacing::Absolute(*s), - Expression::Num(f) => Spacing::Relative(*f as f32), - _ => err!("invalid spacing, expected size or number"), - }; - - Ok($ident(spacing)) - } - - layout(this, ctx) { - let $var = match this.0 { - Spacing::Absolute(s) => s, - Spacing::Relative(f) => f * ctx.style.font_size, - }; - - Ok(commands![$command]) - } - } - ); -} - -/// Absolute or font-relative spacing. -#[derive(Debug, PartialEq)] -enum Spacing { - Absolute(Size), - Relative(f32), -} - -spacefunc!(HorizontalSpace, "📖 `h`: Adds horizontal whitespace.", - space => AddFlex(Layout::empty(space, Size::zero()))); - -spacefunc!(VerticalSpace, "📑 `v`: Adds vertical whitespace.", - space => Add(Layout::empty(Size::zero(), space))); diff --git a/src/library/style.rs b/src/library/style.rs index 9bcdcccd..90a5fd31 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -23,17 +23,17 @@ macro_rules! stylefunc { Ok(match &this.body { Some(body) => commands![ - Command::SetStyle(new_style), - Command::LayoutTree(body), - Command::SetStyle(ctx.style.clone()), + SetStyle(new_style), + LayoutTree(body), + SetStyle(ctx.style.clone()), ], - None => commands![Command::SetStyle(new_style)] + None => commands![SetStyle(new_style)] }) } } ); } -stylefunc!(Italic, "💡 `italic`: Sets text in _italics_."); -stylefunc!(Bold, "🧱 `bold`: Sets text in **bold**."); -stylefunc!(Monospace, "👩‍💻 `mono`: Sets text in `monospace`."); +stylefunc!(Italic, "`italic`: Sets text in _italics_."); +stylefunc!(Bold, "`bold`: Sets text in **bold**."); +stylefunc!(Monospace, "`mono`: Sets text in `monospace`."); diff --git a/src/macros.rs b/src/macros.rs index 70c67105..e7113672 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -57,3 +57,10 @@ macro_rules! debug_display { } ); } + +macro_rules! pub_use_mod { + ($name:ident) => { + mod $name; + pub use $name::*; + }; +} diff --git a/tests/layouting.rs b/tests/layouting.rs index 1a406c8c..27999d43 100644 --- a/tests/layouting.rs +++ b/tests/layouting.rs @@ -77,7 +77,7 @@ fn test(name: &str, src: &str) { // Make run warm. #[cfg(not(debug_assertions))] let warmup_start = Instant::now(); - typesetter.typeset(&src).unwrap(); + #[cfg(not(debug_assertions))] typesetter.typeset(&src).unwrap(); #[cfg(not(debug_assertions))] let warmup_end = Instant::now(); // Layout into box layout.