summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/func.rs66
-rw-r--r--src/layout/flex.rs4
-rw-r--r--src/layout/stacked.rs1
-rw-r--r--src/layout/tree.rs18
-rw-r--r--src/lib.rs1
-rw-r--r--src/library/align.rs17
-rw-r--r--src/library/breaks.rs24
-rw-r--r--src/library/mod.rs28
-rw-r--r--src/library/styles.rs36
9 files changed, 130 insertions, 65 deletions
diff --git a/src/func.rs b/src/func.rs
index e2391a55..eaaa476d 100644
--- a/src/func.rs
+++ b/src/func.rs
@@ -35,6 +35,35 @@ impl PartialEq for dyn Function {
}
}
+/// A helper trait that describes requirements for types that can implement
+/// [`Function`].
+///
+/// Automatically implemented for all types which fulfill to the bounds `Debug +
+/// PartialEq + 'static`. There should be no need to implement this manually.
+pub trait FunctionBounds: Debug {
+ /// Cast self into `Any`.
+ fn help_cast_as_any(&self) -> &dyn Any;
+
+ /// Compare self with another function.
+ fn help_eq(&self, other: &dyn Function) -> bool;
+}
+
+impl<T> FunctionBounds for T
+where T: Debug + PartialEq + 'static
+{
+ fn help_cast_as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn help_eq(&self, other: &dyn Function) -> bool {
+ if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
+ self == other
+ } else {
+ false
+ }
+ }
+}
+
/// A sequence of commands requested for execution by a function.
#[derive(Debug)]
pub struct CommandList<'a> {
@@ -47,6 +76,11 @@ impl<'a> CommandList<'a> {
CommandList { commands: vec![] }
}
+ /// Create a command list with commands from a vector.
+ pub fn from_vec(commands: Vec<Command<'a>>) -> CommandList<'a> {
+ CommandList { commands }
+ }
+
/// Add a command to the sequence.
pub fn add(&mut self, command: Command<'a>) {
self.commands.push(command);
@@ -84,35 +118,13 @@ pub enum Command<'a> {
AddMany(MultiLayout),
SetAlignment(Alignment),
SetStyle(TextStyle),
+ FinishLayout,
}
-/// A helper trait that describes requirements for types that can implement
-/// [`Function`].
-///
-/// Automatically implemented for all types which fulfill to the bounds `Debug +
-/// PartialEq + 'static`. There should be no need to implement this manually.
-pub trait FunctionBounds: Debug {
- /// Cast self into `Any`.
- fn help_cast_as_any(&self) -> &dyn Any;
-
- /// Compare self with another function.
- fn help_eq(&self, other: &dyn Function) -> bool;
-}
-
-impl<T> FunctionBounds for T
-where T: Debug + PartialEq + 'static
-{
- fn help_cast_as_any(&self) -> &dyn Any {
- self
- }
-
- fn help_eq(&self, other: &dyn Function) -> bool {
- if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
- self == other
- } else {
- false
- }
- }
+macro_rules! commands {
+ ($($x:expr),*$(,)*) => ({
+ $crate::func::CommandList::from_vec(vec![$($x,)*])
+ });
}
/// A map from identifiers to functions.
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 98cca2f9..39c16aef 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -17,6 +17,7 @@ use super::*;
/// flows into a new line. A _glue_ layout is typically used for a space character
/// since it prevents a space from appearing in the beginning or end of a line.
/// However, it can be any layout.
+#[derive(Debug, Clone)]
pub struct FlexLayouter {
ctx: FlexContext,
units: Vec<FlexUnit>,
@@ -64,6 +65,7 @@ impl FlexContext {
}
}
+#[derive(Debug, Clone)]
enum FlexUnit {
/// A content unit to be arranged flexibly.
Boxed(Layout),
@@ -73,6 +75,7 @@ enum FlexUnit {
Glue(Size2D),
}
+#[derive(Debug, Clone)]
struct FlexRun {
content: Vec<(Size, Layout)>,
size: Size2D,
@@ -168,7 +171,6 @@ impl FlexLayouter {
}
fn layout_glue(&mut self, glue: Size2D) {
- self.flush_glue();
self.cached_glue = Some(glue);
}
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs
index 0b7bfd4f..d87e5394 100644
--- a/src/layout/stacked.rs
+++ b/src/layout/stacked.rs
@@ -3,6 +3,7 @@ use super::*;
/// Layouts boxes stack-like.
///
/// The boxes are arranged vertically, each layout gettings it's own "line".
+#[derive(Debug, Clone)]
pub struct StackLayouter {
ctx: StackContext,
layouts: MultiLayout,
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index d125bc84..bd4adb8a 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -7,6 +7,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiL
layouter.finish()
}
+#[derive(Debug, Clone)]
struct TreeLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
stack: StackLayouter,
@@ -85,13 +86,18 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
+ // Finish the current flex layout on a copy to find out how
+ // much space would be remaining if we finished.
+ let mut lookahead_stack = self.stack.clone();
+ let layouts = self.flex.clone().finish()?;
+ lookahead_stack.add_many(layouts)?;
+ let remaining = lookahead_stack.remaining();
+
let mut ctx = self.ctx;
ctx.style = &self.style;
ctx.shrink_to_fit = true;
-
- ctx.space.dimensions = self.stack.remaining();
+ ctx.space.dimensions = remaining;
ctx.space.padding = SizeBox::zero();
-
if let Some(space) = ctx.followup_spaces.as_mut() {
*space = space.usable_space();
}
@@ -127,6 +133,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::SetStyle(style) => {
*self.style.to_mut() = style;
}
+
+ Command::FinishLayout => {
+ self.finish_flex()?;
+ self.stack.finish_layout(true)?;
+ self.start_new_flex();
+ }
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 424d8dbf..c01e5d7e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,6 +29,7 @@ use crate::syntax::SyntaxTree;
#[macro_use]
mod macros;
pub mod export;
+#[macro_use]
pub mod func;
pub mod layout;
pub mod library;
diff --git a/src/library/align.rs b/src/library/align.rs
index 922464a8..cc41f295 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -12,7 +12,7 @@ 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");
+ return err("align: expected exactly one positional argument");
}
let alignment = if let Expression::Ident(ident) = &header.args[0] {
@@ -29,11 +29,7 @@ impl Function for AlignFunc {
));
};
- let body = if let Some(body) = body {
- Some(parse(body, ctx)?)
- } else {
- None
- };
+ let body = parse_maybe_body(body, ctx)?;
Ok(AlignFunc { alignment, body })
}
@@ -45,14 +41,9 @@ impl Function for AlignFunc {
.. ctx
})?;
- let mut commands = CommandList::new();
- commands.add(Command::AddMany(layouts));
- Ok(commands)
+ Ok(commands![Command::AddMany(layouts)])
} else {
- let mut commands = CommandList::new();
- commands.add(Command::SetAlignment(self.alignment));
-
- Ok(commands)
+ Ok(commands![Command::SetAlignment(self.alignment)])
}
}
}
diff --git a/src/library/breaks.rs b/src/library/breaks.rs
new file mode 100644
index 00000000..22d572f0
--- /dev/null
+++ b/src/library/breaks.rs
@@ -0,0 +1,24 @@
+use super::prelude::*;
+
+/// Ends the current page.
+#[derive(Debug, PartialEq)]
+pub struct PagebreakFunc;
+
+impl Function for PagebreakFunc {
+ fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
+ where Self: Sized {
+ if has_arguments(header) {
+ return err("pagebreak: expected no arguments");
+ }
+
+ if body.is_some() {
+ return err("pagebreak: expected no body");
+ }
+
+ Ok(PagebreakFunc)
+ }
+
+ fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
+ Ok(commands![Command::FinishLayout])
+ }
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 9a8b2e21..7c54a9f6 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -4,6 +4,7 @@ use crate::func::Scope;
mod align;
mod styles;
+mod breaks;
/// Useful imports for creating your own functions.
pub mod prelude {
@@ -12,13 +13,11 @@ pub mod prelude {
pub use crate::layout::{LayoutError, LayoutResult};
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
-
- pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
- Err(ParseError::new(message))
- }
+ pub use super::helpers::*;
}
pub use align::AlignFunc;
+pub use breaks::PagebreakFunc;
pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc};
/// Create a scope with all standard functions.
@@ -28,5 +27,26 @@ pub fn std() -> Scope {
std.add::<ItalicFunc>("italic");
std.add::<MonospaceFunc>("mono");
std.add::<AlignFunc>("align");
+ std.add::<PagebreakFunc>("pagebreak");
std
}
+
+pub mod helpers {
+ use super::prelude::*;
+
+ pub fn has_arguments(header: &FuncHeader) -> bool {
+ !header.args.is_empty() || !header.kwargs.is_empty()
+ }
+
+ pub fn parse_maybe_body(body: Option<&str>, ctx: ParseContext) -> ParseResult<Option<SyntaxTree>> {
+ if let Some(body) = body {
+ Ok(Some(parse(body, ctx)?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
+ Err(ParseError::new(message))
+ }
+}
diff --git a/src/library/styles.rs b/src/library/styles.rs
index f0e5bbdb..bc84ac3b 100644
--- a/src/library/styles.rs
+++ b/src/library/styles.rs
@@ -10,35 +10,37 @@ macro_rules! style_func {
) => {
$(#[$outer])*
#[derive(Debug, PartialEq)]
- pub struct $struct { body: SyntaxTree }
+ pub struct $struct {
+ body: Option<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)))
+ if has_arguments(header) {
+ return err(format!("{}: expected no arguments", $name));
}
+
+ let body = parse_maybe_body(body, ctx)?;
+
+ Ok($struct { body })
}
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
- let mut commands = CommandList::new();
-
- let saved_style = ctx.style.clone();
let mut new_style = ctx.style.clone();
new_style.toggle_class(FontClass::$class);
- commands.add(Command::SetStyle(new_style));
- commands.add(Command::Layout(&self.body));
- commands.add(Command::SetStyle(saved_style));
-
- Ok(commands)
+ if let Some(body) = &self.body {
+ let saved_style = ctx.style.clone();
+ Ok(commands![
+ Command::SetStyle(new_style),
+ Command::Layout(body),
+ Command::SetStyle(saved_style),
+ ])
+ } else {
+ Ok(commands![Command::SetStyle(new_style)])
+ }
}
}
};