Create basic box and line-break functions 📦

This commit is contained in:
Laurenz 2019-10-17 12:55:34 +02:00
parent f22f9513ae
commit 1987e5861c
8 changed files with 111 additions and 26 deletions

View File

@ -119,6 +119,7 @@ pub enum Command<'a> {
SetAlignment(Alignment), SetAlignment(Alignment),
SetStyle(TextStyle), SetStyle(TextStyle),
FinishLayout, FinishLayout,
FinishFlexRun,
} }
macro_rules! commands { macro_rules! commands {

View File

@ -73,6 +73,8 @@ enum FlexUnit {
/// 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(Size2D), Glue(Size2D),
/// A forced break of the current flex run.
Break,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -114,6 +116,11 @@ impl FlexLayouter {
self.units.push(FlexUnit::Glue(glue)); self.units.push(FlexUnit::Glue(glue));
} }
/// Add a forced line break.
pub fn add_break(&mut self) {
self.units.push(FlexUnit::Break);
}
/// Compute the justified layout. /// Compute the justified layout.
/// ///
/// The layouter is not consumed by this to prevent ownership problems /// The layouter is not consumed by this to prevent ownership problems
@ -127,6 +134,7 @@ impl FlexLayouter {
match unit { match unit {
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?, FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
FlexUnit::Glue(glue) => self.layout_glue(glue), FlexUnit::Glue(glue) => self.layout_glue(glue),
FlexUnit::Break => self.layout_break()?,
} }
} }
@ -157,14 +165,12 @@ impl FlexLayouter {
} }
self.finish_run()?; self.finish_run()?;
} else {
// Only add the glue if we did not move to a new line.
self.flush_glue();
} }
self.flush_glue();
let dimensions = boxed.dimensions; let dimensions = boxed.dimensions;
self.run.content.push((self.run.size.x, boxed)); self.run.content.push((self.run.size.x, boxed));
self.grow_run(dimensions); self.grow_run(dimensions);
Ok(()) Ok(())
@ -174,20 +180,12 @@ impl FlexLayouter {
self.cached_glue = Some(glue); self.cached_glue = Some(glue);
} }
fn flush_glue(&mut self) { fn layout_break(&mut self) -> LayoutResult<()> {
if let Some(glue) = self.cached_glue.take() { self.cached_glue = None;
let new_line_width = self.run.size.x + glue.x; self.finish_run()
if !self.overflows_line(new_line_width) {
self.grow_run(glue);
}
}
}
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);
} }
/// Finish the current flex run.
fn finish_run(&mut self) -> LayoutResult<()> { fn finish_run(&mut self) -> LayoutResult<()> {
self.run.size.y += self.ctx.flex_spacing; self.run.size.y += self.ctx.flex_spacing;
@ -208,6 +206,19 @@ impl FlexLayouter {
Ok(()) Ok(())
} }
fn flush_glue(&mut self) {
if let Some(glue) = self.cached_glue.take() {
if self.run.size.x > Size::zero() && !self.overflows_line(self.run.size.x + glue.x) {
self.grow_run(glue);
}
}
}
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);
}
/// Whether this layouter contains any items. /// Whether this layouter contains any items.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.units.is_empty() self.units.is_empty()

View File

@ -88,6 +88,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
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 // Finish the current flex layout on a copy to find out how
// much space would be remaining if we finished. // much space would be remaining if we finished.
let mut lookahead_stack = self.stack.clone(); let mut lookahead_stack = self.stack.clone();
let layouts = self.flex.clone().finish()?; let layouts = self.flex.clone().finish()?;
lookahead_stack.add_many(layouts)?; lookahead_stack.add_many(layouts)?;
@ -106,9 +107,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
for command in commands { for command in commands {
match command { match command {
Command::Layout(tree) => { Command::Layout(tree) => self.layout(tree)?,
self.layout(tree)?;
}
Command::Add(layout) => { Command::Add(layout) => {
self.finish_flex()?; self.finish_flex()?;
@ -130,15 +129,15 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
self.start_new_flex(); self.start_new_flex();
} }
Command::SetStyle(style) => { Command::SetStyle(style) => *self.style.to_mut() = style,
*self.style.to_mut() = style;
}
Command::FinishLayout => { Command::FinishLayout => {
self.finish_flex()?; self.finish_flex()?;
self.stack.finish_layout(true)?; self.stack.finish_layout(true)?;
self.start_new_flex(); self.start_new_flex();
} }
Command::FinishFlexRun => self.flex.add_break(),
} }
} }

