mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Refactor pdf font handling ♻
This commit is contained in:
parent
89205368c2
commit
67b1945034
54
src/font.rs
54
src/font.rs
@ -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,
|
||||
|
88
src/pdf.rs
88
src/pdf.rs
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user