Add pagebreak function ⏭

This commit is contained in:
Laurenz 2019-10-17 10:12:34 +02:00
parent 9a1d57a11a
commit f22f9513ae
12 changed files with 169 additions and 79 deletions

View File

@ -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. /// A sequence of commands requested for execution by a function.
#[derive(Debug)] #[derive(Debug)]
pub struct CommandList<'a> { pub struct CommandList<'a> {
@ -47,6 +76,11 @@ impl<'a> CommandList<'a> {
CommandList { commands: vec![] } 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. /// Add a command to the sequence.
pub fn add(&mut self, command: Command<'a>) { pub fn add(&mut self, command: Command<'a>) {
self.commands.push(command); self.commands.push(command);
@ -84,35 +118,13 @@ pub enum Command<'a> {
AddMany(MultiLayout), AddMany(MultiLayout),
SetAlignment(Alignment), SetAlignment(Alignment),
SetStyle(TextStyle), SetStyle(TextStyle),
FinishLayout,
} }
/// A helper trait that describes requirements for types that can implement macro_rules! commands {
/// [`Function`]. ($($x:expr),*$(,)*) => ({
/// $crate::func::CommandList::from_vec(vec![$($x,)*])
/// 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 map from identifiers to functions. /// A map from identifiers to functions.

View File

@ -17,6 +17,7 @@ use super::*;
/// flows into a new line. A _glue_ layout is typically used for a space character /// 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. /// since it prevents a space from appearing in the beginning or end of a line.
/// However, it can be any layout. /// However, it can be any layout.
#[derive(Debug, Clone)]
pub struct FlexLayouter { pub struct FlexLayouter {
ctx: FlexContext, ctx: FlexContext,
units: Vec<FlexUnit>, units: Vec<FlexUnit>,
@ -64,6 +65,7 @@ impl FlexContext {
} }
} }
#[derive(Debug, Clone)]
enum FlexUnit { enum FlexUnit {
/// A content unit to be arranged flexibly. /// A content unit to be arranged flexibly.
Boxed(Layout), Boxed(Layout),
@ -73,6 +75,7 @@ enum FlexUnit {
Glue(Size2D), Glue(Size2D),
} }
#[derive(Debug, Clone)]
struct FlexRun { struct FlexRun {
content: Vec<(Size, Layout)>, content: Vec<(Size, Layout)>,
size: Size2D, size: Size2D,
@ -168,7 +171,6 @@ impl FlexLayouter {
} }
fn layout_glue(&mut self, glue: Size2D) { fn layout_glue(&mut self, glue: Size2D) {
self.flush_glue();
self.cached_glue = Some(glue); self.cached_glue = Some(glue);
} }

View File

@ -3,6 +3,7 @@ use super::*;
/// Layouts boxes stack-like. /// Layouts boxes stack-like.
/// ///
/// The boxes are arranged vertically, each layout gettings it's own "line". /// The boxes are arranged vertically, each layout gettings it's own "line".
#[derive(Debug, Clone)]
pub struct StackLayouter { pub struct StackLayouter {
ctx: StackContext, ctx: StackContext,
layouts: MultiLayout, layouts: MultiLayout,

View File

@ -7,6 +7,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiL
layouter.finish() layouter.finish()
} }
#[derive(Debug, Clone)]
struct TreeLayouter<'a, 'p> { struct TreeLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>, ctx: LayoutContext<'a, 'p>,
stack: StackLayouter, stack: StackLayouter,
@ -85,13 +86,18 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Layout a function. /// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { 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; let mut ctx = self.ctx;
ctx.style = &self.style; ctx.style = &self.style;
ctx.shrink_to_fit = true; ctx.shrink_to_fit = true;
ctx.space.dimensions = remaining;
ctx.space.dimensions = self.stack.remaining();
ctx.space.padding = SizeBox::zero(); ctx.space.padding = SizeBox::zero();
if let Some(space) = ctx.followup_spaces.as_mut() { if let Some(space) = ctx.followup_spaces.as_mut() {
*space = space.usable_space(); *space = space.usable_space();
} }
@ -127,6 +133,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::SetStyle(style) => { Command::SetStyle(style) => {
*self.style.to_mut() = style; *self.style.to_mut() = style;
} }
Command::FinishLayout => {
self.finish_flex()?;
self.stack.finish_layout(true)?;
self.start_new_flex();
}
} }
} }

View File

@ -29,6 +29,7 @@ use crate::syntax::SyntaxTree;
#[macro_use] #[macro_use]
mod macros; mod macros;
pub mod export; pub mod export;
#[macro_use]
pub mod func; pub mod func;
pub mod layout; pub mod layout;
pub mod library; pub mod library;

View File

@ -12,7 +12,7 @@ impl Function for AlignFunc {
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self> fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
where Self: Sized { where Self: Sized {
if header.args.len() != 1 || !header.kwargs.is_empty() { 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] { 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 { let body = parse_maybe_body(body, ctx)?;
Some(parse(body, ctx)?)
} else {
None
};
Ok(AlignFunc { alignment, body }) Ok(AlignFunc { alignment, body })
} }
@ -45,14 +41,9 @@ impl Function for AlignFunc {
.. ctx .. ctx
})?; })?;
let mut commands = CommandList::new(); Ok(commands![Command::AddMany(layouts)])
commands.add(Command::AddMany(layouts));
Ok(commands)
} else { } else {
let mut commands = CommandList::new(); Ok(commands![Command::SetAlignment(self.alignment)])
commands.add(Command::SetAlignment(self.alignment));
Ok(commands)
} }
} }
} }

