M7: Repair Thai maps reads a /CIDToGIDMap given by reference, and takes a missing one as Identity

The Consumers review of ba28458ad..b7224cbc9:

- `recovered_thai_map` read the descendant's /CIDToGIDMap with
  `Dict::name`, on the line after e9142b03b's /Encoding check, so a map
  given by reference made Repair Thai maps pass the font over, and so
  did a missing one, which is Identity (ISO 32000-2 Table 115), as
  `composite_glyph_maps`, `LegacyThaiFont` and the renderer read it. It
  now resolves the map and takes a missing one as Identity.
- `MAX_THAI_PROGRAMS` says what it pays for: the programs, and the CMaps
  and glyph maps read to adapt their cmaps. The metrics read to find and
  measure the subsets aren't charged to it. Its refusal ("MiB of font
  data") still says what it counts.

Test: `recovered_thai_map_checks_every_kept_glyph_against_the_font_program`
recovers the same map with the font's /CIDToGIDMap given by reference and
with none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
Grzegorz MartensandClaude Opus 5.5 committed 2026-11-09 16:48:01 +01:00
1 parent b7224cbc9a
commit 92e8e6eff0
2 files changed
+24 -3

No files matched your search

+3 -2
View File
@@ -117,8 +117,9 @@ struct LegacyFace {
}
/// 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.
/// 16 MiB), which it keeps, and the CMaps and glyph maps read to adapt their programs' cmaps. A
/// page may name many of them. The metrics read to find and measure the subsets
/// (`Metrics::from_dict`) aren't charged to it.
const MAX_THAI_PROGRAMS: usize = 64 << 20;
struct PreparedCharacters {
+21 -1
View File
@@ -24,7 +24,9 @@ pub fn recovered_thai_map(doc: &Document, font: &Dict) -> Option<Vec<(u16, char)
return None;
}
let desc = dict(doc, font.get(b"DescendantFonts").and_then(|a| doc.resolve(a).as_array().and_then(|a| a.first()).cloned()).as_ref())?;
if desc.name(b"CIDToGIDMap") != Some(b"Identity") {
// A map given by reference is the name it refers to, and a missing one is Identity, the
// default (ISO 32000-2 Table 115), as `composite_glyph_maps` reads them.
if desc.get(b"CIDToGIDMap").is_some_and(|m| doc.resolve(m).as_name() != Some(b"Identity".as_slice())) {
return None;
}
let map = unicode_map(doc, font, true, &mut Reads::new(doc, &mut DecodeBudget::new(usize::MAX, 0)));
@@ -552,6 +554,24 @@ mod tests {
let mut indirect = font.clone();
indirect.set(b"Encoding".to_vec(), Object::Ref(doc.add(Object::name("Identity-H"))));
assert_eq!(recovered_thai_map(&doc, &indirect), recovered_thai_map(&doc, &font));
// `/CIDToGIDMap` given through a reference is the name it refers to, and a missing one is
// Identity, the default (ISO 32000-2 Table 115).
let with_map = |map: Option<Object>| {
let mut f = font.clone();
let Some(Object::Array(desc)) = f.get(b"DescendantFonts").cloned() else { panic!("fixture") };
let Some(Object::Dict(mut desc)) = desc.first().cloned() else { panic!("fixture") };
match map {
Some(map) => desc.set(b"CIDToGIDMap".to_vec(), map),
None => {
desc.remove(b"CIDToGIDMap");
}
}
f.set(b"DescendantFonts".to_vec(), Object::Array(vec![Object::Dict(desc)]));
f
};
let by_reference = with_map(Some(Object::Ref(doc.add(Object::name("Identity")))));
assert_eq!(recovered_thai_map(&doc, &by_reference), recovered_thai_map(&doc, &font), "a map by reference");
assert_eq!(recovered_thai_map(&doc, &with_map(None)), recovered_thai_map(&doc, &font), "a missing map");
// 259–261 are ฮ, ฯ and ะ, between the last anchor (อ, 258) and the first mark: a font that
// put Mai Han-akat one glyph earlier passes every anchor and only this check sees it.
let mut shifted = thai_ordered_program();