Reach a THSarabunPSK subset's Thai glyphs when its own cmap is Latin-only

A legacy subset can keep a cmap of its own that maps only Latin, so the
editor program returned it unchanged and an edit such as a consonant with a
tone mark failed although the subset keeps both outlines. Add the PDF's
verified Thai and alternate mappings to the editor-only cmap where the
program's own lacks them, keeping its existing mappings.
This commit is contained in:
bflatastic
2026-11-08 23:51:51 +07:00
parent d351aa2760
commit dff6c2cc3e
3 changed files with 36 additions and 2 deletions
+2 -1
View File
@@ -88,7 +88,8 @@ exact program preservation, Unicode/CID/GID mapping and full-face subset complet
never exported. See [existing-text editing](../../docs/editing-existing-text.md).
`pdf::embedded_sfnt_within` reconstructs a missing Unicode cmap for Identity-H TrueType
subsets from the PDF's ToUnicode and CID-to-glyph maps. Only the in-memory editor copy changes;
subsets from the PDF's ToUnicode and CID-to-glyph maps, and completes a THSarabunPSK subset's
Latin-only one with them. Only the in-memory editor copy changes;
the PDF's font program stays intact. `pdf::LegacyThaiFont` shapes verified THSarabunPSK
positional glyphs into their original PDF codes, retaining the source face and weight. Missing
contextual glyphs are refused. `pdf::recovered_thai_map` recognizes narrowly fingerprinted
+19 -1
View File
@@ -113,7 +113,11 @@ pub fn embedded_sfnt_within(doc: &Document, font: &Dict, budget: &mut DecodeBudg
if face.metrics(skrifa::instance::Size::unscaled(), skrifa::instance::LocationRef::default()).units_per_em == 0 {
continue;
}
if face.charmap().mappings().next().is_some() {
let own_cmap = face.charmap().mappings().next().is_some();
// A legacy THSarabunPSK subset may keep a Latin-only cmap beside its Thai glyphs, which
// only the PDF's verified maps reach (`composite_glyph_maps`); other programs keep theirs.
let base_font = font.name(b"BaseFont").map(String::from_utf8_lossy).unwrap_or_default();
if own_cmap && !base_font.rsplit('+').next().is_some_and(|f| f.starts_with("THSarabunPSK")) {
return Some(bytes);
}
// PDF subsets may remove the Unicode cmap entirely. Rebuild an editor-only
@@ -124,6 +128,20 @@ pub fn embedded_sfnt_within(doc: &Document, font: &Dict, budget: &mut DecodeBudg
if let Some((map, _)) = composite_glyph_maps(doc, font, &bytes, &metrics, budget) {
mappings = map;
}
if own_cmap {
let charmap = face.charmap();
if mappings.keys().all(|&u| char::from_u32(u).is_some_and(|c| charmap.map(c).is_some())) {
return Some(bytes);
}
// The program's own mappings stay; the PDF's fill only what they lack.
mappings.extend(charmap.mappings().filter(|(_, g)| g.to_u32() != 0).take(65_537).map(|(u, g)| (u, g.to_u32())));
let mut merged = bytes.clone();
let usable = append_editor_cmap(&mut merged, &mappings).is_some()
&& skrifa::FontRef::new(&merged).is_ok_and(|font| font.charmap().mappings().next().is_some());
return Some(if usable { merged } else { bytes });
}
} else if own_cmap {
return Some(bytes);
} else if let Ok(cmap) = face.cmap() {
for code in 0..=255 {
let Some(text) = metrics.unicode.text(code) else { continue };
+15
View File
@@ -583,6 +583,21 @@ mod tests {
assert_eq!(shaped.shaped.glyphs.iter().map(|g| g.id.to_u32()).collect::<Vec<_>>(), [214, 352]);
}
/// A legacy subset that kept a Latin-only cmap of its own still reaches its Thai glyphs through
/// the PDF's verified maps, and keeps its own mappings.
#[test]
fn a_latin_only_cmap_does_not_hide_a_legacy_subsets_thai_glyphs() {
use skrifa::MetadataProvider as _;
let program = without_tables(&thai_ordered_program(), &[b"GSUB", b"GPOS"]);
let latin = skrifa::FontRef::new(&program).unwrap().charmap().map('A').unwrap();
let mut doc = Document::new_empty();
let font = thai_font(&mut doc, &program, INTACT);
let shaped = LegacyThaiFont::from_pdf(&doc, &font, 0.25).unwrap().expect("a legacy subset").shape("ก่").unwrap();
assert_eq!(shaped.shaped.glyphs.iter().map(|g| g.id.to_u32()).collect::<Vec<_>>(), [214, 352]);
let editor = crate::pdf::embedded_sfnt(&doc, &font).unwrap();
assert_eq!(skrifa::FontRef::new(&editor).unwrap().charmap().map('A'), Some(latin), "the program's own mappings stay");
}
/// A THSarabunPSK program with its own Thai mark positioning is shaped by that layout; one
/// without it only if every Thai glyph it keeps is where the legacy order has that character
/// (the legacy shaper skips the positioning check). A subset needs no particular glyph.