Refactor pdf font handling ♻

This commit is contained in:
Laurenz 2019-03-14 00:06:27 +01:00
parent 89205368c2
commit 67b1945034
2 changed files with 78 additions and 64 deletions

View File

@ -6,7 +6,8 @@ use std::fmt;
use std::io::{self, Cursor, Seek, SeekFrom}; use std::io::{self, Cursor, Seek, SeekFrom};
use byteorder::{BE, ReadBytesExt, WriteBytesExt}; use byteorder::{BE, ReadBytesExt, WriteBytesExt};
use opentype::{OpenTypeReader, Outlines, TableRecord, Tag}; use opentype::{OpenTypeReader, Outlines, TableRecord, Tag};
use opentype::tables::{Header, Name, NameEntry, CharMap, MaximumProfile, HorizontalMetrics, OS2}; use opentype::tables::{Header, MacStyleFlags, Name, NameEntry, CharMap,
MaximumProfile, HorizontalMetrics, Post, OS2};
use crate::doc::Size; use crate::doc::Size;
@ -30,37 +31,72 @@ pub struct Font {
/// Font metrics relevant to the typesetting engine. /// Font metrics relevant to the typesetting engine.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FontMetrics { pub struct FontMetrics {
/// Whether the font is italic.
pub is_italic: bool,
/// Whether font is fixed pitch.
pub is_fixed_pitch: bool,
/// The angle of italics.
pub italic_angle: f32,
/// The glyph bounding box: [x_min, y_min, x_max, y_max],
pub bounding_box: [Size; 4],
/// The typographics ascender relevant for line spacing. /// The typographics ascender relevant for line spacing.
pub ascender: Size, pub ascender: Size,
/// The typographics descender relevant for line spacing.
pub descender: Size,
/// The approximate height of capital letters.
pub cap_height: Size,
/// The weight class of the font.
pub weight_class: u16,
} }
impl Font { impl Font {
/// Create a new font from a font program. /// Create a new font from a font program.
pub fn new(program: Vec<u8>) -> Result<Font, opentype::Error> { pub fn new(program: Vec<u8>) -> Result<Font, opentype::Error> {
// Create opentype reader to parse font tables
let mut readable = Cursor::new(&program); let mut readable = Cursor::new(&program);
let mut reader = OpenTypeReader::new(&mut readable); let mut reader = OpenTypeReader::new(&mut readable);
// Read the relevant tables
// (all of these are required by the OpenType specification)
let head = reader.read_table::<Header>()?; let head = reader.read_table::<Header>()?;
let name = reader.read_table::<Name>()?; let name = reader.read_table::<Name>()?;
let os2 = reader.read_table::<OS2>()?; let os2 = reader.read_table::<OS2>()?;
let charmap = reader.read_table::<CharMap>()?; let cmap = reader.read_table::<CharMap>()?;
let hmtx = reader.read_table::<HorizontalMetrics>()?; let hmtx = reader.read_table::<HorizontalMetrics>()?;
let post = reader.read_table::<Post>()?;
let unit_ratio = 1.0 / (head.units_per_em as f32); // Create conversion function between font units and sizes
let convert = |x| Size::from_points(unit_ratio * x as f32); let font_unit_ratio = 1.0 / (head.units_per_em as f32);
let font_unit_to_size = |x| Size::from_points(font_unit_ratio * x as f32);
let base_font = name.get_decoded(NameEntry::PostScriptName); // Find out the name of the font
let font_name = base_font.unwrap_or_else(|| "unknown".to_owned()); let font_name = name.get_decoded(NameEntry::PostScriptName)
let widths = hmtx.metrics.iter().map(|m| convert(m.advance_width)).collect(); .unwrap_or_else(|| "unknown".to_owned());
// Convert the widths
let widths = hmtx.metrics.iter().map(|m| font_unit_to_size(m.advance_width)).collect();
// Calculate some metrics
let metrics = FontMetrics { let metrics = FontMetrics {
ascender: convert(os2.s_typo_ascender), is_italic: head.mac_style.contains(MacStyleFlags::ITALIC),
is_fixed_pitch: post.is_fixed_pitch,
italic_angle: post.italic_angle.to_f32(),
bounding_box: [
font_unit_to_size(head.x_min),
font_unit_to_size(head.y_min),
font_unit_to_size(head.x_max),
font_unit_to_size(head.y_max),
],
ascender: font_unit_to_size(os2.s_typo_ascender),
descender: font_unit_to_size(os2.s_typo_descender),
cap_height: font_unit_to_size(os2.s_cap_height.unwrap_or(os2.s_typo_ascender)),
weight_class: os2.us_weight_class,
}; };
Ok(Font { Ok(Font {
name: font_name, name: font_name,
program, program,
mapping: charmap.mapping, mapping: cmap.mapping,
widths, widths,
default_glyph: os2.us_default_char.unwrap_or(0), default_glyph: os2.us_default_char.unwrap_or(0),
metrics, metrics,

View File

@ -3,13 +3,12 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::io::{self, Write, Cursor}; use std::io::{self, Write};
use pdf::{PdfWriter, Reference, Rect, Version, Trailer}; use pdf::{PdfWriter, Reference, Rect, Version, Trailer};
use pdf::{DocumentCatalog, PageTree, Page, Resource, Text, Content}; use pdf::{DocumentCatalog, PageTree, Page, Resource, Text, Content};
use pdf::font::{Type0Font, CMapEncoding, CIDFont, CIDFontType, CIDSystemInfo, WidthRecord, use pdf::font::{Type0Font, CMapEncoding, CIDFont, CIDFontType, CIDSystemInfo,
FontDescriptor, FontFlags, EmbeddedFont, GlyphUnit}; WidthRecord, FontDescriptor, FontFlags, EmbeddedFont, GlyphUnit};
use opentype::{OpenTypeReader, tables::{self, MacStyleFlags}}; use crate::doc::{Document, Size, Text as DocText, TextCommand as DocTextCommand};
use crate::doc::{self, Document, TextCommand};
use crate::font::Font; use crate::font::Font;
@ -49,8 +48,6 @@ impl<'a, W: Write> PdfCreator<'a, W> {
fonts, fonts,
}; };
assert!(doc.fonts.len() > 0);
// Find out which chars are used in this document. // Find out which chars are used in this document.
let mut char_sets = vec![HashSet::new(); doc.fonts.len()]; let mut char_sets = vec![HashSet::new(); doc.fonts.len()];
let mut current_font: usize = 0; let mut current_font: usize = 0;
@ -58,13 +55,9 @@ impl<'a, W: Write> PdfCreator<'a, W> {
for text in &page.text { for text in &page.text {
for command in &text.commands { for command in &text.commands {
match command { match command {
TextCommand::Text(string) => { DocTextCommand::Text(string)
char_sets[current_font].extend(string.chars()); => char_sets[current_font].extend(string.chars()),
}, DocTextCommand::SetFont(id, _) => current_font = *id,
TextCommand::SetFont(id, _) => {
assert!(*id < doc.fonts.len());
current_font = *id;
},
_ => {}, _ => {},
} }
} }
@ -148,11 +141,11 @@ impl<'a, W: Write> PdfCreator<'a, W> {
Ok(()) Ok(())
} }
fn write_text(&mut self, id: u32, text: &doc::Text) -> PdfResult<()> { fn write_text(&mut self, id: u32, text: &DocText) -> PdfResult<()> {
let mut current_font = 0; let mut current_font = 0;
let encoded = text.commands.iter().filter_map(|cmd| match cmd { let encoded = text.commands.iter().filter_map(|cmd| match cmd {
TextCommand::Text(string) => Some(self.fonts[current_font].encode(&string)), DocTextCommand::Text(string) => Some(self.fonts[current_font].encode(&string)),
TextCommand::SetFont(id, _) => { current_font = *id; None }, DocTextCommand::SetFont(id, _) => { current_font = *id; None },
_ => None, _ => None,
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
@ -161,16 +154,12 @@ impl<'a, W: Write> PdfCreator<'a, W> {
for command in &text.commands { for command in &text.commands {
match command { match command {
TextCommand::Text(_) => { DocTextCommand::Text(_) => {
object.write_text(&encoded[nr]); object.write_text(&encoded[nr]);
nr += 1; nr += 1;
}, },
TextCommand::SetFont(id, size) => { DocTextCommand::SetFont(id, size) => { object.set_font(*id as u32 + 1, *size); },
object.set_font(*id as u32 + 1, *size); DocTextCommand::Move(x, y) => { object.move_line(x.to_points(), y.to_points()); },
},
TextCommand::Move(x, y) => {
object.move_line(x.to_points(), y.to_points());
}
} }
} }
@ -239,52 +228,48 @@ impl PdfFont {
/// Create a subetted version of the font and calculate some information /// Create a subetted version of the font and calculate some information
/// needed for creating the _PDF_. /// needed for creating the _PDF_.
pub fn new(font: &Font, chars: &HashSet<char>) -> PdfResult<PdfFont> { pub fn new(font: &Font, chars: &HashSet<char>) -> PdfResult<PdfFont> {
let mut readable = Cursor::new(&font.program); // Subset the font using the selected characters
let mut reader = OpenTypeReader::new(&mut readable);
let head = reader.read_table::<tables::Header>()?;
let post = reader.read_table::<tables::Post>()?;
let os2 = reader.read_table::<tables::OS2>()?;
let subsetted = font.subsetted( let subsetted = font.subsetted(
chars.iter().cloned(), chars.iter().cloned(),
&["head", "hhea", "maxp", "hmtx", "loca", "glyf"], &["head", "hhea", "maxp", "hmtx", "loca", "glyf"],
&["cvt ", "prep", "fpgm", /* "OS/2", "cmap", "name", "post" */], &["cvt ", "prep", "fpgm", /* "OS/2", "cmap", "name", "post" */],
)?; )?;
// Specify flags for the font
let mut flags = FontFlags::empty(); let mut flags = FontFlags::empty();
flags.set(FontFlags::FIXED_PITCH, post.is_fixed_pitch); flags.set(FontFlags::FIXED_PITCH, font.metrics.is_fixed_pitch);
flags.set(FontFlags::SERIF, font.name.contains("Serif")); flags.set(FontFlags::SERIF, font.name.contains("Serif"));
flags.insert(FontFlags::SYMBOLIC); flags.insert(FontFlags::SYMBOLIC);
flags.set(FontFlags::ITALIC, head.mac_style.contains(MacStyleFlags::ITALIC)); flags.set(FontFlags::ITALIC, font.metrics.is_italic);
flags.insert(FontFlags::SMALL_CAP); flags.insert(FontFlags::SMALL_CAP);
let widths = subsetted.widths.iter() // Transform the widths
.map(|w| (1000.0 * w.to_points()).round() as GlyphUnit) let widths = subsetted.widths.iter().map(|&x| size_to_glyph_unit(x)).collect();
.collect();
let unit_ratio = 1.0 / (head.units_per_em as f32);
let convert = |x| (unit_ratio * x as f32).round() as GlyphUnit;
Ok(PdfFont { Ok(PdfFont {
font: subsetted, font: subsetted,
widths, widths,
flags, flags,
italic_angle: post.italic_angle.to_f32(), italic_angle: font.metrics.italic_angle,
bounding_box: Rect::new( bounding_box: Rect::new(
convert(head.x_min), size_to_glyph_unit(font.metrics.bounding_box[0]),
convert(head.y_min), size_to_glyph_unit(font.metrics.bounding_box[1]),
convert(head.x_max), size_to_glyph_unit(font.metrics.bounding_box[2]),
convert(head.y_max) size_to_glyph_unit(font.metrics.bounding_box[3]),
), ),
ascender: convert(os2.s_typo_ascender), ascender: size_to_glyph_unit(font.metrics.ascender),
descender: convert(os2.s_typo_descender), descender: size_to_glyph_unit(font.metrics.descender),
cap_height: convert(os2.s_cap_height.unwrap_or(os2.s_typo_ascender)), cap_height: size_to_glyph_unit(font.metrics.cap_height),
stem_v: (10.0 + 220.0 * (os2.us_weight_class as f32 - 50.0) / 900.0) as GlyphUnit, stem_v: (10.0 + 0.244 * (font.metrics.weight_class as f32 - 50.0)) as GlyphUnit,
}) })
} }
} }
/// Convert a size into a _PDF_ glyph unit.
fn size_to_glyph_unit(size: Size) -> GlyphUnit {
(1000.0 * size.to_points()).round() as GlyphUnit
}
impl std::ops::Deref for PdfFont { impl std::ops::Deref for PdfFont {
type Target = Font; type Target = Font;
@ -312,13 +297,6 @@ impl From<io::Error> for PdfWritingError {
} }
} }
impl From<opentype::Error> for PdfWritingError {
#[inline]
fn from(err: opentype::Error) -> PdfWritingError {
PdfWritingError { message: format!("{}", err) }
}
}
impl From<crate::font::SubsettingError> for PdfWritingError { impl From<crate::font::SubsettingError> for PdfWritingError {
#[inline] #[inline]
fn from(err: crate::font::SubsettingError) -> PdfWritingError { fn from(err: crate::font::SubsettingError) -> PdfWritingError {