mirror of
https://github.com/typst/typst
synced 2025-06-28 08:12:53 +08:00
Font embedding fixes
- Subset prefix based on hash instead of hardcoded - Fix base font name of Type0 font - Embed CFF directly as standalone font Co-Authored-By: Martin Haug <mhaug@live.de>
This commit is contained in:
parent
8c9c959f4f
commit
c0b332a4c0
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -1749,9 +1749,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pdf-writer"
|
name = "pdf-writer"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86af2eb3faa4614bc7fda8bd578c25e76a17ff3b1577be034b81e0c20527e204"
|
checksum = "9d77bc47c8968aa63f86a7e6693e270a6cbd1e3b784c364f1711a0ddecc71447"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
@ -32,7 +32,7 @@ log = "0.4"
|
|||||||
miniz_oxide = "0.7"
|
miniz_oxide = "0.7"
|
||||||
oklab = "1"
|
oklab = "1"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
pdf-writer = "0.8"
|
pdf-writer = "0.8.1"
|
||||||
pixglyph = "0.1"
|
pixglyph = "0.1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
resvg = { version = "0.32", default-features = false, features = ["raster-images"] }
|
resvg = { version = "0.32", default-features = false, features = ["raster-images"] }
|
||||||
|
@ -11,6 +11,8 @@ use crate::eval::Bytes;
|
|||||||
use crate::font::Font;
|
use crate::font::Font;
|
||||||
use crate::util::SliceExt;
|
use crate::util::SliceExt;
|
||||||
|
|
||||||
|
const CFF: Tag = Tag::from_bytes(b"CFF ");
|
||||||
|
const CFF2: Tag = Tag::from_bytes(b"CFF2");
|
||||||
const CMAP_NAME: Name = Name(b"Custom");
|
const CMAP_NAME: Name = Name(b"Custom");
|
||||||
const SYSTEM_INFO: SystemInfo = SystemInfo {
|
const SYSTEM_INFO: SystemInfo = SystemInfo {
|
||||||
registry: Str(b"Adobe"),
|
registry: Str(b"Adobe"),
|
||||||
@ -33,48 +35,57 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
let metrics = font.metrics();
|
let metrics = font.metrics();
|
||||||
let ttf = font.ttf();
|
let ttf = font.ttf();
|
||||||
|
|
||||||
|
// Do we have a TrueType or CFF font?
|
||||||
|
//
|
||||||
|
// FIXME 1: CFF2 must be handled differently and requires PDF 2.0
|
||||||
|
// (or we have to convert it to CFF).
|
||||||
|
//
|
||||||
|
// FIXME 2: CFF fonts that have a Top DICT that uses CIDFont operators
|
||||||
|
// may not have an identity CID-GID encoding. These are currently not
|
||||||
|
// handled correctly. See also:
|
||||||
|
// - PDF Spec, Section 9.7.4.2
|
||||||
|
// - https://stackoverflow.com/questions/74165171/embedded-opentype-cff-font-in-a-pdf-shows-strange-behaviour-in-some-viewers
|
||||||
|
let is_cff = ttf
|
||||||
|
.raw_face()
|
||||||
|
.table(CFF)
|
||||||
|
.or_else(|| ttf.raw_face().table(CFF2))
|
||||||
|
.is_some();
|
||||||
|
|
||||||
let postscript_name = font
|
let postscript_name = font
|
||||||
.find_name(name_id::POST_SCRIPT_NAME)
|
.find_name(name_id::POST_SCRIPT_NAME)
|
||||||
.unwrap_or_else(|| "unknown".to_string());
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
|
||||||
let base_font = eco_format!("ABCDEF+{}", postscript_name);
|
let subset_tag = subset_tag(glyph_set);
|
||||||
let base_font = Name(base_font.as_bytes());
|
let base_font = eco_format!("{subset_tag}+{postscript_name}");
|
||||||
|
let base_font_type0 = if is_cff {
|
||||||
|
eco_format!("{base_font}-Identity-H")
|
||||||
|
} else {
|
||||||
|
base_font.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// Write the base font object referencing the CID font.
|
// Write the base font object referencing the CID font.
|
||||||
ctx.writer
|
ctx.writer
|
||||||
.type0_font(type0_ref)
|
.type0_font(type0_ref)
|
||||||
.base_font(base_font)
|
.base_font(Name(base_font_type0.as_bytes()))
|
||||||
.encoding_predefined(Name(b"Identity-H"))
|
.encoding_predefined(Name(b"Identity-H"))
|
||||||
.descendant_font(cid_ref)
|
.descendant_font(cid_ref)
|
||||||
.to_unicode(cmap_ref);
|
.to_unicode(cmap_ref);
|
||||||
|
|
||||||
// Check for the presence of CFF outlines to select the correct
|
|
||||||
// CID-Font subtype.
|
|
||||||
let subtype = match ttf
|
|
||||||
.raw_face()
|
|
||||||
.table(Tag::from_bytes(b"CFF "))
|
|
||||||
.or(ttf.raw_face().table(Tag::from_bytes(b"CFF2")))
|
|
||||||
{
|
|
||||||
Some(_) => CidFontType::Type0,
|
|
||||||
None => CidFontType::Type2,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write the CID font referencing the font descriptor.
|
// Write the CID font referencing the font descriptor.
|
||||||
let mut cid = ctx.writer.cid_font(cid_ref);
|
let mut cid = ctx.writer.cid_font(cid_ref);
|
||||||
cid.subtype(subtype);
|
cid.subtype(if is_cff { CidFontType::Type0 } else { CidFontType::Type2 });
|
||||||
cid.base_font(base_font);
|
cid.base_font(Name(base_font.as_bytes()));
|
||||||
cid.system_info(SYSTEM_INFO);
|
cid.system_info(SYSTEM_INFO);
|
||||||
cid.font_descriptor(descriptor_ref);
|
cid.font_descriptor(descriptor_ref);
|
||||||
cid.default_width(0.0);
|
cid.default_width(0.0);
|
||||||
|
if !is_cff {
|
||||||
if subtype == CidFontType::Type2 {
|
|
||||||
cid.cid_to_gid_map_predefined(Name(b"Identity"));
|
cid.cid_to_gid_map_predefined(Name(b"Identity"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the widths of all glyphs.
|
// Extract the widths of all glyphs.
|
||||||
let num_glyphs = ttf.number_of_glyphs();
|
let num_glyphs = ttf.number_of_glyphs();
|
||||||
let mut widths = vec![0.0; num_glyphs as usize];
|
let mut widths = vec![0.0; num_glyphs as usize];
|
||||||
for &g in glyph_set.keys() {
|
for g in std::iter::once(0).chain(glyph_set.keys().copied()) {
|
||||||
let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0);
|
let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0);
|
||||||
widths[g as usize] = font.to_em(x).to_font_units();
|
widths[g as usize] = font.to_em(x).to_font_units();
|
||||||
}
|
}
|
||||||
@ -118,7 +129,7 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
// Write the font descriptor (contains metrics about the font).
|
// Write the font descriptor (contains metrics about the font).
|
||||||
let mut font_descriptor = ctx.writer.font_descriptor(descriptor_ref);
|
let mut font_descriptor = ctx.writer.font_descriptor(descriptor_ref);
|
||||||
font_descriptor
|
font_descriptor
|
||||||
.name(base_font)
|
.name(Name(base_font.as_bytes()))
|
||||||
.flags(flags)
|
.flags(flags)
|
||||||
.bbox(bbox)
|
.bbox(bbox)
|
||||||
.italic_angle(italic_angle)
|
.italic_angle(italic_angle)
|
||||||
@ -127,10 +138,11 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
.cap_height(cap_height)
|
.cap_height(cap_height)
|
||||||
.stem_v(stem_v);
|
.stem_v(stem_v);
|
||||||
|
|
||||||
match subtype {
|
if is_cff {
|
||||||
CidFontType::Type0 => font_descriptor.font_file3(data_ref),
|
font_descriptor.font_file3(data_ref);
|
||||||
CidFontType::Type2 => font_descriptor.font_file2(data_ref),
|
} else {
|
||||||
};
|
font_descriptor.font_file2(data_ref);
|
||||||
|
}
|
||||||
|
|
||||||
font_descriptor.finish();
|
font_descriptor.finish();
|
||||||
|
|
||||||
@ -142,10 +154,10 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
// Subset and write the font's bytes.
|
// Subset and write the font's bytes.
|
||||||
let glyphs: Vec<_> = glyph_set.keys().copied().collect();
|
let glyphs: Vec<_> = glyph_set.keys().copied().collect();
|
||||||
let data = subset_font(font, &glyphs);
|
let data = subset_font(font, &glyphs);
|
||||||
|
|
||||||
let mut stream = ctx.writer.stream(data_ref, &data);
|
let mut stream = ctx.writer.stream(data_ref, &data);
|
||||||
stream.filter(Filter::FlateDecode);
|
stream.filter(Filter::FlateDecode);
|
||||||
|
if is_cff {
|
||||||
if subtype == CidFontType::Type0 {
|
|
||||||
stream.pair(Name(b"Subtype"), Name(b"CIDFontType0C"));
|
stream.pair(Name(b"Subtype"), Name(b"CIDFontType0C"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,15 +166,38 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Subset a font to the given glyphs.
|
/// Subset a font to the given glyphs.
|
||||||
|
///
|
||||||
|
/// - For a font with TrueType outlines, this returns the whole OpenType font.
|
||||||
|
/// - For a font with CFF outlines, this returns just the CFF font program.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn subset_font(font: &Font, glyphs: &[u16]) -> Bytes {
|
fn subset_font(font: &Font, glyphs: &[u16]) -> Bytes {
|
||||||
let data = font.data();
|
let data = font.data();
|
||||||
let profile = subsetter::Profile::pdf(glyphs);
|
let profile = subsetter::Profile::pdf(glyphs);
|
||||||
let subsetted = subsetter::subset(data, font.index(), profile);
|
let subsetted = subsetter::subset(data, font.index(), profile);
|
||||||
let data = subsetted.as_deref().unwrap_or(data);
|
let mut data = subsetted.as_deref().unwrap_or(data);
|
||||||
|
|
||||||
|
// Extract the standalone CFF font program if applicable.
|
||||||
|
let raw = ttf_parser::RawFace::parse(data, 0).unwrap();
|
||||||
|
if let Some(cff) = raw.table(CFF) {
|
||||||
|
data = cff;
|
||||||
|
}
|
||||||
|
|
||||||
deflate(data).into()
|
deflate(data).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce a unique 6 letter tag for a glyph set.
|
||||||
|
fn subset_tag(glyphs: &BTreeMap<u16, EcoString>) -> EcoString {
|
||||||
|
const LEN: usize = 6;
|
||||||
|
const BASE: u128 = 26;
|
||||||
|
let mut hash = crate::util::hash128(&glyphs);
|
||||||
|
let mut letter = [b'A'; LEN];
|
||||||
|
for l in letter.iter_mut() {
|
||||||
|
*l = b'A' + (hash % BASE) as u8;
|
||||||
|
hash /= BASE;
|
||||||
|
}
|
||||||
|
std::str::from_utf8(&letter).unwrap().into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a /ToUnicode CMap.
|
/// Create a /ToUnicode CMap.
|
||||||
fn create_cmap(
|
fn create_cmap(
|
||||||
ttf: &ttf_parser::Face,
|
ttf: &ttf_parser::Face,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user