Implement paragraphs 📜
This commit is contained in:
parent
3b4b55c59e
commit
6c29e45ce2
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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]
|
||||
|
117
src/parsing.rs
117
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::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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user