diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-06-14 21:36:33 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-06-14 21:36:33 +0200 |
| commit | 4817c62dfbaf8cd200ed3582665995fd01fac263 (patch) | |
| tree | aa5dd0143743c5b7013e5d47eb7fdbe7621318ca /src/export/pdf/font.rs | |
| parent | 7fb19d5ef8dc3b183d7de811e373768de56f7ee8 (diff) | |
Split up PDF exporter
Diffstat (limited to 'src/export/pdf/font.rs')
| -rw-r--r-- | src/export/pdf/font.rs | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/src/export/pdf/font.rs b/src/export/pdf/font.rs new file mode 100644 index 00000000..804a2cb1 --- /dev/null +++ b/src/export/pdf/font.rs @@ -0,0 +1,183 @@ +use std::collections::BTreeMap; + +use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap}; +use pdf_writer::{Filter, Finish, Name, Rect, Str}; +use ttf_parser::{name_id, GlyphId, Tag}; + +use super::{deflate, EmExt, PdfContext, RefExt}; +use crate::util::SliceExt; + +/// Embed all used fonts into the PDF. +pub fn write_fonts(ctx: &mut PdfContext) { + for face_id in ctx.face_map.layout_indices() { + let type0_ref = ctx.alloc.bump(); + let cid_ref = ctx.alloc.bump(); + let descriptor_ref = ctx.alloc.bump(); + let cmap_ref = ctx.alloc.bump(); + let data_ref = ctx.alloc.bump(); + ctx.face_refs.push(type0_ref); + + let glyphs = &ctx.glyph_sets[&face_id]; + let face = ctx.fonts.get(face_id); + let metrics = face.metrics(); + let ttf = face.ttf(); + + let postscript_name = face + .find_name(name_id::POST_SCRIPT_NAME) + .unwrap_or_else(|| "unknown".to_string()); + + let base_font = format_eco!("ABCDEF+{}", postscript_name); + let base_font = Name(base_font.as_bytes()); + let cmap_name = Name(b"Custom"); + let system_info = SystemInfo { + registry: Str(b"Adobe"), + ordering: Str(b"Identity"), + supplement: 0, + }; + + // Write the base font object referencing the CID font. + ctx.writer + .type0_font(type0_ref) + .base_font(base_font) + .encoding_predefined(Name(b"Identity-H")) + .descendant_font(cid_ref) + .to_unicode(cmap_ref); + + // Check for the presence of CFF outlines to select the correct + // CID-Font subtype. + let subtype = match ttf + .table_data(Tag::from_bytes(b"CFF ")) + .or(ttf.table_data(Tag::from_bytes(b"CFF2"))) + { + Some(_) => CidFontType::Type0, + None => CidFontType::Type2, + }; + + // Write the CID font referencing the font descriptor. + let mut cid = ctx.writer.cid_font(cid_ref); + cid.subtype(subtype); + cid.base_font(base_font); + cid.system_info(system_info); + cid.font_descriptor(descriptor_ref); + cid.default_width(0.0); + + if subtype == CidFontType::Type2 { + cid.cid_to_gid_map_predefined(Name(b"Identity")); + } + + // Extract the widths of all glyphs. + let num_glyphs = ttf.number_of_glyphs(); + let mut widths = vec![0.0; num_glyphs as usize]; + for &g in glyphs { + let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0); + widths[g as usize] = face.to_em(x).to_font_units(); + } + + // Write all non-zero glyph widths. + let mut first = 0; + let mut width_writer = cid.widths(); + for (w, group) in widths.group_by_key(|&w| w) { + let end = first + group.len(); + if w != 0.0 { + let last = end - 1; + width_writer.same(first as u16, last as u16, w); + } + first = end; + } + + width_writer.finish(); + cid.finish(); + + let mut flags = FontFlags::empty(); + flags.set(FontFlags::SERIF, postscript_name.contains("Serif")); + flags.set(FontFlags::FIXED_PITCH, ttf.is_monospaced()); + flags.set(FontFlags::ITALIC, ttf.is_italic()); + flags.insert(FontFlags::SYMBOLIC); + flags.insert(FontFlags::SMALL_CAP); + + let global_bbox = ttf.global_bounding_box(); + let bbox = Rect::new( + face.to_em(global_bbox.x_min).to_font_units(), + face.to_em(global_bbox.y_min).to_font_units(), + face.to_em(global_bbox.x_max).to_font_units(), + face.to_em(global_bbox.y_max).to_font_units(), + ); + + let italic_angle = ttf.italic_angle().unwrap_or(0.0); + let ascender = metrics.ascender.to_font_units(); + let descender = metrics.descender.to_font_units(); + let cap_height = metrics.cap_height.to_font_units(); + let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0); + + // Write the font descriptor (contains metrics about the font). + let mut font_descriptor = ctx.writer.font_descriptor(descriptor_ref); + font_descriptor + .name(base_font) + .flags(flags) + .bbox(bbox) + .italic_angle(italic_angle) + .ascent(ascender) + .descent(descender) + .cap_height(cap_height) + .stem_v(stem_v); + + match subtype { + CidFontType::Type0 => font_descriptor.font_file3(data_ref), + CidFontType::Type2 => font_descriptor.font_file2(data_ref), + }; + + font_descriptor.finish(); + + // Compute a reverse mapping from glyphs to unicode. + let cmap = { + let mut mapping = BTreeMap::new(); + for subtable in + ttf.tables().cmap.into_iter().flat_map(|table| table.subtables) + { + if subtable.is_unicode() { + subtable.codepoints(|n| { + if let Some(c) = std::char::from_u32(n) { + if let Some(GlyphId(g)) = ttf.glyph_index(c) { + if glyphs.contains(&g) { + mapping.insert(g, c); + } + } + } + }); + } + } + + let mut cmap = UnicodeCmap::new(cmap_name, system_info); + for (g, c) in mapping { + cmap.pair(g, c); + } + cmap + }; + + // Write the /ToUnicode character map, which maps glyph ids back to + // unicode codepoints to enable copying out of the PDF. + ctx.writer + .cmap(cmap_ref, &deflate(&cmap.finish())) + .filter(Filter::FlateDecode); + + // Subset and write the face's bytes. + let data = face.buffer(); + let subsetted = { + let glyphs: Vec<_> = glyphs.iter().copied().collect(); + let profile = subsetter::Profile::pdf(&glyphs); + subsetter::subset(data, face.index(), profile) + }; + + // Compress and write the face's byte. + let data = subsetted.as_deref().unwrap_or(data); + let data = deflate(data); + let mut stream = ctx.writer.stream(data_ref, &data); + stream.filter(Filter::FlateDecode); + + if subtype == CidFontType::Type0 { + stream.pair(Name(b"Subtype"), Name(b"OpenType")); + } + + stream.finish(); + } +} |
