mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
M7: the editor reads no metrics for a composite font it can't adapt
The Resources review of 8eead8ecd:
- `embedded_sfnt_within` read a composite font's metrics within the
editor's allowance before `composite_glyph_maps` refused any `/Encoding`
but Identity-H, so an Identity-V or UniJIS font spent its ToUnicode and
`/CIDToGIDMap` for nothing. Its `/Encoding` is now checked first; a
program with a cmap of its own is kept as it is, as when the rebuild
finds nothing.
- The Thai edit's allowance, `MAX_THAI_PROGRAMS`, now also pays for the
CMaps and glyph maps its subsets' metrics and codes are read from: its
doc and its refusal say "font data", not "font programs".
- `a_symbolic_truetype_font_without_an_encoding_reads_its_glyph_names`
also reads its font, whose `post` table names its glyphs, within an
allowance: the program is paid for by the glyph-name reading, by the
Mac OS Roman one and for the widths, and a byte short of the first the
names go unread.
Tests: `a_composite_font_that_cant_be_adapted_spends_only_its_program`,
and `adapting_a_programs_cmap_is_paid_for` again expects a composite font
whose codes aren't Identity-H to pay for its program only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
1 parent
063f8629e4
commit
509cf9f9fd
3 files changed
+47
-6
No files matched your search
@@ -116,8 +116,9 @@ struct LegacyFace {
|
||||
space: f64,
|
||||
}
|
||||
|
||||
/// What one edit may decode, and so keep, of the font programs of its THSarabunPSK subsets
|
||||
/// (each at most 16 MiB): a page may name many of them.
|
||||
/// What one edit may decode of its THSarabunPSK subsets' font data: their programs (each at most
|
||||
/// 16 MiB), which it keeps, and the CMaps and glyph maps their metrics and codes are read from. A
|
||||
/// page may name many of them.
|
||||
const MAX_THAI_PROGRAMS: usize = 64 << 20;
|
||||
|
||||
struct PreparedCharacters {
|
||||
@@ -926,7 +927,7 @@ fn legacy_thai_font(
|
||||
// A subset left unread would count as unusable, silently changing which subset draws the text.
|
||||
if budget.remaining() == 0 {
|
||||
return Err(EditError::Invalid(format!(
|
||||
"the page's {} subsets need more than {} MiB of font programs for one edit; choose a replacement font for this text",
|
||||
"the page's {} subsets need more than {} MiB of font data for one edit; choose a replacement font for this text",
|
||||
source.base_font.rsplit('+').next().unwrap_or(&source.base_font),
|
||||
MAX_THAI_PROGRAMS >> 20
|
||||
)));
|
||||
|
||||
@@ -135,6 +135,15 @@ pub fn embedded_sfnt_within(doc: &Document, font: &Dict, budget: &mut DecodeBudg
|
||||
if own_cmap && !base_font.rsplit('+').next().is_some_and(|f| f.starts_with("THSarabunPSK")) {
|
||||
return Some(bytes);
|
||||
}
|
||||
// A composite font's cmap is rebuilt only through Identity-H codes (`composite_glyph_maps`):
|
||||
// with any other `/Encoding` nothing is, so its metrics aren't read, and a program with a
|
||||
// cmap of its own is kept as it is, as when the rebuild finds nothing.
|
||||
if font.name(b"Subtype") == Some(b"Type0") && font.name(b"Encoding") != Some(b"Identity-H") {
|
||||
if own_cmap {
|
||||
return Some(bytes);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// PDF subsets may remove the Unicode cmap entirely. Rebuild an editor-only
|
||||
// cmap from ToUnicode and the actual CID-to-glyph map; source bytes stay unchanged.
|
||||
// Metrics read past the allowance, its widths' read of the program again included,
|
||||
|
||||
@@ -243,13 +243,13 @@ fn adapting_a_programs_cmap_is_paid_for() {
|
||||
assert_eq!(skrifa::FontRef::new(&editor).unwrap().charmap().map('Ω'), Some(expected));
|
||||
assert_eq!(budget.remaining(), 0);
|
||||
assert!(embedded_sfnt_within(&doc, &source, &mut DecodeBudget::new(cost - 1, 0)).is_none());
|
||||
// A composite font's metrics are read too, and paid for: the program and its ToUnicode, which
|
||||
// give no glyph here (its codes aren't Identity-H).
|
||||
// A composite font whose codes aren't Identity-H can't be adapted, so its metrics aren't
|
||||
// read: the program is all it pays for.
|
||||
let mut budget = DecodeBudget::new(cost, 0);
|
||||
let mut composite = source.clone();
|
||||
composite.set(b"Subtype".to_vec(), Object::name("Type0"));
|
||||
assert!(embedded_sfnt_within(&doc, &composite, &mut budget).is_none());
|
||||
assert_eq!(budget.remaining(), cost - subset.len() - cmap.len());
|
||||
assert_eq!(budget.remaining(), cost - subset.len());
|
||||
}
|
||||
|
||||
/// A Type 0 subset font whose `/Encoding`, program, `/CIDToGIDMap` and ToUnicode are streams, and
|
||||
@@ -721,6 +721,21 @@ fn a_symbolic_truetype_font_without_an_encoding_reads_its_glyph_names() {
|
||||
],
|
||||
);
|
||||
assert_eq!(Metrics::from_dict(&doc, &f).decode(&codes), "");
|
||||
// The names are read within the allowance the metrics are read within: the program is paid
|
||||
// for by the glyph-name reading, by the Mac OS Roman one (which finds the glyphs named and
|
||||
// reads nothing) and for the widths. A byte short of the first, the names go unread.
|
||||
let mut doc = Document::new_empty();
|
||||
let program = doc.add(Object::Stream(Stream::flate(Dict::new(), &subset)));
|
||||
let mut descriptor = Dict::new();
|
||||
descriptor.set(b"Flags".to_vec(), Object::Int(4));
|
||||
descriptor.set(b"FontFile2".to_vec(), Object::Ref(program));
|
||||
let f = font(&mut doc, vec![("Subtype", Object::name("TrueType")), ("FontDescriptor", Object::Dict(descriptor))]);
|
||||
let mut budget = DecodeBudget::new(3 * subset.len(), 0);
|
||||
assert_eq!(Metrics::from_dict_within(&doc, &f, &mut budget), symbolic);
|
||||
assert_eq!(budget.remaining(), 0);
|
||||
let short = Metrics::from_dict_within(&doc, &f, &mut DecodeBudget::new(subset.len() - 1, 0));
|
||||
assert!(short.past_allowance);
|
||||
assert_ne!(short.decode(&codes), TEXT, "the names unread");
|
||||
}
|
||||
|
||||
/// The glyph list specification's `uni` and `u` forms spell their hex digits in uppercase
|
||||
@@ -1984,6 +1999,22 @@ fn adapting_a_composite_subsets_cmap_is_paid_for() {
|
||||
assert!(embedded_sfnt_within(&doc, &source, &mut DecodeBudget::new(subset.len() + cmap - 1, 0)).is_none());
|
||||
}
|
||||
|
||||
/// A composite font whose `/Encoding` isn't Identity-H can't have its cmap rebuilt
|
||||
/// (`composite_glyph_maps` reads only Identity-H codes), so the editor reads its program and no
|
||||
/// more: its ToUnicode and `/CIDToGIDMap` are left for what is left of the allowance.
|
||||
#[test]
|
||||
fn a_composite_font_that_cant_be_adapted_spends_only_its_program() {
|
||||
let mut doc = Document::new_empty();
|
||||
let (source, _, subset, _, [cmap, map]) = cmapless_composite(&mut doc);
|
||||
for encoding in ["Identity-V", "UniJIS-UCS2-H"] {
|
||||
let mut f = source.clone();
|
||||
f.set(b"Encoding".to_vec(), Object::name(encoding));
|
||||
let mut budget = DecodeBudget::new(subset.len() + cmap + map, 0);
|
||||
assert!(embedded_sfnt_within(&doc, &f, &mut budget).is_none(), "{encoding}");
|
||||
assert_eq!(budget.remaining(), cmap + map, "{encoding}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The `/CIDToGIDMap` a composite subset's cmap is rebuilt from reads as the font's other streams
|
||||
/// do: a full map, which compresses well, reads behind ASCIIHex and Flate, though the hex stage's
|
||||
/// output comes on top of the map.
|
||||
|
||||
Reference in new issue
Block a user