29
src/library/boxed.rs Normal file
View File

@ -0,0 +1,29 @@
use super::prelude::*;
/// Wraps content into a box.
#[derive(Debug, PartialEq)]
pub struct BoxFunc {
body: SyntaxTree
}
impl Function for BoxFunc {
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
where Self: Sized {
if has_arguments(header) {
return err("pagebreak: expected no arguments");
}
if let Some(body) = body {
Ok(BoxFunc {
body: parse(body, ctx)?
})
} else {
err("box: expected body")
}
}
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
let layout = layout_tree(&self.body, ctx)?;
Ok(commands![Command::AddMany(layout)])
}
}

View File

@ -1,5 +1,28 @@
use super::prelude::*; use super::prelude::*;
/// Ends the current line.
#[derive(Debug, PartialEq)]
pub struct LinebreakFunc;
impl Function for LinebreakFunc {
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
where Self: Sized {
if has_arguments(header) {
return err("linebreak: expected no arguments");
}
if body.is_some() {
return err("linebreak: expected no body");
}
Ok(LinebreakFunc)
}
fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
Ok(commands![Command::FinishFlexRun])
}
}
/// Ends the current page. /// Ends the current page.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct PagebreakFunc; pub struct PagebreakFunc;

View File

@ -3,8 +3,9 @@
use crate::func::Scope; use crate::func::Scope;
mod align; mod align;
mod styles; mod boxed;
mod breaks; mod breaks;
mod styles;
/// Useful imports for creating your own functions. /// Useful imports for creating your own functions.
pub mod prelude { pub mod prelude {
@ -17,17 +18,21 @@ pub mod prelude {
} }
pub use align::AlignFunc; pub use align::AlignFunc;
pub use breaks::PagebreakFunc; pub use boxed::BoxFunc;
pub use breaks::{LinebreakFunc, 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.
pub fn std() -> Scope { pub fn std() -> Scope {
let mut std = Scope::new(); let mut std = Scope::new();
std.add::<AlignFunc>("align");
std.add::<BoxFunc>("box");
std.add::<LinebreakFunc>("linebreak");
std.add::<LinebreakFunc>("n");
std.add::<PagebreakFunc>("pagebreak");
std.add::<BoldFunc>("bold"); std.add::<BoldFunc>("bold");
std.add::<ItalicFunc>("italic"); std.add::<ItalicFunc>("italic");
std.add::<MonospaceFunc>("mono"); std.add::<MonospaceFunc>("mono");
std.add::<AlignFunc>("align");
std.add::<PagebreakFunc>("pagebreak");
std std
} }

10
tests/layouts/boxes.typ Normal file
View File

@ -0,0 +1,10 @@
{size:400pt*250pt}
[box][
*Technical University Berlin* [n]
*Faculty II, Institute for Mathematics* [n]
Secretary Example [n]
Prof. Dr. Example [n]
Assistant #1, Assistant #2, Assistant #3
]
[align: right][*WiSe 2019/2020* [n] Week 1]

View File

@ -17,3 +17,10 @@ This is not italic anymore, but still bold.
[bold] [bold]
This is completely reset. 😀 This is completely reset. 😀
[box][
[italic]
Styles are scoped by boxes.
]
Outside of the box: No effect.