Implement paragraphs 📜

This commit is contained in:
Laurenz 2019-04-01 13:25:00 +02:00
parent 3b4b55c59e
commit 6c29e45ce2
4 changed files with 106 additions and 46 deletions

View File

@ -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,
}
}
}

View File

@ -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.

View File

@ -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]

View File

@ -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::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<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,6 +338,43 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
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<F>(&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<Item=Token<'s>> {
}
}
/// 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<R, S: Into<String>>(&self, message: S) -> ParseResult<R> {
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.