mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46: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 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,
|
||||||
|
88
src/pdf.rs
88
src/pdf.rs
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user