summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-04-01 13:25:00 +0200
committerLaurenz <laurmaedje@gmail.com>2019-04-01 13:33:54 +0200
commit6c29e45ce2c848fe88003b62aaf8aacf7c0b8da6 (patch)
tree0e7d308ea5115c99141075113501206730fe82fa /src
parent3b4b55c59ecc85fc7446795f136baf632d0c9449 (diff)
Implement paragraphs 📜
Diffstat (limited to 'src')
-rw-r--r--src/engine/mod.rs25
-rw-r--r--src/font.rs2
-rw-r--r--src/lib.rs8
-rw-r--r--src/parsing.rs115
4 files changed, 105 insertions, 45 deletions
diff --git a/src/engine/mod.rs b/src/engine/mod.rs
index 80341dec..ea81053b 100644
--- a/src/engine/mod.rs
+++ b/src/engine/mod.rs
@@ -61,7 +61,10 @@ impl<'t> Engine<'t> {
match node {
Node::Word(word) => self.write_word(word)?,
Node::Space => self.write_space()?,
- Node::Newline => {},
+ Node::Newline => {
+ self.write_buffered_text();
+ self.move_newline(self.ctx.style.paragraph_spacing);
+ },
Node::ToggleItalics => self.italic = !self.italic,
Node::ToggleBold => self.bold = !self.bold,
@@ -105,7 +108,7 @@ impl<'t> Engine<'t> {
// If this would overflow, we move to a new line and finally write the previous one.
if self.would_overflow(word_width) {
self.write_buffered_text();
- self.move_newline();
+ self.move_newline(1.0);
}
// Finally write the word.
@@ -152,18 +155,23 @@ impl<'t> Engine<'t> {
}
/// Move to a new line.
- fn move_newline(&mut self) {
- let vertical_move = - if self.current_max_vertical_move == Size::zero() {
+ fn move_newline(&mut self, factor: f32) {
+ if self.active_font == std::usize::MAX {
+ return;
+ }
+
+ let vertical_move = if self.current_max_vertical_move == Size::zero() {
// If max vertical move is still zero, the line is empty and we take the
// font size from the previous line.
self.ctx.style.font_size
* self.ctx.style.line_spacing
* self.get_font_at(self.active_font).metrics.ascender
+ * factor
} else {
self.current_max_vertical_move
};
- self.text_commands.push(TextCommand::Move(Size::zero(), vertical_move));
+ self.text_commands.push(TextCommand::Move(Size::zero(), -vertical_move));
self.current_max_vertical_move = Size::zero();
self.current_line_width = Size::zero();
}
@@ -383,6 +391,8 @@ pub struct Style {
pub font_size: f32,
/// The line spacing (as a multiple of the font size).
pub line_spacing: f32,
+ /// The spacing for paragraphs (as a multiple of the line spacing).
+ pub paragraph_spacing: f32,
}
impl Default for Style {
@@ -394,15 +404,16 @@ impl Default for Style {
height: Size::from_mm(297.0),
// Margins. A bit more on top and bottom.
- margin_left: Size::from_cm(2.5),
+ margin_left: Size::from_cm(3.0),
margin_top: Size::from_cm(3.0),
- margin_right: Size::from_cm(2.5),
+ margin_right: Size::from_cm(3.0),
margin_bottom: Size::from_cm(3.0),
// Default font family, font size and line spacing.
font_families: vec![SansSerif, Serif, Monospace],
font_size: 11.0,
line_spacing: 1.25,
+ paragraph_spacing: 1.5,
}
}
}
diff --git a/src/font.rs b/src/font.rs
index f9039a71..49670df8 100644
--- a/src/font.rs
+++ b/src/font.rs
@@ -193,7 +193,7 @@ impl<T> FontData for T where T: Read + Seek {}
/// Describes a font.
///
-/// Can be constructed conventiently with the [`font_info`] macro.
+/// Can be constructed conveniently with the [`font_info`] macro.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FontInfo {
/// The font families this font is part of.
diff --git a/src/lib.rs b/src/lib.rs
index d9d250b9..f9883a87 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23,7 +23,7 @@
//! let src = "Hello World from __Typeset__! 🌍";
//!
//! // Create a compiler with a font provider that provides three fonts
-//! // (the default sans-serif fonts and a fallback for the emoji).
+//! // (two sans-serif fonts and a fallback for the emoji).
//! let mut compiler = Compiler::new();
//! compiler.add_font_provider(FileSystemFontProvider::new("../fonts", vec![
//! ("NotoSans-Regular.ttf", font_info!(["NotoSans", "Noto", SansSerif])),
@@ -197,7 +197,11 @@ mod test {
#[test]
fn styled() {
- test("styled", "**Hello World**. That's __great__!");
+ test("styled", "
+ **Hello World**.
+
+ That's __great__!
+ ");
}
#[test]
diff --git a/src/parsing.rs b/src/parsing.rs
index 085e7d1b..5333cbd3 100644
--- a/src/parsing.rs
+++ b/src/parsing.rs
@@ -130,9 +130,7 @@ impl<'s> Iterator for Tokens<'s> {
"=" if self.state == TS::Function => Token::Equals,
// Double star/underscore
- "*" if afterwards == Some(&"*") => {
- self.consumed(Token::DoubleStar)
- },
+ "*" if afterwards == Some(&"*") => self.consumed(Token::DoubleStar),
"__" => Token::DoubleUnderscore,
// Newlines
@@ -219,6 +217,10 @@ pub struct Parser<'s, T> where T: Iterator<Item=Token<'s>> {
enum ParserState {
/// The base state of the parser.
Body,
+ /// We saw one newline already.
+ FirstNewline,
+ /// We wrote a newline.
+ WroteNewline,
/// Inside a function header.
Function,
}
@@ -238,30 +240,46 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
pub(crate) fn parse(mut self) -> ParseResult<SyntaxTree<'s>> {
use ParserState as PS;
- while let Some(token) = self.tokens.next() {
- // Comment
+ while let Some(token) = self.tokens.peek() {
+ let token = *token;
+
+ // Skip over comments.
if token == Token::Hashtag {
self.skip_while(|&t| t != Token::Newline);
self.advance();
}
+ // Handles all the states.
match self.state {
- PS::Body => match token {
- // Whitespace
- Token::Space => self.append(Node::Space),
+ PS::FirstNewline => match token {
Token::Newline => {
- self.append(Node::Newline);
- if self.tokens.peek() != Some(&Token::Space) {
- self.append(Node::Space);
- }
+ self.append_consumed(Node::Newline);
+ self.switch(PS::WroteNewline);
},
+ Token::Space => self.append_space_consumed(),
+ _ => {
+ self.append_space();
+ self.switch(PS::Body);
+ },
+ }
+
+ PS::WroteNewline => match token {
+ Token::Newline | Token::Space => self.append_space_consumed(),
+ _ => self.switch(PS::Body),
+ }
+
+ PS::Body => match token {
+ // Whitespace
+ Token::Space => self.append_space_consumed(),
+ Token::Newline => self.switch_consumed(PS::FirstNewline),
// Words
- Token::Word(word) => self.append(Node::Word(word)),
+ Token::Word(word) => self.append_consumed(Node::Word(word)),
// Functions
- Token::LeftBracket => self.switch(PS::Function),
+ Token::LeftBracket => self.switch_consumed(PS::Function),
Token::RightBracket => {
+ self.advance();
match self.stack.pop() {
Some(func) => self.append(Node::Func(func)),
None => return self.err("unexpected closing bracket"),
@@ -269,9 +287,9 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
},
// Modifiers
- Token::DoubleUnderscore => self.append(Node::ToggleItalics),
- Token::DoubleStar => self.append(Node::ToggleBold),
- Token::Dollar => self.append(Node::ToggleMath),
+ Token::DoubleUnderscore => self.append_consumed(Node::ToggleItalics),
+ Token::DoubleStar => self.append_consumed(Node::ToggleBold),
+ Token::Dollar => self.append_consumed(Node::ToggleMath),
// Should not happen
Token::Colon | Token::Equals | Token::Hashtag => unreachable!(),
@@ -283,6 +301,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
_ => return self.err("expected identifier"),
};
+ self.advance();
if self.tokens.next() != Some(Token::RightBracket) {
return self.err("expected closing bracket");
}
@@ -303,6 +322,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
self.switch(PS::Body);
},
+
}
}
@@ -318,29 +338,51 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
self.tokens.next();
}
- /// Skip tokens until the condition is met.
- fn skip_while<F>(&mut self, f: F) where F: Fn(&Token) -> bool {
- while let Some(token) = self.tokens.peek() {
- if !f(token) {
- break;
- }
- self.advance();
+ /// Append a node to the top-of-stack function or the main tree itself.
+ fn append(&mut self, node: Node<'s>) {
+ match self.stack.last_mut() {
+ Some(func) => func.body.as_mut().unwrap(),
+ None => &mut self.tree,
+ }.nodes.push(node);
+ }
+
+ /// Advance and return the given node.
+ fn append_consumed(&mut self, node: Node<'s>) { self.advance(); self.append(node); }
+
+ /// Append a space if there is not one already.
+ fn append_space(&mut self) {
+ if self.last() != Some(&Node::Space) {
+ self.append(Node::Space);
}
}
+ /// Advance and append a space if there is not one already.
+ fn append_space_consumed(&mut self) { self.advance(); self.append_space(); }
+
/// Switch the state.
fn switch(&mut self, state: ParserState) {
self.state = state;
}
- /// Append a node to the top-of-stack function or the main tree itself.
- fn append(&mut self, node: Node<'s>) {
- let tree = match self.stack.last_mut() {
- Some(func) => func.body.as_mut().unwrap(),
- None => &mut self.tree,
- };
+ /// Advance and switch the state.
+ fn switch_consumed(&mut self, state: ParserState) { self.advance(); self.state = state; }
+
+ /// The last appended node of the top-of-stack function or of the main tree.
+ fn last(&self) -> Option<&Node<'s>> {
+ match self.stack.last() {
+ Some(func) => func.body.as_ref().unwrap(),
+ None => &self.tree,
+ }.nodes.last()
+ }
- tree.nodes.push(node);
+ /// Skip tokens until the condition is met.
+ fn skip_while<F>(&mut self, f: F) where F: Fn(&Token) -> bool {
+ while let Some(token) = self.tokens.peek() {
+ if !f(token) {
+ break;
+ }
+ self.advance();
+ }
}
/// Gives a parsing error with a message.
@@ -506,10 +548,13 @@ mod parse_tests {
/// Test whether newlines generate the correct whitespace.
#[test]
fn parse_newlines_whitespace() {
- test("Hello \n World", tree! { W("Hello"), S, N, S, W("World") });
- test("Hello\nWorld", tree! { W("Hello"), N, S, W("World") });
- test("Hello\n World", tree! { W("Hello"), N, S, W("World") });
- test("Hello \nWorld", tree! { W("Hello"), S, N, S, W("World") });
+ test("Hello\nWorld", tree! { W("Hello"), S, W("World") });
+ test("Hello \n World", tree! { W("Hello"), S, W("World") });
+ test("Hello\n\nWorld", tree! { W("Hello"), N, W("World") });
+ test("Hello \n\nWorld", tree! { W("Hello"), S, N, W("World") });
+ test("Hello\n\n World", tree! { W("Hello"), N, S, W("World") });
+ test("Hello \n \n \n World", tree! { W("Hello"), S, N, S, W("World") });
+ test("Hello\n \n\n World", tree! { W("Hello"), S, N, S, W("World") });
}
/// Parse things dealing with functions.