PDF import: a font the file doesn't carry keeps the style its name says
A PDF that names a font without embedding it (here a standard Helvetica) is drawn with a stand-in, so comparing its glyphs with the installed family's styles picks whichever style happens to look most like the stand-in: with a large Helvetica family installed, Helvetica became Helvetica CE Medium (tests_import_fidelity::text_becomes_point_type failed on such a Mac). Fonts without a font program now keep the face their name resolves to; embedded fonts are still compared as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
This commit is contained in:
parent
c94d38e53c
commit
adad531adb
|
|
@ -21,7 +21,7 @@ use vectorcraft_geom::{FillRule, PathData};
|
|||
|
||||
use crate::import_color::{Colors, Native};
|
||||
use crate::import_mask::{MaskSpec, contains, is_rectangle, luminance, mask_spec, white_cover};
|
||||
use crate::import_scan::{MAX_NESTING, Ocgs, Scan, all_on, hides_forms, scan_page, tag_key};
|
||||
use crate::import_scan::{FontNames, MAX_NESTING, Ocgs, Scan, all_on, hides_forms, scan_page, tag_key};
|
||||
use crate::import_shading::{clipped, extend_clip, fold_stop_opacity, mesh_shading, shading_gradient};
|
||||
use crate::import_text::{Families, LineFacts, Look, Placement, TextLine, Upright};
|
||||
use crate::{CropTo, ImportOptions, ImportReport, PdfError, TextAs};
|
||||
|
|
@ -692,8 +692,9 @@ struct Builder<'p> {
|
|||
/// Mask keys of CMYK images whose mask turned out to hide nothing.
|
||||
opaque: HashSet<u128>,
|
||||
warnings: Vec<String>,
|
||||
/// Fonts by cache key → base font name (from [`scan_page`]).
|
||||
fonts: HashMap<u128, String>,
|
||||
/// Fonts by cache key → base font name and whether the file carries its glyphs (from
|
||||
/// [`scan_page`]).
|
||||
fonts: FontNames,
|
||||
font_names: HashMap<u128, FontInfo>,
|
||||
families: Option<Families>,
|
||||
missing_fonts: Vec<String>,
|
||||
|
|
@ -1346,7 +1347,7 @@ impl<'p> Builder<'p> {
|
|||
return n.clone();
|
||||
}
|
||||
let data = o.font_data();
|
||||
let name = self.fonts.get(&key).cloned().or_else(|| data.as_ref().and_then(|d| d.postscript_name.clone()));
|
||||
let name = self.fonts.get(&key).map(|(n, _)| n.clone()).or_else(|| data.as_ref().and_then(|d| d.postscript_name.clone()));
|
||||
let (weight, italic) = data.as_ref().map_or((None, false), |d| (d.weight, d.is_italic));
|
||||
// A subset's six-letter tag.
|
||||
let ps = name.as_deref().map(|n| match n.split_once('+') {
|
||||
|
|
@ -1429,7 +1430,12 @@ impl<'p> Builder<'p> {
|
|||
let upright = width.is_some_and(|w| w.abs() <= 1.0);
|
||||
let top = m * kurbo::Point::new(f64::from(width.filter(|w| *w > 1.0).unwrap_or(1000.0)) * 0.5, 880.0);
|
||||
let mut info = self.font_name(key, o);
|
||||
if let Some(face) = &info.face {
|
||||
// A font the file doesn't carry is drawn with a stand-in: its glyphs can't tell which
|
||||
// style of the installed family it is, so the name decides.
|
||||
let embedded = self.fonts.get(&key).is_none_or(|(_, e)| *e);
|
||||
if let Some(face) = &info.face
|
||||
&& embedded
|
||||
{
|
||||
let decided = match self.matched.get(&key) {
|
||||
Some(m) => m.clone(),
|
||||
// A glyph that can't be compared (several characters, a vertical form) decides
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ struct Walker<'w, 'p> {
|
|||
/// The groups that are off for the interpreter (none once [`all_on`] turned them on).
|
||||
off: HashSet<ObjectIdentifier>,
|
||||
xref: &'p hayro_syntax::xref::XRef,
|
||||
fonts: &'w mut HashMap<u128, String>,
|
||||
fonts: &'w mut FontNames,
|
||||
visible: Vec<bool>,
|
||||
ops: usize,
|
||||
max_ops: usize,
|
||||
|
|
@ -296,7 +296,7 @@ impl Walker<'_, '_> {
|
|||
if let Some(f) = res.fonts.get::<Dict<'_>>(key.as_ref())
|
||||
&& let Some(name) = f.get::<Name<'_>>(b"BaseFont")
|
||||
{
|
||||
self.fonts.entry(f.cache_key()).or_insert_with(|| String::from_utf8_lossy(name.as_ref()).into_owned());
|
||||
self.fonts.entry(f.cache_key()).or_insert_with(|| (String::from_utf8_lossy(name.as_ref()).into_owned(), embeds_glyphs(&f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -372,13 +372,32 @@ impl Walker<'_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The fonts of a document by cache key: base font name, and whether the file carries the glyphs
|
||||
/// ([`embeds_glyphs`]).
|
||||
pub(crate) type FontNames = HashMap<u128, (String, bool)>;
|
||||
|
||||
/// Whether font `f` carries its own glyphs: a font program in its descriptor (a composite font's in
|
||||
/// its descendant's), or a Type 3 font's glyph procedures. A font that doesn't is drawn with a
|
||||
/// stand-in, whose glyphs say nothing about which installed face the document means.
|
||||
fn embeds_glyphs(f: &Dict<'_>) -> bool {
|
||||
if f.get::<Name<'_>>(b"Subtype").is_some_and(|s| s.as_ref() == b"Type3") {
|
||||
return true;
|
||||
}
|
||||
// A font program is a stream, so always an indirect object.
|
||||
let program = |d: &Dict<'_>| {
|
||||
d.get::<Dict<'_>>(b"FontDescriptor").is_some_and(|fd| [&b"FontFile"[..], b"FontFile2", b"FontFile3"].iter().any(|k| fd.get_ref(*k).is_some()))
|
||||
};
|
||||
program(f) || f.get::<Array<'_>>(b"DescendantFonts").and_then(|a| a.iter::<Dict<'_>>().next()).is_some_and(|d| program(&d))
|
||||
}
|
||||
|
||||
/// Walk `page` as the interpreter will: `all_on` when [`all_on`] turned every group on. Fonts
|
||||
/// found are noted in `fonts` (their cache key → base font name).
|
||||
pub(crate) fn scan_page(page: &Page<'_>, ocgs: &mut Ocgs, all_on: bool, fonts: &mut HashMap<u128, String>) -> Scan {
|
||||
/// found are noted in `fonts` (their cache key → base font name, and whether the file carries its
|
||||
/// glyphs).
|
||||
pub(crate) fn scan_page(page: &Page<'_>, ocgs: &mut Ocgs, all_on: bool, fonts: &mut FontNames) -> Scan {
|
||||
walk_page(page, ocgs, all_on, fonts).out
|
||||
}
|
||||
|
||||
fn walk_page<'w, 'p>(page: &Page<'p>, ocgs: &'w mut Ocgs, all_on: bool, fonts: &'w mut HashMap<u128, String>) -> Walker<'w, 'p> {
|
||||
fn walk_page<'w, 'p>(page: &Page<'p>, ocgs: &'w mut Ocgs, all_on: bool, fonts: &'w mut FontNames) -> Walker<'w, 'p> {
|
||||
let off = if all_on { HashSet::new() } else { ocgs.off.clone() };
|
||||
let mut w = Walker { ocgs, off, xref: page.xref(), fonts, visible: vec![], ops: 0, max_ops: max_ops(), out: Scan::default(), hidden_form: false };
|
||||
w.walk(page.typed_operations(), page.resources(), 0);
|
||||
|
|
|
|||
Loading…
Reference in New Issue