diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 80341decb..ea81053b2 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 f9039a718..49670df80 100644 --- a/src/font.rs +++ b/src/font.rs @@ -193,7 +193,7 @@ impl 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 d9d250b96..f9883a871 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 085e7d1b6..5333cbd3c 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> { 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> { pub(crate) fn parse(mut self) -> ParseResult> { 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::FirstNewline => match token { + Token::Newline => { + 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(Node::Space), - Token::Newline => { - self.append(Node::Newline); - if self.tokens.peek() != Some(&Token::Space) { - self.append(Node::Space); - } - }, + 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> { }, // 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> { _ => 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> { self.switch(PS::Body); }, + } } @@ -318,6 +338,43 @@ impl<'s, T> Parser<'s, T> where T: Iterator> { self.tokens.next(); } + /// 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; + } + + /// 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() + } + /// Skip tokens until the condition is met. fn skip_while(&mut self, f: F) where F: Fn(&Token) -> bool { while let Some(token) = self.tokens.peek() { @@ -328,21 +385,6 @@ impl<'s, T> Parser<'s, T> where T: Iterator> { } } - /// 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, - }; - - tree.nodes.push(node); - } - /// Gives a parsing error with a message. fn err>(&self, message: S) -> ParseResult { Err(ParseError { message: message.into() }) @@ -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.