diff --git a/src/func.rs b/src/func.rs index 1f1b928d2..e2391a55e 100644 --- a/src/func.rs +++ b/src/func.rs @@ -4,10 +4,9 @@ use std::any::Any; use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; -use toddle::query::FontClass; - -use crate::layout::{Layout, LayoutContext, LayoutResult, MultiLayout}; +use crate::layout::{Layout, LayoutContext, Alignment, LayoutResult, MultiLayout}; use crate::parsing::{ParseContext, ParseResult}; +use crate::style::TextStyle; use crate::syntax::{FuncHeader, SyntaxTree}; /// Typesetting function types. @@ -27,7 +26,7 @@ pub trait Function: FunctionBounds { /// /// Returns optionally the resulting layout and a new context if changes to /// the context should be made. - fn layout(&self, ctx: LayoutContext) -> LayoutResult; + fn layout(&self, ctx: LayoutContext) -> LayoutResult; } impl PartialEq for dyn Function { @@ -38,14 +37,14 @@ impl PartialEq for dyn Function { /// A sequence of commands requested for execution by a function. #[derive(Debug)] -pub struct FuncCommands<'a> { +pub struct CommandList<'a> { pub commands: Vec>, } -impl<'a> FuncCommands<'a> { +impl<'a> CommandList<'a> { /// Create an empty command list. - pub fn new() -> FuncCommands<'a> { - FuncCommands { commands: vec![] } + pub fn new() -> CommandList<'a> { + CommandList { commands: vec![] } } /// Add a command to the sequence. @@ -59,7 +58,7 @@ impl<'a> FuncCommands<'a> { } } -impl<'a> IntoIterator for FuncCommands<'a> { +impl<'a> IntoIterator for CommandList<'a> { type Item = Command<'a>; type IntoIter = std::vec::IntoIter>; @@ -68,13 +67,23 @@ impl<'a> IntoIterator for FuncCommands<'a> { } } +impl<'a> IntoIterator for &'a CommandList<'a> { + type Item = &'a Command<'a>; + type IntoIter = std::slice::Iter<'a, Command<'a>>; + + fn into_iter(self) -> Self::IntoIter { + self.commands.iter() + } +} + /// Commands requested for execution by functions. #[derive(Debug)] pub enum Command<'a> { Layout(&'a SyntaxTree), Add(Layout), AddMany(MultiLayout), - ToggleStyleClass(FontClass), + SetAlignment(Alignment), + SetStyle(TextStyle), } /// A helper trait that describes requirements for types that can implement diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 877e0bf37..98cca2f9e 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -24,7 +24,7 @@ pub struct FlexLayouter { stack: StackLayouter, usable_width: Size, run: FlexRun, - cached_glue: Option, + cached_glue: Option, } /// The context for flex layouting. @@ -70,7 +70,7 @@ enum FlexUnit { /// A unit which acts as glue between two [`FlexUnit::Boxed`] units and /// is only present if there was no flow break in between the two /// surrounding boxes. - Glue(Layout), + Glue(Size2D), } struct FlexRun { @@ -106,8 +106,8 @@ impl FlexLayouter { self.units.push(FlexUnit::Boxed(layout)); } - /// Add a glue layout which can be replaced by a line break. - pub fn add_glue(&mut self, glue: Layout) { + /// Add a glue box which can be replaced by a line break. + pub fn add_glue(&mut self, glue: Size2D) { self.units.push(FlexUnit::Glue(glue)); } @@ -136,12 +136,7 @@ impl FlexLayouter { /// Layout a content box into the current flex run or start a new run if /// it does not fit. fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> { - let glue_width = self - .cached_glue - .as_ref() - .map(|layout| layout.dimensions.x) - .unwrap_or(Size::zero()); - + let glue_width = self.cached_glue.unwrap_or(Size2D::zero()).x; let new_line_width = self.run.size.x + glue_width + boxed.dimensions.x; if self.overflows_line(new_line_width) { @@ -164,32 +159,31 @@ impl FlexLayouter { self.flush_glue(); } - self.add_to_run(boxed); + let dimensions = boxed.dimensions; + self.run.content.push((self.run.size.x, boxed)); + + self.grow_run(dimensions); Ok(()) } - fn layout_glue(&mut self, glue: Layout) { + fn layout_glue(&mut self, glue: Size2D) { self.flush_glue(); self.cached_glue = Some(glue); } fn flush_glue(&mut self) { if let Some(glue) = self.cached_glue.take() { - let new_line_width = self.run.size.x + glue.dimensions.x; + let new_line_width = self.run.size.x + glue.x; if !self.overflows_line(new_line_width) { - self.add_to_run(glue); + self.grow_run(glue); } } } - fn add_to_run(&mut self, layout: Layout) { - let x = self.run.size.x; - - self.run.size.x += layout.dimensions.x; - self.run.size.y = crate::size::max(self.run.size.y, layout.dimensions.y); - - self.run.content.push((x, layout)); + fn grow_run(&mut self, dimensions: Size2D) { + self.run.size.x += dimensions.x; + self.run.size.y = crate::size::max(self.run.size.y, dimensions.y); } fn finish_run(&mut self) -> LayoutResult<()> { diff --git a/src/layout/tree.rs b/src/layout/tree.rs index a1918deda..d125bc845 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -12,6 +12,8 @@ struct TreeLayouter<'a, 'p> { stack: StackLayouter, flex: FlexLayouter, style: Cow<'a, TextStyle>, + alignment: Alignment, + set_newline: bool, } impl<'a, 'p> TreeLayouter<'a, 'p> { @@ -27,6 +29,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { .. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style)) }), style: Cow::Borrowed(ctx.style), + alignment: ctx.alignment, + set_newline: false, } } @@ -34,22 +38,28 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> { for node in &tree.nodes { match node { - Node::Text(text) => self.layout_text(text, false)?, + Node::Text(text) => { + let layout = self.layout_text(text)?; + self.flex.add(layout); + self.set_newline = true; + } Node::Space => { // Only add a space if there was any content before. if !self.flex.is_empty() { - self.layout_text(" ", true)?; + let layout = self.layout_text(" ")?; + self.flex.add_glue(layout.dimensions); } } // Finish the current flex layouting process. Node::Newline => { - self.layout_flex()?; + self.finish_flex()?; - if !self.stack.current_space_is_empty() { + if self.set_newline { let space = paragraph_spacing(&self.style); self.stack.add_space(space)?; + self.set_newline = false; } self.start_new_flex(); @@ -69,50 +79,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Finish the layout. fn finish(mut self) -> LayoutResult { - self.layout_flex()?; + self.finish_flex()?; self.stack.finish() } - /// Add text to the flex layout. If `glue` is true, the text will be a glue - /// part in the flex layouter. For details, see [`FlexLayouter`]. - fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> { - let ctx = TextContext { - loader: &self.ctx.loader, - style: &self.style, - }; - - let layout = layout_text(text, ctx)?; - - if glue { - self.flex.add_glue(layout); - } else { - self.flex.add(layout); - } - - Ok(()) - } - - /// Finish the current flex layout and add it the stack. - fn layout_flex(&mut self) -> LayoutResult<()> { - if self.flex.is_empty() { - return Ok(()); - } - - let layouts = self.flex.finish()?; - self.stack.add_many(layouts)?; - - Ok(()) - } - - /// Start a new flex layout. - fn start_new_flex(&mut self) { - let mut ctx = self.flex.ctx(); - ctx.space.dimensions = self.stack.remaining(); - ctx.flex_spacing = flex_spacing(&self.style); - - self.flex = FlexLayouter::new(ctx); - } - /// Layout a function. fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let mut ctx = self.ctx; @@ -130,15 +100,71 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { for command in commands { match command { - Command::Layout(tree) => self.layout(tree)?, - Command::Add(layout) => self.stack.add(layout)?, - Command::AddMany(layouts) => self.stack.add_many(layouts)?, - Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class), + Command::Layout(tree) => { + self.layout(tree)?; + } + + Command::Add(layout) => { + self.finish_flex()?; + self.stack.add(layout)?; + self.set_newline = true; + self.start_new_flex(); + } + + Command::AddMany(layouts) => { + self.finish_flex()?; + self.stack.add_many(layouts)?; + self.set_newline = true; + self.start_new_flex(); + } + + Command::SetAlignment(alignment) => { + self.finish_flex()?; + self.alignment = alignment; + self.start_new_flex(); + } + + Command::SetStyle(style) => { + *self.style.to_mut() = style; + } } } Ok(()) } + + /// Add text to the flex layout. If `glue` is true, the text will be a glue + /// part in the flex layouter. For details, see [`FlexLayouter`]. + fn layout_text(&mut self, text: &str) -> LayoutResult { + let ctx = TextContext { + loader: &self.ctx.loader, + style: &self.style, + }; + + layout_text(text, ctx) + } + + /// Finish the current flex layout and add it the stack. + fn finish_flex(&mut self) -> LayoutResult<()> { + if self.flex.is_empty() { + return Ok(()); + } + + let layouts = self.flex.finish()?; + self.stack.add_many(layouts)?; + + Ok(()) + } + + /// Start a new flex layout. + fn start_new_flex(&mut self) { + let mut ctx = self.flex.ctx(); + ctx.space.dimensions = self.stack.remaining(); + ctx.alignment = self.alignment; + ctx.flex_spacing = flex_spacing(&self.style); + + self.flex = FlexLayouter::new(ctx); + } } fn flex_spacing(style: &TextStyle) -> Size { diff --git a/src/library/align.rs b/src/library/align.rs index be564c1bf..922464a8b 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -38,18 +38,21 @@ impl Function for AlignFunc { Ok(AlignFunc { alignment, body }) } - fn layout(&self, mut ctx: LayoutContext) -> LayoutResult { + fn layout(&self, ctx: LayoutContext) -> LayoutResult { if let Some(body) = &self.body { - ctx.alignment = self.alignment; + let layouts = layout_tree(body, LayoutContext { + alignment: self.alignment, + .. ctx + })?; - let layouts = layout_tree(body, ctx)?; - - let mut commands = FuncCommands::new(); + let mut commands = CommandList::new(); commands.add(Command::AddMany(layouts)); - Ok(commands) } else { - unimplemented!("context-modifying align func") + let mut commands = CommandList::new(); + commands.add(Command::SetAlignment(self.alignment)); + + Ok(commands) } } } diff --git a/src/library/mod.rs b/src/library/mod.rs index f3156b4a0..9a8b2e21c 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -7,7 +7,7 @@ mod styles; /// Useful imports for creating your own functions. pub mod prelude { - pub use crate::func::{Command, FuncCommands, Function}; + pub use crate::func::{Command, CommandList, Function}; pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout}; pub use crate::layout::{LayoutError, LayoutResult}; pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult}; diff --git a/src/library/styles.rs b/src/library/styles.rs index 666c8975d..f0e5bbdb4 100644 --- a/src/library/styles.rs +++ b/src/library/styles.rs @@ -27,12 +27,16 @@ macro_rules! style_func { } } - fn layout(&self, _: LayoutContext) -> LayoutResult { - let mut commands = FuncCommands::new(); + fn layout(&self, ctx: LayoutContext) -> LayoutResult { + let mut commands = CommandList::new(); - commands.add(Command::ToggleStyleClass(FontClass::$class)); + 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::ToggleStyleClass(FontClass::$class)); + commands.add(Command::SetStyle(saved_style)); Ok(commands) } diff --git a/src/parsing/mod.rs b/src/parsing/mod.rs index 6214bfdb9..cd886fb46 100644 --- a/src/parsing/mod.rs +++ b/src/parsing/mod.rs @@ -439,7 +439,7 @@ error_type! { #[cfg(test)] mod tests { use super::*; - use crate::func::{FuncCommands, Function, Scope}; + use crate::func::{CommandList, Function, Scope}; use crate::layout::{LayoutContext, LayoutResult}; use funcs::*; use Node::{Func as F, Newline as N, Space as S}; @@ -463,8 +463,8 @@ mod tests { } } - fn layout(&self, _: LayoutContext) -> LayoutResult { - Ok(FuncCommands::new()) + fn layout(&self, _: LayoutContext) -> LayoutResult { + Ok(CommandList::new()) } } @@ -482,8 +482,8 @@ mod tests { } } - fn layout(&self, _: LayoutContext) -> LayoutResult { - Ok(FuncCommands::new()) + fn layout(&self, _: LayoutContext) -> LayoutResult { + Ok(CommandList::new()) } } } diff --git a/tests/layouts/align.typ b/tests/layouts/align.typ index 7a512f472..f13a20108 100644 --- a/tests/layouts/align.typ +++ b/tests/layouts/align.typ @@ -1,8 +1,25 @@ {size:150pt*208pt} +// Without newline in between [align: left][Left: {lorem:20}] [align: right][Right: {lorem:20}] +// With newline in between [align: center][Center: {lorem:80}] [align: left][Left: {lorem:20}] + +// Context-modifying align +[align: right] + +New Right: {lorem:30} + +[align: left][Inside Left: {lorem:10}] + +Right Again: {lorem:10} + +// Reset context-modifier +[align: left] + +// All in one line +{lorem:25} [align: right][{lorem:50}] {lorem:15}