diff --git a/crates/render/src/text.rs b/crates/render/src/text.rs index 5775e239a..5f0987a8a 100644 --- a/crates/render/src/text.rs +++ b/crates/render/src/text.rs @@ -1904,9 +1904,10 @@ mod tests { /// #3169, #3170: glyph names outside the Adobe Glyph List still read as text. Ligature /// (`f_i`) and suffixed (`hyphen.case`) names go by the glyph list specification's full - /// mapping; a name that only numbers the glyph (`c251` small caps, `C39`, `C174`) reads as - /// the code in StandardEncoding, where 39 is a right quote and 174 the fi ligature. A - /// ZapfDingbats name (`a12`) and a name with no meaning left (`foo`) still read as nothing. + /// mapping. A name that only numbers the glyph (`c251` small caps, `C39`, `C174`, `C39.sc`) + /// reads as nothing, not as a guess from the code (review on #3239: with `/C1` at 233, + /// StandardEncoding's 233 would read `Ø` for a glyph drawn as `é`), and so do a ZapfDingbats + /// name (`a12`) and a name with no meaning left (`foo`). #[test] fn glyph_names_outside_the_glyph_list_still_read_as_text() { use lopdf::{Object, dictionary}; @@ -1917,13 +1918,22 @@ mod tests { Object::Integer(30), name("f_i"), name("f_l"), Object::Integer(2), name("hyphen.case"), Object::Integer(39), name("C39"), Object::Integer(97), name("c251"), Object::Integer(174), name("C174"), Object::Integer(88), name("a12"), name("foo"), name("uni00660069"), - // #3239 review: a numbered name keeps its meaning under a suffix. - Object::Integer(61), name("C39.sc"), + Object::Integer(61), name("C39.sc"), Object::Integer(233), name("C1"), ], }); - let pdf = encoded_pages(&[(differences, &b"BT /F1 20 Tf 1 0 0 1 20 150 Tm (\x1e\x1f\x02'a\xaeXYZ=) Tj ET"[..])]); - let texts = extract_page(&pdf, 0, &InterpreterSettings::default()).unwrap().glyphs.iter().map(|g| g.text.clone()).collect::>(); - assert_eq!(texts, ["fi", "fl", "-", "\u{2019}", "a", "\u{fb01}", "fi", "="]); + // One glyph per line: the test font has none of these glyphs, so they have no width, and + // on one line a later `fi` would sit on the first and be dropped as its fake-bold copy. + let mut content = b"BT /F1 20 Tf 20 290 Td".to_vec(); + for code in *b"\x1e\x1f\x02'a\xaeXYZ=\xe9" { + content.extend_from_slice(b" ("); + content.push(code); + content.extend_from_slice(b") Tj 0 -25 Td"); + } + content.extend_from_slice(b" ET"); + let pdf = encoded_pages(&[(differences, &content[..])]); + let text = extract_page(&pdf, 0, &InterpreterSettings::default()).unwrap().plain_text(); + let lines: Vec<&str> = text.lines().map(str::trim).filter(|l| !l.is_empty()).collect(); + assert_eq!(lines, ["fi", "fl", "-", "fi"], "{text:?}"); } /// Review on #3239: the glyph list specification's `uni` and `u` forms take uppercase hex diff --git a/vendor/README.md b/vendor/README.md index 314525c57..c20e6a253 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -47,11 +47,9 @@ horizontal differencing and an 8-bit control) and Glyph names outside the Adobe Glyph List (#3169, #3170): hayro-interpret's `glyph_name_to_text` maps a name by the glyph list specification's full mapping (drop from the first `.`, split on `_`, each part through the list, `uniXXXX…` or `uXXXX`; a `uni` part with any group that isn't a -character maps to nothing), so `f_i`, `f_l` and `hyphen.case` read as text. A simple Type 1 or -CFF font whose name only numbers the glyph (`C39`, `c251`, also with a suffix such as `C39.sc`; -one or two letters then digits, ZapfDingbats' `aN` excepted) reads as the code's StandardEncoding character, -so a book set in such fonts extracts instead of returning nothing. Embedded TrueType fonts take -the full mapping only. Covered by `text::tests::glyph_names_outside_the_glyph_list_still_read_as_text`. +character maps to nothing), so `f_i`, `f_l` and `hyphen.case` read as text. A name that only +numbers the glyph (`C39`, `c251`) still reads as nothing: the code's StandardEncoding character +would be a guess, wrong wherever the font's /Encoding puts the glyph elsewhere (review on #3239). Covered by `text::tests::glyph_names_outside_the_glyph_list_still_read_as_text`. Text extraction: hayro-interpret exposes `OutlineGlyph::is_vertical` from a Type0 font’s CMap writing mode. PdfCraft follows negative glyph-space y for WMode 1 so upright vertical columns can be searched (#126). diff --git a/vendor/hayro-interpret/src/font/mod.rs b/vendor/hayro-interpret/src/font/mod.rs index 2a3d3f4dc..103ee3961 100644 --- a/vendor/hayro-interpret/src/font/mod.rs +++ b/vendor/hayro-interpret/src/font/mod.rs @@ -712,19 +712,6 @@ pub(crate) fn glyph_name_to_text(name: &str) -> Option { } } -/// PdfCraft patch: whether a glyph name only numbers the glyph, one or two letters and then -/// digits (`C39`, `c251`, `g12`), so it says nothing about the character. ZapfDingbats' own -/// names (`a1` to `a191`) are left out: they name dingbats, not the code's letter. -pub(crate) fn numbered_glyph_name(name: &str) -> bool { - // As in the glyph list's mapping, a suffix (`C39.sc`) is dropped first. - let name = name.split('.').next().unwrap_or_default(); - let letters = name.bytes().take_while(u8::is_ascii_alphabetic).count(); - (1..=2).contains(&letters) - && name.len() > letters - && name.bytes().skip(letters).all(|b| b.is_ascii_digit()) - && !(letters == 1 && name.starts_with('a')) -} - pub(crate) fn unicode_from_name(name: &str) -> Option { let convert = |input: &str| u32::from_str_radix(input, 16).ok().and_then(char::from_u32); diff --git a/vendor/hayro-interpret/src/font/type1.rs b/vendor/hayro-interpret/src/font/type1.rs index 16c023a88..4ef5f953f 100644 --- a/vendor/hayro-interpret/src/font/type1.rs +++ b/vendor/hayro-interpret/src/font/type1.rs @@ -3,7 +3,7 @@ use crate::font::standard_font::{StandardFont, StandardKind, select_standard_fon use crate::font::true_type::{Width, read_encoding, read_widths}; use crate::font::{ Encoding, FallbackFontQuery, glyph_name_to_text, glyph_name_to_unicode, normalized_glyph_name, - numbered_glyph_name, read_to_unicode, + read_to_unicode, }; use crate::{CMapResolverFn, CacheKey, FontResolverFn}; use hayro_cmap::{BfString, CMap}; @@ -128,21 +128,15 @@ impl Type1Font { } // PdfCraft patch (#3169, #3170): a name outside the glyph list still reads by the // glyph list specification's full mapping (`f_i`, `hyphen.case`). A name that only - // numbers the glyph (`C39`, `c251`) reads as the code's character in StandardEncoding, - // the encoding a Type 1 program is written against: a book set in such fonts extracted - // as nothing at all, though every page draws. + // numbers the glyph (`C39`, `c251`) says nothing about the character and reads as + // nothing: the code's StandardEncoding character would be a guess, wrong wherever the + // font's /Encoding puts the glyph elsewhere (review on #3239). let name = match &self.1 { Kind::Standard(s) => s.code_to_ps_name(code), Kind::Cff(c) => c.code_to_ps_name(code), Kind::Type1(t) => t.code_to_ps_name(code), }?; - glyph_name_to_text(name).or_else(|| { - numbered_glyph_name(name) - .then(|| Encoding::Standard.map_code(code)) - .flatten() - .and_then(glyph_name_to_unicode) - .map(BfString::Char) - }) + glyph_name_to_text(name) } }