mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
M7: a composite font's /Encoding is read through a reference
The Legal forms review of 8fb534ad4. A Type 0 font's /Encoding may be
given by reference (a name or a CMap stream, either indirect), and the
metrics read resolve it: an indirect /Identity-H reads two-byte codes.
The editor's check before reading a composite font's metrics, and
`main`'s `composite_glyph_maps` and `recovered_thai_map`, read it with
`Dict::name`, which doesn't resolve: such a font counted as another
encoding, so the editor adapted no copy of it and Repair Thai maps
passed it over. All three now read it through `identity_h`, which
resolves it as the metrics do.
Tests: `a_composite_fonts_encoding_given_by_reference_is_read` (the
editor adapts a copy of a composite subset with `/Encoding 5 0 R`, object
5 `/Identity-H`, that maps 'Ω', and its metrics read two-byte codes), and
`recovered_thai_map_checks_every_kept_glyph_against_the_font_program`
recovers the same map with the font's /Encoding by reference.
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
8fb534ad4c
commit
e9142b03b9
3 files changed
+28
-4
No files matched your search
@@ -84,6 +84,12 @@ fn dict(doc: &Document, o: Option<&Object>) -> Option<Dict> {
|
||||
o.and_then(|o| doc.resolve(o).as_dict().cloned())
|
||||
}
|
||||
|
||||
/// Whether `font`'s `/Encoding` is the name Identity-H, given directly or through a reference,
|
||||
/// as [`Metrics::from_dict`] reads it (a Type 0 font's `/Encoding` may be indirect).
|
||||
fn identity_h(doc: &Document, font: &Dict) -> bool {
|
||||
font.get(b"Encoding").is_some_and(|e| doc.resolve(e).as_name() == Some(b"Identity-H".as_slice()))
|
||||
}
|
||||
|
||||
/// Whether `base_font` (subset tag allowed) names a legacy Indic font family that is laid out in
|
||||
/// visual order (Kruti Dev, DevLys, Chanakya, Shusha, Shivaji, Walkman, Akruti, APS-DV, DV-TT…).
|
||||
fn legacy_indic_font(base_font: &str) -> bool {
|
||||
@@ -138,7 +144,7 @@ pub fn embedded_sfnt_within(doc: &Document, font: &Dict, budget: &mut DecodeBudg
|
||||
// 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 font.name(b"Subtype") == Some(b"Type0") && !identity_h(doc, font) {
|
||||
if own_cmap {
|
||||
return Some(bytes);
|
||||
}
|
||||
|
||||
@@ -2015,6 +2015,20 @@ fn a_composite_font_that_cant_be_adapted_spends_only_its_program() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A composite font's `/Encoding` given through a reference is the name it refers to, as the
|
||||
/// metrics read it: Identity-H by reference is adapted as Identity-H written directly is.
|
||||
#[test]
|
||||
fn a_composite_fonts_encoding_given_by_reference_is_read() {
|
||||
use skrifa::MetadataProvider as _;
|
||||
let mut doc = Document::new_empty();
|
||||
let (mut source, _, _, expected, _) = cmapless_composite(&mut doc);
|
||||
let identity = doc.add(Object::name("Identity-H"));
|
||||
source.set(b"Encoding".to_vec(), Object::Ref(identity));
|
||||
assert_eq!(Metrics::from_dict(&doc, &source).code_len, 2);
|
||||
let editor = embedded_sfnt(&doc, &source).expect("Identity-H by reference is adapted");
|
||||
assert_eq!(skrifa::FontRef::new(&editor).unwrap().charmap().map('Ω'), Some(expected));
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! does (a mark takes none); correct maps and unrelated fonts are never changed. No font
|
||||
//! artwork is bundled.
|
||||
|
||||
use super::{Dict, Document, Metrics, Reads, dict, only_char, unicode_map};
|
||||
use super::{Dict, Document, Metrics, Reads, dict, identity_h, only_char, unicode_map};
|
||||
use pdfcraft_cos::{DecodeBudget, Object};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn recovered_thai_map(doc: &Document, font: &Dict) -> Option<Vec<(u16, char)
|
||||
let family = base.rsplit('+').next()?;
|
||||
if !matches!(family, "THSarabunPSK" | "THSarabunPSK-Bold" | "THSarabunPSK-Italic" | "THSarabunPSK-BoldItalic")
|
||||
|| font.name(b"Subtype") != Some(b"Type0")
|
||||
|| font.name(b"Encoding") != Some(b"Identity-H")
|
||||
|| !identity_h(doc, font)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ pub(super) fn composite_glyph_maps(
|
||||
budget: &mut DecodeBudget,
|
||||
) -> Option<(BTreeMap<u32, u32>, BTreeMap<u32, u16>)> {
|
||||
use skrifa::{MetadataProvider as _, raw::TableProvider as _};
|
||||
if font.name(b"Encoding") != Some(b"Identity-H") {
|
||||
if !identity_h(doc, font) {
|
||||
return None;
|
||||
}
|
||||
let descendant = dict(doc, font.get(b"DescendantFonts").and_then(|a| doc.resolve(a).as_array().and_then(|a| a.first()).cloned()).as_ref())?;
|
||||
@@ -548,6 +548,10 @@ mod tests {
|
||||
let recovered: HashMap<_, _> = recovered_thai_map(&doc, &font).unwrap().into_iter().collect();
|
||||
assert_eq!(recovered[&262], 'ั');
|
||||
assert_eq!(recovered[&297], '๘');
|
||||
// `/Encoding` given through a reference is the name it refers to.
|
||||
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));
|
||||
// 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();
|
||||
|
||||
Reference in new issue
Block a user