Fix top-left text alignment 📐

This commit is contained in:
Laurenz 2019-06-22 12:51:06 +02:00
parent f6fe3b5cdd
commit c7ee2b393a
8 changed files with 71 additions and 56 deletions

View File

@ -20,19 +20,17 @@ pub struct Page {
pub width: Size, pub width: Size,
/// The height of the page. /// The height of the page.
pub height: Size, pub height: Size,
/// Text actions specifying how to draw text content on the page. /// Layouting actions specifying how to draw content on the page.
pub actions: Vec<TextAction>, pub actions: Vec<LayoutAction>,
} }
/// A text layouting action. /// A layouting action.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TextAction { pub enum LayoutAction {
/// Move to an absolute position. /// Move to an absolute position.
MoveAbsolute(Size2D), MoveAbsolute(Size2D),
/// Move from the _start_ of the current line by an (x, y) offset.
MoveNewline(Size2D),
/// Write text starting at the current position.
WriteText(String),
/// Set the font by index and font size. /// Set the font by index and font size.
SetFont(usize, f32), SetFont(usize, f32),
/// Write text starting at the current position.
WriteText(String),
} }

View File

@ -8,9 +8,9 @@ use pdf::doc::{Catalog, PageTree, Page, Resource, Text};
use pdf::font::{Type0Font, CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags}; use pdf::font::{Type0Font, CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags};
use pdf::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream}; use pdf::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream};
use crate::doc::{Document, Page as DocPage, TextAction}; use crate::doc::{Document, Page as DocPage, LayoutAction};
use crate::font::{Font, FontError}; use crate::font::{Font, FontError};
use crate::size::Size; use crate::size::{Size, Size2D};
/// Exports documents into _PDFs_. /// Exports documents into _PDFs_.
@ -72,8 +72,8 @@ impl<'d, W: Write> PdfEngine<'d, W> {
for page in &doc.pages { for page in &doc.pages {
for action in &page.actions { for action in &page.actions {
match action { match action {
TextAction::WriteText(string) => chars[font].extend(string.chars()), LayoutAction::WriteText(string) => chars[font].extend(string.chars()),
TextAction::SetFont(id, _) => font = *id, LayoutAction::SetFont(id, _) => font = *id,
_ => {}, _ => {},
} }
} }
@ -123,7 +123,7 @@ impl<'d, W: Write> PdfEngine<'d, W> {
// The page objects // The page objects
for (id, page) in ids(self.offsets.pages).zip(&self.doc.pages) { for (id, page) in ids(self.offsets.pages).zip(&self.doc.pages) {
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, page.width.to_points(), page.height.to_points())) .media_box(Rect::new(0.0, 0.0, page.width.to_pt(), page.height.to_pt()))
.contents(ids(self.offsets.contents)) .contents(ids(self.offsets.contents))
)?; )?;
} }
@ -141,23 +141,40 @@ impl<'d, W: Write> PdfEngine<'d, W> {
/// Write the content of a page. /// Write the content of a page.
fn write_page(&mut self, id: u32, page: &DocPage) -> PdfResult<()> { fn write_page(&mut self, id: u32, page: &DocPage) -> PdfResult<()> {
let mut font = 0; // The currently used font.
let mut text = Text::new(); let mut active_font = (std::usize::MAX, 0.0);
text.tm(1.0, 0.0, 0.0, 1.0, 0.0, page.height.to_points()); // The last set position and font, these get flushed when content is written.
let mut next_pos = Some(Size2D::zero());
let mut next_font = None;
// The output text.
let mut text = Text::new();
for action in &page.actions { for action in &page.actions {
match action { match action {
TextAction::MoveAbsolute(pos) => { LayoutAction::MoveAbsolute(pos) => next_pos = Some(*pos),
let x = pos.x.to_points(); LayoutAction::SetFont(id, size) => next_font = Some((*id, *size)),
let y = (page.height - pos.y).to_points(); LayoutAction::WriteText(string) => {
text.tm(1.0, 0.0, 0.0, 1.0, x, y); // Flush the font if it is different from the current.
}, if let Some((id, size)) = next_font {
TextAction::MoveNewline(pos) => { text.td(pos.x.to_points(), -pos.y.to_points()); }, if (id, size) != active_font {
TextAction::WriteText(string) => { text.tj(self.fonts[font].encode(&string)); }, text.tf(id as u32 + 1, size);
TextAction::SetFont(id, size) => { active_font = (id, size);
font = *id; next_font = None;
text.tf(*id as u32 + 1, *size); }
}
// Flush the position.
if let Some(pos) = next_pos {
let x = pos.x.to_pt();
let y = (page.height - pos.y - Size::pt(active_font.1)).to_pt();
text.tm(1.0, 0.0, 0.0, 1.0, x, y);
next_pos = None;
}
// Write the text.
text.tj(self.fonts[active_font.0].encode(&string));
}, },
} }
} }
@ -249,7 +266,7 @@ impl PdfFont {
fn new(font: &Font, chars: &HashSet<char>) -> PdfResult<PdfFont> { fn new(font: &Font, chars: &HashSet<char>) -> PdfResult<PdfFont> {
/// Convert a size into a _PDF_ glyph unit. /// Convert a size into a _PDF_ glyph unit.
fn size_to_glyph_unit(size: Size) -> GlyphUnit { fn size_to_glyph_unit(size: Size) -> GlyphUnit {
(1000.0 * size.to_points()).round() as GlyphUnit (1000.0 * size.to_pt()).round() as GlyphUnit
} }
// Subset the font using the selected characters. // Subset the font using the selected characters.

View File

@ -60,7 +60,7 @@ impl Font {
// Create a conversion function between font units and sizes. // Create a conversion function between font units and sizes.
let font_unit_ratio = 1.0 / (head.units_per_em as f32); let font_unit_ratio = 1.0 / (head.units_per_em as f32);
let font_unit_to_size = |x| Size::points(font_unit_ratio * x as f32); let font_unit_to_size = |x| Size::pt(font_unit_ratio * x as f32);
// Find out the name of the font. // Find out the name of the font.
let font_name = name.get_decoded(NameEntry::PostScriptName) let font_name = name.get_decoded(NameEntry::PostScriptName)

View File

@ -1,6 +1,6 @@
//! Block-style layouting of boxes. //! Block-style layouting of boxes.
use crate::doc::{Document, Page, TextAction}; use crate::doc::{Document, Page, LayoutAction};
use crate::font::Font; use crate::font::Font;
use crate::size::{Size, Size2D}; use crate::size::{Size, Size2D};
use super::{ActionList, LayoutSpace, LayoutResult, LayoutError}; use super::{ActionList, LayoutSpace, LayoutResult, LayoutError};
@ -12,7 +12,7 @@ pub struct BoxLayout {
/// The size of the box. /// The size of the box.
pub dimensions: Size2D, pub dimensions: Size2D,
/// The actions composing this layout. /// The actions composing this layout.
pub actions: Vec<TextAction>, pub actions: Vec<LayoutAction>,
} }
impl BoxLayout { impl BoxLayout {
@ -90,7 +90,7 @@ impl BoxLayouter {
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) { pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
// Move all actions into this layout and translate absolute positions. // Move all actions into this layout and translate absolute positions.
self.actions.reset_origin(); self.actions.reset_origin();
self.actions.add(TextAction::MoveAbsolute(position)); self.actions.add(LayoutAction::MoveAbsolute(position));
self.actions.set_origin(position); self.actions.set_origin(position);
self.actions.extend(layout.actions); self.actions.extend(layout.actions);
} }

View File

@ -1,6 +1,6 @@
//! Flexible and lazy layouting of boxes. //! Flexible and lazy layouting of boxes.
use crate::doc::TextAction; use crate::doc::LayoutAction;
use crate::size::{Size, Size2D}; use crate::size::{Size, Size2D};
use super::{BoxLayout, ActionList, LayoutSpace, LayoutResult, LayoutError}; use super::{BoxLayout, ActionList, LayoutSpace, LayoutResult, LayoutError};
@ -157,7 +157,7 @@ impl FlexFinisher {
fn append(&mut self, layout: BoxLayout) { fn append(&mut self, layout: BoxLayout) {
// Move all actions into this layout and translate absolute positions. // Move all actions into this layout and translate absolute positions.
self.actions.reset_origin(); self.actions.reset_origin();
self.actions.add(TextAction::MoveAbsolute(self.cursor)); self.actions.add(LayoutAction::MoveAbsolute(self.cursor));
self.actions.set_origin(self.cursor); self.actions.set_origin(self.cursor);
self.actions.extend(layout.actions); self.actions.extend(layout.actions);

View File

@ -1,6 +1,6 @@
//! The layouting engine. //! The layouting engine.
use crate::doc::TextAction; use crate::doc::LayoutAction;
use crate::font::{FontLoader, FontError}; use crate::font::{FontLoader, FontError};
use crate::size::{Size, Size2D, SizeBox}; use crate::size::{Size, Size2D, SizeBox};
use crate::syntax::{SyntaxTree, Node}; use crate::syntax::{SyntaxTree, Node};
@ -86,7 +86,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
padding: SizeBox::zero(), padding: SizeBox::zero(),
shrink_to_fit: true, shrink_to_fit: true,
}, },
flex_spacing: (ctx.style.line_spacing - 1.0) * Size::points(ctx.style.font_size), flex_spacing: (ctx.style.line_spacing - 1.0) * Size::pt(ctx.style.font_size),
}; };
// The mutable context for layouting single pieces of text. // The mutable context for layouting single pieces of text.
@ -192,7 +192,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
/// Add the spacing between two paragraphs. /// Add the spacing between two paragraphs.
fn add_paragraph_spacing(&mut self) -> LayoutResult<()> { fn add_paragraph_spacing(&mut self) -> LayoutResult<()> {
let size = Size::points(self.text_ctx.style.font_size) let size = Size::pt(self.text_ctx.style.font_size)
* (self.text_ctx.style.line_spacing * self.text_ctx.style.paragraph_spacing - 1.0); * (self.text_ctx.style.line_spacing * self.text_ctx.style.paragraph_spacing - 1.0);
self.box_layouter.add_space(size) self.box_layouter.add_space(size)
} }
@ -201,7 +201,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
/// Manipulates and optimizes a list of actions. /// Manipulates and optimizes a list of actions.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ActionList { pub struct ActionList {
actions: Vec<TextAction>, actions: Vec<LayoutAction>,
origin: Size2D, origin: Size2D,
active_font: (usize, f32), active_font: (usize, f32),
} }
@ -218,8 +218,8 @@ impl ActionList {
/// Add an action to the list if it is not useless /// Add an action to the list if it is not useless
/// (like changing to a font that is already active). /// (like changing to a font that is already active).
pub fn add(&mut self, action: TextAction) { pub fn add(&mut self, action: LayoutAction) {
use TextAction::*; use LayoutAction::*;
match action { match action {
MoveAbsolute(pos) => self.actions.push(MoveAbsolute(self.origin + pos)), MoveAbsolute(pos) => self.actions.push(MoveAbsolute(self.origin + pos)),
SetFont(index, size) => if (index, size) != self.active_font { SetFont(index, size) => if (index, size) != self.active_font {
@ -231,7 +231,7 @@ impl ActionList {
} }
/// Add a series of actions. /// Add a series of actions.
pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=TextAction> { pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=LayoutAction> {
for action in actions.into_iter() { for action in actions.into_iter() {
self.add(action); self.add(action);
} }
@ -254,7 +254,7 @@ impl ActionList {
} }
/// Return the list of actions as a vector. /// Return the list of actions as a vector.
pub fn into_vec(self) -> Vec<TextAction> { pub fn into_vec(self) -> Vec<LayoutAction> {
self.actions self.actions
} }
} }

View File

@ -1,6 +1,6 @@
//! Layouting of text into boxes. //! Layouting of text into boxes.
use crate::doc::TextAction; use crate::doc::LayoutAction;
use crate::font::FontQuery; use crate::font::FontQuery;
use crate::size::{Size, Size2D}; use crate::size::{Size, Size2D};
use super::*; use super::*;
@ -39,11 +39,11 @@ pub fn layout(text: &str, ctx: &TextContext) -> LayoutResult<BoxLayout> {
// Change the font if necessary. // Change the font if necessary.
if active_font != index { if active_font != index {
if !buffer.is_empty() { if !buffer.is_empty() {
actions.push(TextAction::WriteText(buffer)); actions.push(LayoutAction::WriteText(buffer));
buffer = String::new(); buffer = String::new();
} }
actions.push(TextAction::SetFont(index, ctx.style.font_size)); actions.push(LayoutAction::SetFont(index, ctx.style.font_size));
active_font = index; active_font = index;
} }
@ -52,11 +52,11 @@ pub fn layout(text: &str, ctx: &TextContext) -> LayoutResult<BoxLayout> {
// Write the remaining characters. // Write the remaining characters.
if !buffer.is_empty() { if !buffer.is_empty() {
actions.push(TextAction::WriteText(buffer)); actions.push(LayoutAction::WriteText(buffer));
} }
Ok(BoxLayout { Ok(BoxLayout {
dimensions: Size2D::new(width, Size::points(ctx.style.font_size)), dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
actions, actions,
}) })
} }

View File

@ -42,11 +42,7 @@ impl Size {
/// Create a size from an amount of points. /// Create a size from an amount of points.
#[inline] #[inline]
pub fn points(points: f32) -> Size { Size { points } } pub fn pt(points: f32) -> Size { Size { points } }
/// Create a size from an amount of inches.
#[inline]
pub fn inches(inches: f32) -> Size { Size { points: 72.0 * inches } }
/// Create a size from an amount of millimeters. /// Create a size from an amount of millimeters.
#[inline] #[inline]
@ -56,13 +52,13 @@ impl Size {
#[inline] #[inline]
pub fn cm(cm: f32) -> Size { Size { points: 28.3465 * cm } } pub fn cm(cm: f32) -> Size { Size { points: 28.3465 * cm } }
/// Create a size from an amount of inches.
#[inline]
pub fn inches(inches: f32) -> Size { Size { points: 72.0 * inches } }
/// Convert this size into points. /// Convert this size into points.
#[inline] #[inline]
pub fn to_points(&self) -> f32 { self.points } pub fn to_pt(&self) -> f32 { self.points }
/// Convert this size into inches.
#[inline]
pub fn to_inches(&self) -> f32 { self.points * 0.0138889 }
/// Convert this size into millimeters. /// Convert this size into millimeters.
#[inline] #[inline]
@ -71,6 +67,10 @@ impl Size {
/// Convert this size into centimeters. /// Convert this size into centimeters.
#[inline] #[inline]
pub fn to_cm(&self) -> f32 { self.points * 0.0352778 } pub fn to_cm(&self) -> f32 { self.points * 0.0352778 }
/// Convert this size into inches.
#[inline]
pub fn to_inches(&self) -> f32 { self.points * 0.0138889 }
} }
impl Size2D { impl Size2D {