24
src/library/breaks.rs Normal file
View File

@ -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])
}
}

View File

@ -4,6 +4,7 @@ use crate::func::Scope;
mod align; mod align;
mod styles; mod styles;
mod breaks;
/// Useful imports for creating your own functions. /// Useful imports for creating your own functions.
pub mod prelude { pub mod prelude {
@ -12,13 +13,11 @@ pub mod prelude {
pub use crate::layout::{LayoutError, LayoutResult}; pub use crate::layout::{LayoutError, LayoutResult};
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult}; pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree}; pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
pub use super::helpers::*;
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
Err(ParseError::new(message))
}
} }
pub use align::AlignFunc; pub use align::AlignFunc;
pub use breaks::PagebreakFunc;
pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc}; pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc};
/// Create a scope with all standard functions. /// Create a scope with all standard functions.
@ -28,5 +27,26 @@ pub fn std() -> Scope {
std.add::<ItalicFunc>("italic"); std.add::<ItalicFunc>("italic");
std.add::<MonospaceFunc>("mono"); std.add::<MonospaceFunc>("mono");
std.add::<AlignFunc>("align"); std.add::<AlignFunc>("align");
std.add::<PagebreakFunc>("pagebreak");
std 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))
}
}

View File

@ -10,35 +10,37 @@ macro_rules! style_func {
) => { ) => {
$(#[$outer])* $(#[$outer])*
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct $struct { body: SyntaxTree } pub struct $struct {
body: Option<SyntaxTree>
}
impl Function for $struct { impl Function for $struct {
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
-> ParseResult<Self> where Self: Sized { -> ParseResult<Self> where Self: Sized {
// Accept only invocations without arguments and with body. // Accept only invocations without arguments and with body.
if header.args.is_empty() && header.kwargs.is_empty() { if has_arguments(header) {
if let Some(body) = body { return err(format!("{}: expected no arguments", $name));
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)))
} }
let body = parse_maybe_body(body, ctx)?;
Ok($struct { body })
} }
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> { 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(); let mut new_style = ctx.style.clone();
new_style.toggle_class(FontClass::$class); new_style.toggle_class(FontClass::$class);
commands.add(Command::SetStyle(new_style)); if let Some(body) = &self.body {
commands.add(Command::Layout(&self.body)); let saved_style = ctx.style.clone();
commands.add(Command::SetStyle(saved_style)); Ok(commands![
Command::SetStyle(new_style),
Ok(commands) Command::Layout(body),
Command::SetStyle(saved_style),
])
} else {
Ok(commands![Command::SetStyle(new_style)])
}
} }
} }
}; };

View File

@ -1,25 +1,33 @@
{size:150pt*208pt} {size:150pt*215pt}
// Without newline in between // ---------------------------------- //
// Without newline in between.
[align: left][Left: {lorem:20}] [align: left][Left: {lorem:20}]
[align: right][Right: {lorem:20}] [align: right][Right: {lorem:20}]
// With newline in between // Over three pages.
[align: center][Center: {lorem:80}] [align: center][Center: {lorem:80}]
[align: left][Left: {lorem:20}] // Over multiple pages after the pervious 3-page run.
[align: left][Left: {lorem:80}]
// Context-modifying align [pagebreak]
// ---------------------------------- //
// Context-modifying align.
[align: right] [align: right]
New Right: {lorem:30} Context Right: {lorem:10}
[align: left][Inside Left: {lorem:10}] [align: left][In-between Left: {lorem:10}]
Right Again: {lorem:10} Right Again: {lorem:10}
// Reset context-modifier // Reset context-modifier.
[align: left] [align: left]
// All in one line [pagebreak]
{lorem:25} [align: right][{lorem:50}] {lorem:15}
// ---------------------------------- //
// All in one line.
All in one line: {lorem:25} [align: right][{lorem:50}] {lorem:15}

View File

@ -1,2 +1,10 @@
{size:200pt*200pt} {size:150pt*200pt}
{lorem:300} {lorem:100}
[pagebreak]
[pagebreak]
{lorem:20}
[pagebreak]
{lorem:150}

View File

@ -1,5 +1,4 @@
_Multiline:_ {size:250pt*500pt}
{lorem:45}
_Emoji:_ Hello World! 🌍 _Emoji:_ Hello World! 🌍
@ -8,3 +7,13 @@ built-in syntax!
_Styles with functions:_ This [bold][word] is made bold and [italic][that] italic _Styles with functions:_ This [bold][word] is made bold and [italic][that] italic
using the standard library functions `bold` and `italic`! using the standard library functions `bold` and `italic`!
[italic]
Styles can also be changed through [bold] context modification.
This works basically in the same way as the built-in syntax.
_
This is not italic anymore, but still bold.
[bold]
This is completely reset. 😀