summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-06-22 12:25:01 +0200
committerLaurenz <laurmaedje@gmail.com>2019-06-22 12:25:01 +0200
commitf6fe3b5cdd2805f3975985752f9cb0e04e3daf49 (patch)
treedaddf46ba6add0b7cead1f9015b8f24364672138 /src
parente39a6efccff7ba2b5dd546ddf86f23d2714161e7 (diff)
Implement function layouting ✒
Diffstat (limited to 'src')
-rw-r--r--src/bin/main.rs22
-rw-r--r--src/func.rs68
-rw-r--r--src/layout/boxed.rs28
-rw-r--r--src/layout/flex.rs36
-rw-r--r--src/layout/mod.rs58
-rw-r--r--src/lib.rs7
-rw-r--r--src/parsing.rs6
-rw-r--r--src/style.rs2
8 files changed, 166 insertions, 61 deletions
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 6ca1ba56..b2f3d405 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -48,16 +48,22 @@ fn run() -> Result<(), Box<Error>> {
let mut src = String::new();
file.read_to_string(&mut src).map_err(|_| "failed to read from source file")?;
- // Create a typesetter with a font provider that provides three fonts
- // (two sans-serif fonts and a fallback for the emoji).
+ // Create a typesetter with a font provider that provides the default fonts.
let mut typesetter = Typesetter::new();
typesetter.add_font_provider(FileSystemFontProvider::new("fonts", vec![
- ("NotoSans-Regular.ttf", font_info!(["NotoSans", "Noto", SansSerif])),
- ("NotoSans-Italic.ttf", font_info!(["NotoSans", "Noto", SansSerif], italic)),
- ("NotoSans-Bold.ttf", font_info!(["NotoSans", "Noto", SansSerif], bold)),
- ("NotoSans-BoldItalic.ttf", font_info!(["NotoSans", "Noto", SansSerif], italic, bold)),
- ("NotoSansMath-Regular.ttf", font_info!(["NotoSansMath", "Noto", SansSerif])),
- ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])),
+ ("CMU-SansSerif-Regular.ttf", font_info!(["Computer Modern", SansSerif])),
+ ("CMU-SansSerif-Italic.ttf", font_info!(["Computer Modern", SansSerif], italic)),
+ ("CMU-SansSerif-Bold.ttf", font_info!(["Computer Modern", SansSerif], bold)),
+ ("CMU-SansSerif-Bold-Italic.ttf", font_info!(["Computer Modern", SansSerif], bold, italic)),
+ ("CMU-Serif-Regular.ttf", font_info!(["Computer Modern", Serif])),
+ ("CMU-Serif-Italic.ttf", font_info!(["Computer Modern", Serif], italic)),
+ ("CMU-Serif-Bold.ttf", font_info!(["Computer Modern", Serif], bold)),
+ ("CMU-Serif-Bold-Italic.ttf", font_info!(["Computer Modern", Serif], bold, italic)),
+ ("CMU-Typewriter-Regular.ttf", font_info!(["Computer Modern", Monospace])),
+ ("CMU-Typewriter-Italic.ttf", font_info!(["Computer Modern", Monospace], italic)),
+ ("CMU-Typewriter-Bold.ttf", font_info!(["Computer Modern", Monospace], bold)),
+ ("CMU-Typewriter-Bold-Italic.ttf", font_info!(["Computer Modern", Monospace], bold, italic)),
+ ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])),
]));
// Typeset the source code.
diff --git a/src/func.rs b/src/func.rs
index 402bf6fa..0d2a6274 100644
--- a/src/func.rs
+++ b/src/func.rs
@@ -4,9 +4,10 @@ use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
-use crate::layout::{Layout, LayoutContext, LayoutResult};
-use crate::parsing::{ParseContext, ParseResult};
-use crate::syntax::FuncHeader;
+use crate::layout::{layout, Layout, LayoutContext, LayoutResult};
+use crate::layout::flex::FlexLayout;
+use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
+use crate::syntax::{SyntaxTree, FuncHeader};
/// Typesetting function types.
@@ -25,8 +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<(Option<Layout>, Option<LayoutContext>)>;
+ fn layout(&self, ctx: &LayoutContext) -> LayoutResult<Option<Layout>>;
}
impl PartialEq for dyn Function {
@@ -76,9 +76,14 @@ impl Scope {
Scope { parsers: HashMap::new() }
}
- /// Create a new scope with the standard functions contained.
+ /// Create a new scope with the standard functions contained:
+ /// - `italic`
+ /// - `bold`
pub fn with_std() -> Scope {
- Scope::new()
+ let mut std = Scope::new();
+ std.add::<BoldFunc>("bold");
+ std.add::<ItalicFunc>("italic");
+ std
}
/// Add a function type to the scope giving it a name.
@@ -103,3 +108,52 @@ impl Debug for Scope {
write!(f, "{:?}", self.parsers.keys())
}
}
+
+/// Creates style functions like bold and italic.
+macro_rules! style_func {
+ ($(#[$outer:meta])* pub struct $struct:ident { $name:expr },
+ $new_ctx:ident => $ctx_change:block) => {
+ $(#[$outer])*
+ #[derive(Debug, PartialEq)]
+ pub struct $struct { body: SyntaxTree }
+ impl Function for $struct {
+ fn parse(header: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
+ -> ParseResult<Self> 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)))
+ }
+ }
+
+ fn layout(&self, ctx: &LayoutContext) -> LayoutResult<Option<Layout>> {
+ // Change the context.
+ let mut $new_ctx = ctx.clone();
+ $ctx_change
+
+ // Create a box and put it into a flex layout.
+ let boxed = layout(&self.body, &$new_ctx)?;
+ let flex = FlexLayout::from_box(boxed);
+
+ Ok(Some(Layout::Flex(flex)))
+ }
+ }
+ };
+}
+
+style_func! {
+ /// Typesets text in bold.
+ pub struct BoldFunc { "bold" },
+ ctx => { ctx.style.bold = !ctx.style.bold }
+}
+
+style_func! {
+ /// Typesets text in italics.
+ pub struct ItalicFunc { "italic" },
+ ctx => { ctx.style.italic = !ctx.style.italic }
+}
diff --git a/src/layout/boxed.rs b/src/layout/boxed.rs
index 89c641bc..e712f650 100644
--- a/src/layout/boxed.rs
+++ b/src/layout/boxed.rs
@@ -86,6 +86,15 @@ impl BoxLayouter {
Ok(())
}
+ /// Add a sublayout at an absolute position.
+ pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
+ // Move all actions into this layout and translate absolute positions.
+ self.actions.reset_origin();
+ self.actions.add(TextAction::MoveAbsolute(position));
+ self.actions.set_origin(position);
+ self.actions.extend(layout.actions);
+ }
+
/// Add some space in between two boxes.
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
// Check whether this space fits.
@@ -100,20 +109,6 @@ impl BoxLayouter {
Ok(())
}
- /// Add a sublayout at an absolute position.
- pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
- // Move all actions into this layout and translate absolute positions.
- self.actions.reset_origin();
- self.actions.add(TextAction::MoveAbsolute(position));
- self.actions.set_origin(position);
- self.actions.extend(layout.actions);
- }
-
- /// Whether this layouter contains any items.
- pub fn is_empty(&self) -> bool {
- self.actions.is_empty()
- }
-
/// The remaining space for new boxes.
pub fn remaining(&self) -> Size2D {
Size2D {
@@ -122,6 +117,11 @@ impl BoxLayouter {
}
}
+ /// Whether this layouter contains any items.
+ pub fn is_empty(&self) -> bool {
+ self.actions.is_empty()
+ }
+
/// Finish the layouting and create a box layout from this.
pub fn finish(self) -> BoxLayout {
BoxLayout {
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 5fd7b157..f966eae2 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -1,7 +1,7 @@
//! Flexible and lazy layouting of boxes.
use crate::doc::TextAction;
-use crate::size::Size2D;
+use crate::size::{Size, Size2D};
use super::{BoxLayout, ActionList, LayoutSpace, LayoutResult, LayoutError};
@@ -10,8 +10,6 @@ use super::{BoxLayout, ActionList, LayoutSpace, LayoutResult, LayoutError};
pub struct FlexLayout {
/// The sublayouts composing this layout.
pub units: Vec<FlexUnit>,
- /// The layout space to arrange in.
- pub ctx: FlexContext,
}
/// A unit in a flex layout.
@@ -25,14 +23,20 @@ pub enum FlexUnit {
}
impl FlexLayout {
- /// Create a new flex layouter.
- pub fn new(ctx: FlexContext) -> FlexLayout {
+ /// Create a new flex layout.
+ pub fn new() -> FlexLayout {
FlexLayout {
- ctx,
units: vec![],
}
}
+ /// Create a new flex layout containing just one box.
+ pub fn from_box(boxed: BoxLayout) -> FlexLayout {
+ FlexLayout {
+ units: vec![FlexUnit::Boxed(boxed)],
+ }
+ }
+
/// Add a sublayout.
pub fn add_box(&mut self, layout: BoxLayout) {
self.units.push(FlexUnit::Boxed(layout));
@@ -54,8 +58,8 @@ impl FlexLayout {
}
/// Compute the justified layout.
- pub fn into_box(self) -> LayoutResult<BoxLayout> {
- FlexFinisher::new(self).finish()
+ pub fn finish(self, ctx: FlexContext) -> LayoutResult<BoxLayout> {
+ FlexFinisher::new(self, ctx).finish()
}
}
@@ -64,8 +68,8 @@ impl FlexLayout {
pub struct FlexContext {
/// The space to layout the boxes in.
pub space: LayoutSpace,
- /// The flex spacing (like line spacing).
- pub flex_spacing: f32,
+ /// The flex spacing between two lines of boxes.
+ pub flex_spacing: Size,
}
/// Finishes a flex layout by justifying the positions of the individual boxes.
@@ -82,11 +86,11 @@ struct FlexFinisher {
impl FlexFinisher {
/// Create the finisher from the layout.
- fn new(layout: FlexLayout) -> FlexFinisher {
- let space = layout.ctx.space;
+ fn new(layout: FlexLayout, ctx: FlexContext) -> FlexFinisher {
+ let space = ctx.space;
FlexFinisher {
units: layout.units,
- ctx: layout.ctx,
+ ctx,
actions: ActionList::new(),
dimensions: Size2D::zero(),
usable: space.usable(),
@@ -165,11 +169,13 @@ impl FlexFinisher {
/// Move to the next line.
fn newline(&mut self) {
- self.line.y *= self.ctx.flex_spacing;
self.dimensions.x = crate::size::max(self.dimensions.x, self.line.x);
+ if self.dimensions.y > Size::zero() {
+ self.dimensions.y += self.ctx.flex_spacing;
+ }
self.dimensions.y += self.line.y;
self.cursor.x = self.ctx.space.padding.left;
- self.cursor.y += self.line.y;
+ self.cursor.y += self.line.y + self.ctx.flex_spacing;
self.line = Size2D::zero();
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index f8819d0b..40948a13 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -70,6 +70,7 @@ struct Layouter<'a, 'p> {
flex_layout: FlexLayout,
flex_ctx: FlexContext,
text_ctx: TextContext<'a, 'p>,
+ func_ctx: LayoutContext<'a, 'p>,
}
impl<'a, 'p> Layouter<'a, 'p> {
@@ -85,7 +86,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
padding: SizeBox::zero(),
shrink_to_fit: true,
},
- flex_spacing: ctx.style.line_spacing,
+ flex_spacing: (ctx.style.line_spacing - 1.0) * Size::points(ctx.style.font_size),
};
// The mutable context for layouting single pieces of text.
@@ -94,12 +95,24 @@ impl<'a, 'p> Layouter<'a, 'p> {
style: ctx.style.clone(),
};
+ // The mutable context for layouting single functions.
+ let func_ctx = LayoutContext {
+ loader: &ctx.loader,
+ style: ctx.style.clone(),
+ space: LayoutSpace {
+ dimensions: ctx.space.usable(),
+ padding: SizeBox::zero(),
+ shrink_to_fit: true,
+ },
+ };
+
Layouter {
tree,
box_layouter: BoxLayouter::new(box_ctx),
- flex_layout: FlexLayout::new(flex_ctx),
+ flex_layout: FlexLayout::new(),
flex_ctx,
text_ctx,
+ func_ctx,
}
}
@@ -124,28 +137,53 @@ impl<'a, 'p> Layouter<'a, 'p> {
// Then start a new flex layouting process.
Node::Newline => {
// Finish the current paragraph into a box and add it.
- self.add_paragraph_spacing()?;
- let boxed = self.flex_layout.into_box()?;
+ let boxed = self.flex_layout.finish(self.flex_ctx)?;
self.box_layouter.add_box(boxed)?;
// Create a fresh flex layout for the next paragraph.
self.flex_ctx.space.dimensions = self.box_layouter.remaining();
- self.flex_layout = FlexLayout::new(self.flex_ctx);
+ self.flex_layout = FlexLayout::new();
+ self.add_paragraph_spacing()?;
},
// Toggle the text styles.
- Node::ToggleItalics => self.text_ctx.style.italic = !self.text_ctx.style.italic,
- Node::ToggleBold => self.text_ctx.style.bold = !self.text_ctx.style.bold,
+ Node::ToggleItalics => {
+ self.text_ctx.style.italic = !self.text_ctx.style.italic;
+ self.func_ctx.style.italic = !self.func_ctx.style.italic;
+ },
+ Node::ToggleBold => {
+ self.text_ctx.style.bold = !self.text_ctx.style.bold;
+ self.func_ctx.style.bold = !self.func_ctx.style.bold;
+ },
// Execute a function.
- Node::Func(_) => unimplemented!(),
+ Node::Func(func) => {
+ self.func_ctx.space.dimensions = self.box_layouter.remaining();
+ let layout = func.body.layout(&self.func_ctx)?;
+
+ // Add the potential layout.
+ if let Some(layout) = layout {
+ match layout {
+ Layout::Boxed(boxed) => {
+ // Finish the previous flex run before adding the box.
+ let previous = self.flex_layout.finish(self.flex_ctx)?;
+ self.box_layouter.add_box(previous)?;
+ self.box_layouter.add_box(boxed)?;
+
+ // Create a fresh flex layout for the following content.
+ self.flex_ctx.space.dimensions = self.box_layouter.remaining();
+ self.flex_layout = FlexLayout::new();
+ },
+ Layout::Flex(flex) => self.flex_layout.add_flexible(flex),
+ }
+ }
+ },
}
}
// If there are remainings, add them to the layout.
if !self.flex_layout.is_empty() {
- self.add_paragraph_spacing()?;
- let boxed = self.flex_layout.into_box()?;
+ let boxed = self.flex_layout.finish(self.flex_ctx)?;
self.box_layouter.add_box(boxed)?;
}
diff --git a/src/lib.rs b/src/lib.rs
index 334f20b9..4799188b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -216,7 +216,7 @@ mod test {
#[test]
fn features() {
test("features", r"
- **FEATURES TEST PAGE**
+ **Features Test Page**
__Multiline:__
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
@@ -226,7 +226,10 @@ mod test {
__Emoji:__ Hello World! 🌍
- __Styles:__ This is **bold** and that is __italic__!
+ __Styles:__ This is made **bold** and that __italic__ using the built-in syntax!
+
+ __Styles with functions:__ This is in [bold][boldface] and that is in [italic][italics]
+ using library functions!
");
}
diff --git a/src/parsing.rs b/src/parsing.rs
index 727a06d8..81a729ab 100644
--- a/src/parsing.rs
+++ b/src/parsing.rs
@@ -822,8 +822,7 @@ mod parse_tests {
}
}
- fn layout(&self, _: &LayoutContext)
- -> LayoutResult<(Option<Layout>, Option<LayoutContext>)> { Ok((None, None)) }
+ fn layout(&self, _: &LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
}
/// A testing function without a body.
@@ -840,8 +839,7 @@ mod parse_tests {
}
}
- fn layout(&self, _: &LayoutContext)
- -> LayoutResult<(Option<Layout>, Option<LayoutContext>)> { Ok((None, None)) }
+ fn layout(&self, _: &LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
}
}
diff --git a/src/style.rs b/src/style.rs
index 97f9552c..cd0a9579 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -30,7 +30,7 @@ impl Default for TextStyle {
italic: false,
bold: false,
font_size: 11.0,
- line_spacing: 1.25,
+ line_spacing: 1.2,
paragraph_spacing: 1.5,
}
}