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 byteorder::{BE, ReadBytesExt, WriteBytesExt};
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;
@ -30,37 +31,72 @@ pub struct Font {
/// Font metrics relevant to the typesetting engine.
#[derive(Debug, Clone, PartialEq)]
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.
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 {
/// Create a new font from a font program.
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 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 name = reader.read_table::<Name>()?;
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 post = reader.read_table::<Post>()?;
let unit_ratio = 1.0 / (head.units_per_em as f32);
let convert = |x| Size::from_points(unit_ratio * x as f32);
// Create conversion function between font units and sizes
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);
let font_name = base_font.unwrap_or_else(|| "unknown".to_owned());
let widths = hmtx.metrics.iter().map(|m| convert(m.advance_width)).collect();
// Find out the name of the font
let font_name = name.get_decoded(NameEntry::PostScriptName)
.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 {
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 {
name: font_name,
program,
mapping: charmap.mapping,
mapping: cmap.mapping,
widths,
default_glyph: os2.us_default_char.unwrap_or(0),
metrics,

View File

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