Implement context-modifying align 🧩

This commit is contained in:
Laurenz 2019-10-17 09:28:06 +02:00
parent e87a34a4d0
commit 9a1d57a11a
8 changed files with 150 additions and 97 deletions

View File

@ -4,10 +4,9 @@ use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use toddle::query::FontClass; use crate::layout::{Layout, LayoutContext, Alignment, LayoutResult, MultiLayout};
use crate::layout::{Layout, LayoutContext, LayoutResult, MultiLayout};
use crate::parsing::{ParseContext, ParseResult}; use crate::parsing::{ParseContext, ParseResult};
use crate::style::TextStyle;
use crate::syntax::{FuncHeader, SyntaxTree}; use crate::syntax::{FuncHeader, SyntaxTree};
/// Typesetting function types. /// Typesetting function types.
@ -27,7 +26,7 @@ pub trait Function: FunctionBounds {
/// ///
/// Returns optionally the resulting layout and a new context if changes to /// Returns optionally the resulting layout and a new context if changes to
/// the context should be made. /// the context should be made.
fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands>; fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList>;
} }
impl PartialEq for dyn Function { impl PartialEq for dyn Function {
@ -38,14 +37,14 @@ impl PartialEq for dyn Function {
/// 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 FuncCommands<'a> { pub struct CommandList<'a> {
pub commands: Vec<Command<'a>>, pub commands: Vec<Command<'a>>,
} }
impl<'a> FuncCommands<'a> { impl<'a> CommandList<'a> {
/// Create an empty command list. /// Create an empty command list.
pub fn new() -> FuncCommands<'a> { pub fn new() -> CommandList<'a> {
FuncCommands { commands: vec![] } CommandList { commands: vec![] }
} }
/// Add a command to the sequence. /// 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 Item = Command<'a>;
type IntoIter = std::vec::IntoIter<Command<'a>>; type IntoIter = std::vec::IntoIter<Command<'a>>;
@ -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. /// Commands requested for execution by functions.
#[derive(Debug)] #[derive(Debug)]
pub enum Command<'a> { pub enum Command<'a> {
Layout(&'a SyntaxTree), Layout(&'a SyntaxTree),
Add(Layout), Add(Layout),
AddMany(MultiLayout), AddMany(MultiLayout),
ToggleStyleClass(FontClass), SetAlignment(Alignment),
SetStyle(TextStyle),
} }
/// A helper trait that describes requirements for types that can implement /// A helper trait that describes requirements for types that can implement

View File

@ -24,7 +24,7 @@ pub struct FlexLayouter {
stack: StackLayouter, stack: StackLayouter,
usable_width: Size, usable_width: Size,
run: FlexRun, run: FlexRun,
cached_glue: Option<Layout>, cached_glue: Option<Size2D>,
} }
/// The context for flex layouting. /// The context for flex layouting.
@ -70,7 +70,7 @@ enum FlexUnit {
/// A unit which acts as glue between two [`FlexUnit::Boxed`] units and /// 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 /// is only present if there was no flow break in between the two
/// surrounding boxes. /// surrounding boxes.
Glue(Layout), Glue(Size2D),
} }
struct FlexRun { struct FlexRun {
@ -106,8 +106,8 @@ impl FlexLayouter {
self.units.push(FlexUnit::Boxed(layout)); self.units.push(FlexUnit::Boxed(layout));
} }
/// Add a glue layout which can be replaced by a line break. /// Add a glue box which can be replaced by a line break.
pub fn add_glue(&mut self, glue: Layout) { pub fn add_glue(&mut self, glue: Size2D) {
self.units.push(FlexUnit::Glue(glue)); 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 /// Layout a content box into the current flex run or start a new run if
/// it does not fit. /// it does not fit.
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> { fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
let glue_width = self let glue_width = self.cached_glue.unwrap_or(Size2D::zero()).x;
.cached_glue
.as_ref()
.map(|layout| layout.dimensions.x)
.unwrap_or(Size::zero());
let new_line_width = self.run.size.x + glue_width + boxed.dimensions.x; let new_line_width = self.run.size.x + glue_width + boxed.dimensions.x;
if self.overflows_line(new_line_width) { if self.overflows_line(new_line_width) {
@ -164,32 +159,31 @@ impl FlexLayouter {
self.flush_glue(); 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(()) Ok(())
} }
fn layout_glue(&mut self, glue: Layout) { fn layout_glue(&mut self, glue: Size2D) {
self.flush_glue(); self.flush_glue();
self.cached_glue = Some(glue); self.cached_glue = Some(glue);
} }
fn flush_glue(&mut self) { fn flush_glue(&mut self) {
if let Some(glue) = self.cached_glue.take() { 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) { if !self.overflows_line(new_line_width) {
self.add_to_run(glue); self.grow_run(glue);
} }
} }
} }
fn add_to_run(&mut self, layout: Layout) { fn grow_run(&mut self, dimensions: Size2D) {
let x = self.run.size.x; self.run.size.x += dimensions.x;
self.run.size.y = crate::size::max(self.run.size.y, dimensions.y);
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 finish_run(&mut self) -> LayoutResult<()> { fn finish_run(&mut self) -> LayoutResult<()> {

View File

@ -12,6 +12,8 @@ struct TreeLayouter<'a, 'p> {
stack: StackLayouter, stack: StackLayouter,
flex: FlexLayouter, flex: FlexLayouter,
style: Cow<'a, TextStyle>, style: Cow<'a, TextStyle>,
alignment: Alignment,
set_newline: bool,
} }
impl<'a, 'p> TreeLayouter<'a, 'p> { 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)) .. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style))
}), }),
style: Cow::Borrowed(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<()> { fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
for node in &tree.nodes { for node in &tree.nodes {
match node { 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 => { Node::Space => {
// Only add a space if there was any content before. // Only add a space if there was any content before.
if !self.flex.is_empty() { 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. // Finish the current flex layouting process.
Node::Newline => { 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); let space = paragraph_spacing(&self.style);
self.stack.add_space(space)?; self.stack.add_space(space)?;
self.set_newline = false;
} }
self.start_new_flex(); self.start_new_flex();
@ -69,50 +79,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Finish the layout. /// Finish the layout.
fn finish(mut self) -> LayoutResult<MultiLayout> { fn finish(mut self) -> LayoutResult<MultiLayout> {
self.layout_flex()?; self.finish_flex()?;
self.stack.finish() 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. /// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
let mut ctx = self.ctx; let mut ctx = self.ctx;
@ -130,15 +100,71 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
for command in commands { for command in commands {
match command { match command {
Command::Layout(tree) => self.layout(tree)?, Command::Layout(tree) => {
Command::Add(layout) => self.stack.add(layout)?, self.layout(tree)?;
Command::AddMany(layouts) => self.stack.add_many(layouts)?, }
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
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(()) 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<Layout> {
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 { fn flex_spacing(style: &TextStyle) -> Size {

View File

@ -38,18 +38,21 @@ impl Function for AlignFunc {
Ok(AlignFunc { alignment, body }) Ok(AlignFunc { alignment, body })
} }
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<FuncCommands> { fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
if let Some(body) = &self.body { 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 = CommandList::new();
let mut commands = FuncCommands::new();
commands.add(Command::AddMany(layouts)); commands.add(Command::AddMany(layouts));
Ok(commands) Ok(commands)
} else { } else {
unimplemented!("context-modifying align func") let mut commands = CommandList::new();
commands.add(Command::SetAlignment(self.alignment));
Ok(commands)
} }
} }
} }

View File

@ -7,7 +7,7 @@ mod styles;
/// Useful imports for creating your own functions. /// Useful imports for creating your own functions.
pub mod prelude { 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::{layout_tree, Layout, LayoutContext, MultiLayout};
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};

View File

@ -27,12 +27,16 @@ macro_rules! style_func {
} }
} }
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> { fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
let mut commands = FuncCommands::new(); 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::Layout(&self.body));
commands.add(Command::ToggleStyleClass(FontClass::$class)); commands.add(Command::SetStyle(saved_style));
Ok(commands) Ok(commands)
} }

View File

@ -439,7 +439,7 @@ error_type! {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::func::{FuncCommands, Function, Scope}; use crate::func::{CommandList, Function, Scope};
use crate::layout::{LayoutContext, LayoutResult}; use crate::layout::{LayoutContext, LayoutResult};
use funcs::*; use funcs::*;
use Node::{Func as F, Newline as N, Space as S}; use Node::{Func as F, Newline as N, Space as S};
@ -463,8 +463,8 @@ mod tests {
} }
} }
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> { fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
Ok(FuncCommands::new()) Ok(CommandList::new())
} }
} }
@ -482,8 +482,8 @@ mod tests {
} }
} }
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> { fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
Ok(FuncCommands::new()) Ok(CommandList::new())
} }
} }
} }

View File

@ -1,8 +1,25 @@
{size:150pt*208pt} {size:150pt*208pt}
// 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
[align: center][Center: {lorem:80}] [align: center][Center: {lorem:80}]
[align: left][Left: {lorem:20}] [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}