Fix space handling for multiline 🔨

This commit is contained in:
Laurenz 2019-03-13 19:13:49 +01:00
parent 0c87c0c5a5
commit 89205368c2
4 changed files with 76 additions and 32 deletions

View File

@ -15,10 +15,19 @@ pub struct Document {
/// Default styles for a document. /// Default styles for a document.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Style { pub struct Style {
/// The width and height of the paper. /// The width of the paper.
pub paper_size: [Size; 2], pub width: Size,
/// The [left, top, right, bottom] margins of the paper. /// The height of the paper.
pub margins: [Size; 4], 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. /// A fallback list of font families to use.
pub font_families: Vec<String>, pub font_families: Vec<String>,
@ -31,9 +40,15 @@ pub struct Style {
impl Default for Style { impl Default for Style {
fn default() -> Style { fn default() -> Style {
Style { Style {
// A4 paper with 1.5 cm margins in all directions // A4 paper
paper_size: [Size::from_mm(210.0), Size::from_mm(297.0)], width: Size::from_mm(210.0),
margins: [Size::from_cm(2.5); 4], 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 // Default font family
font_families: (&[ font_families: (&[
@ -48,8 +63,10 @@ impl Default for Style {
/// A page with text contents in a document. /// A page with text contents in a document.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Page { pub struct Page {
/// The width and height of the page. /// The width of the page.
pub size: [Size; 2], pub width: Size,
/// The height of the page.
pub height: Size,
/// Text content on the page. /// Text content on the page.
pub text: Vec<Text>, pub text: Vec<Text>,
} }

View File

@ -24,9 +24,9 @@ pub struct Engine<'s> {
impl<'s> Engine<'s> { impl<'s> Engine<'s> {
/// Create a new generator from a syntax tree. /// 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 { Engine {
style: Style::default(), style,
tree, tree,
fonts: Vec::new(), fonts: Vec::new(),
active_font: 0, active_font: 0,
@ -48,9 +48,9 @@ impl<'s> Engine<'s> {
// Move cursor to top-left position // Move cursor to top-left position
self.text_commands.push(TextCommand::Move( self.text_commands.push(TextCommand::Move(
self.style.margins[0], self.style.margin_left,
self.style.paper_size[1] - self.style.margins[1]) self.style.height - self.style.margin_top
); ));
// Set the current font // Set the current font
self.text_commands.push(TextCommand::SetFont(0, self.style.font_size)); 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. // Create a page from the contents.
let page = Page { let page = Page {
size: self.style.paper_size, width: self.style.width,
height: self.style.height,
text: vec![Text { text: vec![Text {
commands: self.text_commands, commands: self.text_commands,
}], }],
@ -83,15 +84,10 @@ impl<'s> Engine<'s> {
} }
fn write_word(&mut self, word: &str) { 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 font = &self.fonts[self.active_font];
let width = word.chars()
.map(|c| font.widths[font.map(c) as usize] * self.style.font_size)
.sum();
let width = self.width(word);
if self.current_width + width > max_width { if self.would_overflow(width) {
let vertical_move = - self.style.font_size let vertical_move = - self.style.font_size
* self.style.line_spacing * self.style.line_spacing
* font.metrics.ascender; * font.metrics.ascender;
@ -107,10 +103,29 @@ impl<'s> Engine<'s> {
} }
fn write_space(&mut self) { fn write_space(&mut self) {
if !self.current_line.is_empty() { let space_width = self.width(" ");
self.write_word(" ");
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. /// Result type used for parsing.

View File

@ -37,20 +37,32 @@ use std::error;
use std::fmt; use std::fmt;
use std::io::Write; use std::io::Write;
use crate::syntax::SyntaxTree; use crate::syntax::SyntaxTree;
use crate::doc::Document; use crate::doc::{Document, Style};
/// Emits various compiled intermediates from source code. /// Emits various compiled intermediates from source code.
pub struct Compiler<'s> { pub struct Compiler<'s> {
/// The source code of the document. /// The source code of the document.
source: &'s str, source: &'s str,
/// Style for typesetting.
style: Style,
} }
impl<'s> Compiler<'s> { impl<'s> Compiler<'s> {
/// Create a new compiler from a document. /// Create a new compiler from a document.
#[inline] #[inline]
pub fn new(source: &'s str) -> Compiler<'s> { 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. /// Return an iterator over the tokens of the document.
@ -69,7 +81,7 @@ impl<'s> Compiler<'s> {
#[inline] #[inline]
pub fn typeset(&self) -> Result<Document, Error> { pub fn typeset(&self) -> Result<Document, Error> {
let tree = self.parse()?; 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. /// Write the document as a _PDF_, returning how many bytes were written.
@ -159,7 +171,7 @@ mod test {
} }
#[test] #[test]
fn long() { fn long_styled() {
test("wikipedia", r#" test("wikipedia", r#"
Typesetting is the composition of text by means of arranging physical types or the 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 digital equivalents. Stored letters and other symbols (called sorts in mechanical

View File

@ -122,11 +122,11 @@ impl<'a, W: Write> PdfCreator<'a, W> {
// The page objects // The page objects
let mut id = self.offsets.pages.0; let mut id = self.offsets.pages.0;
for page in &self.doc.pages { 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) 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) .contents(self.offsets.contents.0 ..= self.offsets.contents.1)
)?; )?;