diff --git a/crates/fonts/src/pdf.rs b/crates/fonts/src/pdf.rs index 431d6832ec..a81f1b341e 100644 --- a/crates/fonts/src/pdf.rs +++ b/crates/fonts/src/pdf.rs @@ -1234,7 +1234,8 @@ pub fn glyph_unicode(name: &str) -> Option { /// glyph order, 29 below ASCII, so `Solid` read as `6ROLG` and typing set the wrong glyphs (#3962). /// Codes without a glyph, or whose glyph has no name with a Unicode value, are left out. fn symbolic_glyph_texts(doc: &Document, font: &Dict) -> Vec<(u32, String)> { - use skrifa::{GlyphId, raw::TableProvider as _, raw::tables::cmap::PlatformId}; + use skrifa::GlyphId; + use skrifa::raw::{FontData, FontRead as _, TableProvider as _, tables::cmap::PlatformId, tables::post::Post, types::Tag}; let Some(descriptor) = dict(doc, font.get(b"FontDescriptor")) else { return Vec::new() }; // Flag bit 6, Nonsymbolic (ISO 32000-2 Table 121). let flags = descriptor.get(b"Flags").and_then(|v| doc.resolve(v).as_f64()).unwrap_or(0.0) as i64; @@ -1246,7 +1247,19 @@ fn symbolic_glyph_texts(doc: &Document, font: &Dict) -> Vec<(u32, String)> { let Object::Stream(stream) = object.as_ref() else { return Vec::new() }; let Ok(bytes) = stream.decoded_within(16 << 20) else { return Vec::new() }; let Ok(face) = skrifa::FontRef::from_index(&bytes, 0) else { return Vec::new() }; - let (Ok(cmap), Ok(post)) = (face.cmap(), face.post()) else { return Vec::new() }; + let Ok(cmap) = face.cmap() else { return Vec::new() }; + // A `post` version written with its 16-bit halves swapped (`00 00 00 02` for 2.0, as ScanSoft + // PDF Create! writes it) is read as the version it means, as the renderer reads it (#2946, + // vendored hayro-interpret patch (49)); otherwise its names would be lost here alone. + let repaired = face.table_data(Tag::new(b"post")).and_then(|data| match data.as_bytes() { + [0, 0, 0, n @ 1..=3, rest @ ..] => Some([&[0, *n, 0, 0], rest].concat()), + _ => None, + }); + let post = match &repaired { + Some(bytes) => Post::read(FontData::new(bytes)), + None => face.post(), + }; + let Ok(post) = post else { return Vec::new() }; (0..=255u32) .filter_map(|code| { // The records in order, as the renderer's `map_code` reads a symbolic font: a Windows diff --git a/crates/fonts/src/pdf/tests.rs b/crates/fonts/src/pdf/tests.rs index 75b5649843..83db09047a 100644 --- a/crates/fonts/src/pdf/tests.rs +++ b/crates/fonts/src/pdf/tests.rs @@ -284,6 +284,20 @@ fn a_symbolic_truetype_font_without_an_encoding_reads_its_glyph_names() { }; let symbolic = metrics(4, None); assert_eq!(symbolic.decode(&codes), TEXT); + // The same font with its `post` version halves swapped (`00 00 00 02`) reads the same, + // as the renderer reads it (#2946). + let post = (0..count).map(|i| 12 + i * 16).find(|i| &subset[*i..*i + 4] == b"post").unwrap(); + let at = u32::from_be_bytes(subset[post + 8..post + 12].try_into().unwrap()) as usize; + assert_eq!(subset[at..at + 4], [0, 2, 0, 0], "Inter's post is version 2.0"); + let mut swapped = subset.clone(); + swapped[at..at + 4].copy_from_slice(&[0, 0, 0, 2]); + let mut doc = Document::new_empty(); + let program = doc.add(Object::Stream(Stream::flate(Dict::new(), &swapped))); + 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))]); + assert_eq!(Metrics::from_dict(&doc, &f).decode(&codes), TEXT, "a swapped post version"); assert_eq!(symbolic.decode(&[0x20, 0xC0, 0xC1, 0xC2, 0xC3]), "kéaxw"); assert_eq!(symbolic.encode("Solid").as_deref(), Some(&codes[..5]), "typing sets the same glyphs"); // A named encoding, or the Nonsymbolic flag, says what the codes are: they read through it.