From 89205368c2788842abb13f1c174607d85dc4abe1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 13 Mar 2019 19:13:49 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20space=20handling=20for=20multiline=20?= =?UTF-8?q?=F0=9F=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/doc.rs | 35 ++++++++++++++++++++++++++--------- src/engine.rs | 45 ++++++++++++++++++++++++++++++--------------- src/lib.rs | 20 ++++++++++++++++---- src/pdf.rs | 8 ++++---- 4 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/doc.rs b/src/doc.rs index 3e060f6e1..a3bdfeb66 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -15,10 +15,19 @@ pub struct Document { /// Default styles for a document. #[derive(Debug, Clone, PartialEq)] pub struct Style { - /// The width and height of the paper. - pub paper_size: [Size; 2], - /// The [left, top, right, bottom] margins of the paper. - pub margins: [Size; 4], + /// The width of the paper. + pub width: Size, + /// The height of the paper. + pub height: Size, + + /// The left margin of the paper. + pub margin_left: Size, + /// The top margin of the paper. + pub margin_top: Size, + /// The right margin of the paper. + pub margin_right: Size, + /// The bottom margin of the paper. + pub margin_bottom: Size, /// A fallback list of font families to use. pub font_families: Vec, @@ -31,9 +40,15 @@ pub struct Style { impl Default for Style { fn default() -> Style { Style { - // A4 paper with 1.5 cm margins in all directions - paper_size: [Size::from_mm(210.0), Size::from_mm(297.0)], - margins: [Size::from_cm(2.5); 4], + // A4 paper + width: Size::from_mm(210.0), + height: Size::from_mm(297.0), + + // A bit more on top and bottom + margin_left: Size::from_cm(2.5), + margin_top: Size::from_cm(3.0), + margin_right: Size::from_cm(2.5), + margin_bottom: Size::from_cm(3.0), // Default font family font_families: (&[ @@ -48,8 +63,10 @@ impl Default for Style { /// A page with text contents in a document. #[derive(Debug, Clone, PartialEq)] pub struct Page { - /// The width and height of the page. - pub size: [Size; 2], + /// The width of the page. + pub width: Size, + /// The height of the page. + pub height: Size, /// Text content on the page. pub text: Vec, } diff --git a/src/engine.rs b/src/engine.rs index ef50ecb42..2fbb830bb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -24,9 +24,9 @@ pub struct Engine<'s> { impl<'s> Engine<'s> { /// Create a new generator from a syntax tree. - pub fn new(tree: &'s SyntaxTree<'s>) -> Engine<'s> { + pub fn new(tree: &'s SyntaxTree<'s>, style: Style) -> Engine<'s> { Engine { - style: Style::default(), + style, tree, fonts: Vec::new(), active_font: 0, @@ -48,9 +48,9 @@ impl<'s> Engine<'s> { // Move cursor to top-left position self.text_commands.push(TextCommand::Move( - self.style.margins[0], - self.style.paper_size[1] - self.style.margins[1]) - ); + self.style.margin_left, + self.style.height - self.style.margin_top + )); // Set the current font self.text_commands.push(TextCommand::SetFont(0, self.style.font_size)); @@ -70,7 +70,8 @@ impl<'s> Engine<'s> { // Create a page from the contents. let page = Page { - size: self.style.paper_size, + width: self.style.width, + height: self.style.height, text: vec![Text { commands: self.text_commands, }], @@ -83,15 +84,10 @@ impl<'s> Engine<'s> { } fn write_word(&mut self, word: &str) { - let max_width = self.style.paper_size[0] - 2 * self.style.margins[0]; - let font = &self.fonts[self.active_font]; - let width = word.chars() - .map(|c| font.widths[font.map(c) as usize] * self.style.font_size) - .sum(); - - if self.current_width + width > max_width { + let width = self.width(word); + if self.would_overflow(width) { let vertical_move = - self.style.font_size * self.style.line_spacing * font.metrics.ascender; @@ -107,10 +103,29 @@ impl<'s> Engine<'s> { } fn write_space(&mut self) { - if !self.current_line.is_empty() { - self.write_word(" "); + let space_width = self.width(" "); + + if !self.would_overflow(space_width) && !self.current_line.is_empty() { + self.text_commands.push(TextCommand::Text(" ".to_owned())); + self.current_line.push_str(" "); + self.current_width += space_width; } } + + fn width(&self, word: &str) -> Size { + let font = &self.fonts[self.active_font]; + word.chars() + .map(|c| font.widths[font.map(c) as usize] * self.style.font_size) + .sum() + } + + fn would_overflow(&self, width: Size) -> bool { + let max_width = self.style.width + - self.style.margin_left + - self.style.margin_right; + + self.current_width + width > max_width + } } /// Result type used for parsing. diff --git a/src/lib.rs b/src/lib.rs index deb5b0667..ae12bf8b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,20 +37,32 @@ use std::error; use std::fmt; use std::io::Write; use crate::syntax::SyntaxTree; -use crate::doc::Document; +use crate::doc::{Document, Style}; /// Emits various compiled intermediates from source code. pub struct Compiler<'s> { /// The source code of the document. source: &'s str, + /// Style for typesetting. + style: Style, } impl<'s> Compiler<'s> { /// Create a new compiler from a document. #[inline] pub fn new(source: &'s str) -> Compiler<'s> { - Compiler { source } + Compiler { + source, + style: Style::default(), + } + } + + /// Set the default style for typesetting. + #[inline] + pub fn style(&mut self, style: Style) -> &mut Self { + self.style = style; + self } /// Return an iterator over the tokens of the document. @@ -69,7 +81,7 @@ impl<'s> Compiler<'s> { #[inline] pub fn typeset(&self) -> Result { let tree = self.parse()?; - Engine::new(&tree).typeset().map_err(Into::into) + Engine::new(&tree, self.style.clone()).typeset().map_err(Into::into) } /// Write the document as a _PDF_, returning how many bytes were written. @@ -159,7 +171,7 @@ mod test { } #[test] - fn long() { + fn long_styled() { test("wikipedia", r#" Typesetting is the composition of text by means of arranging physical types or the digital equivalents. Stored letters and other symbols (called sorts in mechanical diff --git a/src/pdf.rs b/src/pdf.rs index d5d3b081a..e5a2cf6be 100644 --- a/src/pdf.rs +++ b/src/pdf.rs @@ -122,11 +122,11 @@ impl<'a, W: Write> PdfCreator<'a, W> { // The page objects let mut id = self.offsets.pages.0; for page in &self.doc.pages { - let width = page.size[0].to_points(); - let height = page.size[1].to_points(); - self.writer.write_obj(id, Page::new(self.offsets.page_tree) - .media_box(Rect::new(0.0, 0.0, width, height)) + .media_box(Rect::new( + 0.0, 0.0, + page.width.to_points(), page.height.to_points()) + ) .contents(self.offsets.contents.0 ..= self.offsets.contents.1) )?;