From f22f9513aea21408ebf6febd01912e630e9ad5e6 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 17 Oct 2019 10:12:34 +0200 Subject: [PATCH] =?UTF-8?q?Add=20pagebreak=20function=20=E2=8F=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/func.rs | 66 +++++++++++++++++++++--------------- src/layout/flex.rs | 4 ++- src/layout/stacked.rs | 1 + src/layout/tree.rs | 18 ++++++++-- src/lib.rs | 1 + src/library/align.rs | 17 +++------- src/library/breaks.rs | 24 +++++++++++++ src/library/mod.rs | 28 ++++++++++++--- src/library/styles.rs | 36 ++++++++++---------- tests/layouts/align.typ | 28 +++++++++------ tests/layouts/pagebreaks.typ | 12 +++++-- tests/layouts/styles.typ | 13 +++++-- 12 files changed, 169 insertions(+), 79 deletions(-) create mode 100644 src/library/breaks.rs diff --git a/src/func.rs b/src/func.rs index e2391a55e..eaaa476da 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 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 == 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>) -> 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 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 == 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 98cca2f9e..39c16aefa 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, @@ -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 0b7bfd4f4..d87e53943 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 d125bc845..bd4adb8aa 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -7,6 +7,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { 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 424d8dbf9..c01e5d7e9 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 922464a8b..cc41f295d 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 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 000000000..22d572f0e --- /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 + 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 { + Ok(commands![Command::FinishLayout]) + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index 9a8b2e21c..7c54a9f69 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, T>(message: S) -> ParseResult { - 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::("italic"); std.add::("mono"); std.add::("align"); + std.add::("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> { + if let Some(body) = body { + Ok(Some(parse(body, ctx)?)) + } else { + Ok(None) + } + } + + pub fn err, T>(message: S) -> ParseResult { + Err(ParseError::new(message)) + } +} diff --git a/src/library/styles.rs b/src/library/styles.rs index f0e5bbdb4..bc84ac3b4 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 + } impl Function for $struct { fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult 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 { - 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)]) + } } } }; diff --git a/tests/layouts/align.typ b/tests/layouts/align.typ index f13a20108..68e5fab59 100644 --- a/tests/layouts/align.typ +++ b/tests/layouts/align.typ @@ -1,25 +1,33 @@ -{size:150pt*208pt} +{size:150pt*215pt} -// Without newline in between +// ---------------------------------- // +// Without newline in between. [align: left][Left: {lorem:20}] [align: right][Right: {lorem:20}] -// With newline in between +// Over three pages. [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] -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} -// Reset context-modifier +// Reset context-modifier. [align: left] -// All in one line -{lorem:25} [align: right][{lorem:50}] {lorem:15} +[pagebreak] + +// ---------------------------------- // +// All in one line. +All in one line: {lorem:25} [align: right][{lorem:50}] {lorem:15} diff --git a/tests/layouts/pagebreaks.typ b/tests/layouts/pagebreaks.typ index 00684f56d..8de361ab7 100644 --- a/tests/layouts/pagebreaks.typ +++ b/tests/layouts/pagebreaks.typ @@ -1,2 +1,10 @@ -{size:200pt*200pt} -{lorem:300} +{size:150pt*200pt} +{lorem:100} + +[pagebreak] +[pagebreak] + +{lorem:20} +[pagebreak] + +{lorem:150} diff --git a/tests/layouts/styles.typ b/tests/layouts/styles.typ index 767a0b73a..b3f671327 100644 --- a/tests/layouts/styles.typ +++ b/tests/layouts/styles.typ @@ -1,5 +1,4 @@ -_Multiline:_ -{lorem:45} +{size:250pt*500pt} _Emoji:_ Hello World! 🌍 @@ -8,3 +7,13 @@ built-in syntax! _Styles with functions:_ This [bold][word] is made bold and [italic][that] 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. 😀