summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-11-16 10:37:30 +0100
committerLaurenz <laurmaedje@gmail.com>2019-11-16 10:37:30 +0100
commit261ef9e33a8548d4b7aa53e69e71866648982ae8 (patch)
tree8c98eeb4a4bb2123b45baf1dd4de706a21d619e9
parent0917d89bb899380ba897382b4945c8426f25c66d (diff)
Generalize tree layouter 🌲
-rw-r--r--src/export/pdf.rs4
-rw-r--r--src/func/mod.rs18
-rw-r--r--src/layout/actions.rs12
-rw-r--r--src/layout/flex.rs17
-rw-r--r--src/layout/mod.rs2
-rw-r--r--src/layout/stacked.rs27
-rw-r--r--src/layout/text.rs19
-rw-r--r--src/layout/tree.rs171
-rw-r--r--src/library/structure.rs32
-rw-r--r--src/style.rs7
10 files changed, 153 insertions, 156 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index bb688118..76fc59b8 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -249,8 +249,8 @@ impl<'d, W: Write> ExportProcess<'d, W> {
},
LayoutAction::SetFont(id, size) => {
- active_font = (self.font_remap[id], *size);
- text.tf(active_font.0 as u32 + 1, *size);
+ active_font = (self.font_remap[id], size.to_pt());
+ text.tf(active_font.0 as u32 + 1, size.to_pt());
}
LayoutAction::WriteText(string) => {
diff --git a/src/func/mod.rs b/src/func/mod.rs
index bd61204e..fa6407d8 100644
--- a/src/func/mod.rs
+++ b/src/func/mod.rs
@@ -12,8 +12,9 @@ pub mod helpers;
/// Useful imports for creating your own functions.
pub mod prelude {
pub use crate::func::{Command, CommandList, Function};
- pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
- pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult};
+ pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext, LayoutSpace};
+ pub use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment};
+ pub use crate::layout::{LayoutError, LayoutResult};
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
pub use crate::size::{Size, Size2D, SizeBox};
@@ -88,13 +89,16 @@ where T: Debug + PartialEq + 'static
#[derive(Debug)]
pub enum Command<'a> {
LayoutTree(&'a SyntaxTree),
+
Add(Layout),
- AddMany(MultiLayout),
- AddFlex(Layout),
- SetAlignment(Alignment),
- SetStyle(TextStyle),
- FinishLayout,
+ AddMultiple(MultiLayout),
+
FinishFlexRun,
+ FinishFlexLayout,
+ FinishLayout,
+
+ SetStyle(TextStyle),
+ SetAxes(LayoutAxes),
}
/// A sequence of commands requested for execution by a function.
diff --git a/src/layout/actions.rs b/src/layout/actions.rs
index 707d6113..ed3bc182 100644
--- a/src/layout/actions.rs
+++ b/src/layout/actions.rs
@@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter};
use std::io::{self, Write};
use super::Layout;
-use crate::size::Size2D;
+use crate::size::{Size, Size2D};
use LayoutAction::*;
/// A layouting action.
@@ -13,7 +13,7 @@ pub enum LayoutAction {
/// Move to an absolute position.
MoveAbsolute(Size2D),
/// Set the font by index and font size.
- SetFont(usize, f32),
+ SetFont(usize, Size),
/// Write text starting at the current position.
WriteText(String),
/// Visualize a box for debugging purposes.
@@ -26,7 +26,7 @@ impl LayoutAction {
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
match self {
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
- SetFont(i, s) => write!(f, "f {} {}", i, s),
+ SetFont(i, s) => write!(f, "f {} {}", i, s.to_pt()),
WriteText(s) => write!(f, "w {}", s),
DebugBox(p, s) => write!(
f,
@@ -69,9 +69,9 @@ debug_display!(LayoutAction);
pub struct LayoutActionList {
pub origin: Size2D,
actions: Vec<LayoutAction>,
- active_font: (usize, f32),
+ active_font: (usize, Size),
next_pos: Option<Size2D>,
- next_font: Option<(usize, f32)>,
+ next_font: Option<(usize, Size)>,
}
impl LayoutActionList {
@@ -80,7 +80,7 @@ impl LayoutActionList {
LayoutActionList {
actions: vec![],
origin: Size2D::zero(),
- active_font: (std::usize::MAX, 0.0),
+ active_font: (std::usize::MAX, Size::zero()),
next_pos: None,
next_font: None,
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index a364b608..c136ad0d 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -75,16 +75,18 @@ impl FlexLayouter {
}
}
- /// This layouter's context.
- pub fn ctx(&self) -> FlexContext {
- self.ctx
- }
-
/// Add a sublayout.
pub fn add(&mut self, layout: Layout) {
self.units.push(FlexUnit::Boxed(layout));
}
+ /// Add multiple sublayouts from a multi-layout.
+ pub fn add_multiple(&mut self, layouts: MultiLayout) {
+ for layout in layouts {
+ self.add(layout);
+ }
+ }
+
/// Add a space box which can be replaced by a run break.
pub fn add_space(&mut self, space: Size) {
self.units.push(FlexUnit::Space(space));
@@ -181,6 +183,11 @@ impl FlexLayouter {
Ok(())
}
+ /// This layouter's context.
+ pub fn ctx(&self) -> FlexContext {
+ self.ctx
+ }
+
/// Whether this layouter contains any items.
pub fn is_empty(&self) -> bool {
self.units.is_empty()
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index f0160f31..2bafd792 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -192,7 +192,7 @@ impl LayoutSpace {
}
/// The axes along which the content is laid out.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LayoutAxes {
pub primary: AlignedAxis,
pub secondary: AlignedAxis,
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs
index 419ec6b7..cc6caa5e 100644
--- a/src/layout/stacked.rs
+++ b/src/layout/stacked.rs
@@ -1,3 +1,4 @@
+use smallvec::smallvec;
use super::*;
/// Layouts boxes stack-like.
@@ -66,7 +67,7 @@ impl StackLayouter {
}
/// Add multiple sublayouts from a multi-layout.
- pub fn add_many(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
+ pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
for layout in layouts {
self.add(layout)?;
}
@@ -135,7 +136,7 @@ impl StackLayouter {
/// finishing this stack. Otherwise, the new layout only appears if new
/// content is added to it.
fn start_new_space(&mut self, include_empty: bool) {
- self.active_space = (self.active_space + 1).min(self.ctx.spaces.len() - 1);
+ self.active_space = self.next_space();
self.usable = self.ctx.spaces[self.active_space].usable().generalized(self.ctx.axes);
self.dimensions = start_dimensions(self.usable, self.ctx.axes);
self.include_empty = include_empty;
@@ -151,10 +152,20 @@ impl StackLayouter {
self.usable
}
- /// The (specialized) remaining area for new layouts in the current space.
- pub fn remaining(&self) -> Size2D {
- Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
- .specialized(self.ctx.axes)
+ /// The remaining spaces for new layouts in the current space.
+ pub fn remaining(&self, shrink_to_fit: bool) -> LayoutSpaces {
+ let mut spaces = smallvec![LayoutSpace {
+ dimensions: Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
+ .specialized(self.ctx.axes),
+ padding: SizeBox::zero(),
+ shrink_to_fit,
+ }];
+
+ for space in &self.ctx.spaces[self.next_space()..] {
+ spaces.push(space.usable_space(shrink_to_fit));
+ }
+
+ spaces
}
/// Whether this layouter is in its last space.
@@ -162,6 +173,10 @@ impl StackLayouter {
self.active_space == self.ctx.spaces.len() - 1
}
+ fn next_space(&self) -> usize {
+ (self.active_space + 1).min(self.ctx.spaces.len() - 1)
+ }
+
/// The combined size of the so-far included boxes with the other size.
fn size_with(&self, other: Size2D) -> Size2D {
Size2D {
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 79ace040..88d83ab7 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -13,16 +13,6 @@ pub struct TextContext<'a, 'p> {
pub style: &'a TextStyle,
}
-impl<'a, 'p> TextContext<'a, 'p> {
- /// Create a text context from a generic layout context.
- pub fn from_layout_ctx(ctx: LayoutContext<'a, 'p>) -> TextContext<'a, 'p> {
- TextContext {
- loader: ctx.loader,
- style: ctx.style,
- }
- }
-}
-
/// Layouts text into a box.
///
/// There is no complex layout involved. The text is simply laid out left-
@@ -81,7 +71,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
}
Ok(Layout {
- dimensions: Size2D::new(self.width, Size::pt(self.ctx.style.font_size)),
+ dimensions: Size2D::new(self.width, self.ctx.style.font_size),
actions: self.actions.into_vec(),
debug_render: false,
})
@@ -107,15 +97,16 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
let glyph = font
.read_table::<CharMap>()?
.get(c)
- .expect("layout text: font should have char");
+ .expect("select_font: font should have char");
let glyph_width = font
.read_table::<HorizontalMetrics>()?
.get(glyph)
- .expect("layout text: font should have glyph")
+ .expect("select_font: font should have glyph")
.advance_width as f32;
- let char_width = font_unit_to_size(glyph_width) * self.ctx.style.font_size;
+ let char_width = font_unit_to_size(glyph_width)
+ * self.ctx.style.font_size.to_pt();
return Ok((index, char_width));
}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index b64dd6eb..ca2051f4 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -13,8 +13,6 @@ struct TreeLayouter<'a, 'p> {
stack: StackLayouter,
flex: FlexLayouter,
style: Cow<'a, TextStyle>,
- alignment: Alignment,
- set_newline: bool,
}
impl<'a, 'p> TreeLayouter<'a, 'p> {
@@ -22,51 +20,41 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
TreeLayouter {
ctx,
- stack: StackLayouter::new(StackContext::from_layout_ctx(ctx)),
+ stack: StackLayouter::new(StackContext {
+ spaces: ctx.spaces,
+ axes: ctx.axes,
+ }),
flex: FlexLayouter::new(FlexContext {
- space: ctx.space.usable_space(),
- followup_spaces: ctx.followup_spaces.map(|s| s.usable_space()),
- shrink_to_fit: true,
- .. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style))
+ flex_spacing: flex_spacing(&ctx.style),
+ spaces: ctx.spaces.iter().map(|space| space.usable_space(true)).collect(),
+ axes: ctx.axes,
}),
style: Cow::Borrowed(ctx.style),
- alignment: ctx.alignment,
- set_newline: false,
}
}
- /// Layout the tree into a box.
+ /// Layout a syntax tree.
fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
for node in &tree.nodes {
match &node.val {
Node::Text(text) => {
- let layout = self.layout_text(text)?;
- self.flex.add(layout);
- self.set_newline = true;
+ self.flex.add(layout_text(text, TextContext {
+ loader: &self.ctx.loader,
+ style: &self.style,
+ })?);
}
Node::Space => {
- // Only add a space if there was any content before.
if !self.flex.is_empty() {
- let layout = self.layout_text(" ")?;
- self.flex.add_glue(layout.dimensions);
+ self.flex.add_space(self.style.word_spacing * self.style.font_size);
}
}
-
- // Finish the current flex layouting process.
Node::Newline => {
- self.finish_flex()?;
-
- if self.set_newline {
- let space = paragraph_spacing(&self.style);
- self.stack.add_space(space);
- self.set_newline = false;
+ if !self.flex.is_empty() {
+ self.finish_paragraph()?;
}
-
- self.start_new_flex();
}
- // Toggle the text styles.
Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic),
Node::ToggleBold => self.style.to_mut().toggle_class(FontClass::Bold),
Node::ToggleMonospace => self.style.to_mut().toggle_class(FontClass::Monospace),
@@ -78,115 +66,98 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Ok(())
}
- /// Finish the layout.
- fn finish(mut self) -> LayoutResult<MultiLayout> {
- self.finish_flex()?;
- Ok(self.stack.finish())
- }
-
/// 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 = self.stack.clone();
+ lookahead.add_multiple(self.flex.clone().finish()?)?;
+ let spaces = lookahead.remaining(true);
- 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.flow = Flow::Vertical;
- ctx.shrink_to_fit = true;
- ctx.space.dimensions = remaining;
- ctx.space.padding = SizeBox::zero();
- if let Some(space) = ctx.followup_spaces.as_mut() {
- *space = space.usable_space();
- }
-
- let commands = func.body.val.layout(ctx)?;
+ let commands = func.body.val.layout(LayoutContext {
+ style: &self.style,
+ spaces,
+ .. self.ctx
+ })?;
for command in commands {
- match command {
- Command::LayoutTree(tree) => self.layout(tree)?,
-
- Command::Add(layout) => {
- self.finish_flex()?;
- self.stack.add(layout)?;
- self.set_newline = true;
- self.start_new_flex();
- }
+ self.execute(command)?;
+ }
- Command::AddMany(layouts) => {
- self.finish_flex()?;
- self.stack.add_many(layouts)?;
- self.set_newline = true;
- self.start_new_flex();
- }
+ Ok(())
+ }
- Command::AddFlex(layout) => self.flex.add(layout),
+ fn execute(&mut self, command: Command) -> LayoutResult<()> {
+ match command {
+ Command::LayoutTree(tree) => self.layout(tree)?,
- Command::SetAlignment(alignment) => {
- self.finish_flex()?;
- self.alignment = alignment;
- self.start_new_flex();
- }
+ Command::Add(layout) => self.flex.add(layout),
+ Command::AddMultiple(layouts) => self.flex.add_multiple(layouts),
- Command::SetStyle(style) => *self.style.to_mut() = style,
+ Command::FinishFlexRun => self.flex.add_break(),
+ Command::FinishFlexLayout => self.finish_paragraph()?,
+ Command::FinishLayout => self.finish_layout(true)?,
- Command::FinishLayout => {
- self.finish_flex()?;
- self.stack.finish_layout(true);
- self.start_new_flex();
+ Command::SetStyle(style) => *self.style.to_mut() = style,
+ Command::SetAxes(axes) => {
+ if axes.secondary != self.ctx.axes.secondary {
+ self.stack.set_axis(axes.secondary);
+ } else if axes.primary != self.ctx.axes.primary {
+ self.flex.set_axis(axes.primary);
}
- Command::FinishFlexRun => self.flex.add_break(),
+ self.ctx.axes = axes;
}
}
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,
- };
+ /// Finish the layout.
+ fn finish(mut self) -> LayoutResult<MultiLayout> {
+ self.finish_flex()?;
+ Ok(self.stack.finish())
+ }
- layout_text(text, ctx)
+ /// Finish the current stack layout.
+ fn finish_layout(&mut self, include_empty: bool) -> LayoutResult<()> {
+ self.finish_flex()?;
+ self.stack.finish_layout(include_empty);
+ self.start_new_flex();
+ Ok(())
+ }
+
+ /// Finish the current flex layout and add space after it.
+ fn finish_paragraph(&mut self) -> LayoutResult<()> {
+ self.finish_flex()?;
+ self.stack.add_space(paragraph_spacing(&self.style));
+ self.start_new_flex();
+ Ok(())
}
/// Finish the current flex layout and add it the stack.
fn finish_flex(&mut self) -> LayoutResult<()> {
- if self.flex.is_empty() {
- return Ok(());
+ if !self.flex.is_empty() {
+ let layouts = self.flex.finish()?;
+ self.stack.add_multiple(layouts)?;
}
-
- 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);
+ self.flex = FlexLayouter::new(FlexContext {
+ flex_spacing: flex_spacing(&self.style),
+ spaces: self.stack.remaining(true),
+ axes: self.ctx.axes,
+ });
}
}
fn flex_spacing(style: &TextStyle) -> Size {
- (style.line_spacing - 1.0) * Size::pt(style.font_size)
+ (style.line_spacing - 1.0) * style.font_size
}
fn paragraph_spacing(style: &TextStyle) -> Size {
- let line_height = Size::pt(style.font_size);
- let space_factor = style.line_spacing * style.paragraph_spacing - 1.0;
- line_height * space_factor
+ (style.paragraph_spacing - 1.0) * style.font_size
}
diff --git a/src/library/structure.rs b/src/library/structure.rs
index e6d242a0..2bcd2744 100644
--- a/src/library/structure.rs
+++ b/src/library/structure.rs
@@ -1,30 +1,36 @@
use crate::func::prelude::*;
use Command::*;
-/// 📜 `page.break`: Ends the current page.
+/// ↩ `line.break`, `n`: Ends the current line.
#[derive(Debug, PartialEq)]
-pub struct Pagebreak;
+pub struct Linebreak;
function! {
- data: Pagebreak,
+ data: Linebreak,
parse: plain,
-
- layout(_, _) {
- Ok(commands![FinishLayout])
- }
+ layout(_, _) { Ok(commands![FinishFlexRun]) }
}
-/// 🔙 `line.break`, `n`: Ends the current line.
+/// ↕ `paragraph.break`: Ends the current paragraph.
+///
+/// This has the same effect as two subsequent newlines.
#[derive(Debug, PartialEq)]
-pub struct Linebreak;
+pub struct Parbreak;
function! {
- data: Linebreak,
+ data: Parbreak,
parse: plain,
+ layout(_, _) { Ok(commands![FinishFlexLayout]) }
+}
- layout(_, _) {
- Ok(commands![FinishFlexRun])
- }
+/// 📜 `page.break`: Ends the current page.
+#[derive(Debug, PartialEq)]
+pub struct Pagebreak;
+
+function! {
+ data: Pagebreak,
+ parse: plain,
+ layout(_, _) { Ok(commands![FinishLayout]) }
}
/// 📐 `align`: Aligns content in different ways.
diff --git a/src/style.rs b/src/style.rs
index 68bd9a27..e2ab0937 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -13,7 +13,9 @@ pub struct TextStyle {
/// leftmost possible one.
pub fallback: Vec<FontClass>,
/// The font size.
- pub font_size: f32,
+ pub font_size: Size,
+ /// The word spacing (as a multiple of the font size).
+ pub word_spacing: f32,
/// The line spacing (as a multiple of the font size).
pub line_spacing: f32,
/// The paragraphs spacing (as a multiple of the font size).
@@ -63,7 +65,8 @@ impl Default for TextStyle {
TextStyle {
classes: vec![Regular],
fallback: vec![Serif],
- font_size: 11.0,
+ font_size: Size::pt(11.0),
+ word_spacing: 0.25,
line_spacing: 1.2,
paragraph_spacing: 1.5,
}