diff --git a/crates/render/src/text.rs b/crates/render/src/text.rs index 995fceb36..41071d1d5 100644 --- a/crates/render/src/text.rs +++ b/crates/render/src/text.rs @@ -1917,11 +1917,13 @@ 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"), ], }); - 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 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"]); + assert_eq!(texts, ["fi", "fl", "-", "\u{2019}", "a", "\u{fb01}", "fi", "="]); } #[test] diff --git a/vendor/README.md b/vendor/README.md index 78e54ac1b..314525c57 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -46,9 +46,10 @@ 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`), 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`; one or two -letters then digits, ZapfDingbats' `aN` excepted) reads as the code's StandardEncoding character, +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`. diff --git a/vendor/hayro-interpret/src/font/mod.rs b/vendor/hayro-interpret/src/font/mod.rs index 9a45e0399..4eeb3a065 100644 --- a/vendor/hayro-interpret/src/font/mod.rs +++ b/vendor/hayro-interpret/src/font/mod.rs @@ -673,7 +673,8 @@ pub(crate) fn glyph_name_to_unicode(name: &str) -> Option { /// Adobe Glyph List Specification's full mapping: drop everything from the first `.` /// (`hyphen.case`, `a.sc`), split the rest on `_` into components (`f_i`, `f_f_l`), and map each /// one through the glyph list, as `uniXXXX[XXXX…]` (BMP values, four hex digits each) or as -/// `uXXXX` to `uXXXXXX`. A component it can't map adds nothing; `None` when nothing is left. +/// `uXXXX` to `uXXXXXX`. A component it can't map, or a `uni` component with any group that +/// isn't a character, adds nothing; `None` when nothing is left. pub(crate) fn glyph_name_to_text(name: &str) -> Option { let hex = |h: &str| u32::from_str_radix(h, 16).ok().and_then(char::from_u32); let base = name.split('.').next().unwrap_or_default(); @@ -685,10 +686,13 @@ pub(crate) fn glyph_name_to_text(name: &str) -> Option { .strip_prefix("uni") .filter(|d| !d.is_empty() && d.len() % 4 == 0 && d.bytes().all(|b| b.is_ascii_hexdigit())) { - // All ASCII, so every fourth byte is a char boundary. - for at in (0..digits.len()).step_by(4) { - text.extend(digits.get(at..at + 4).and_then(hex)); - } + // All ASCII, so every fourth byte is a char boundary. A group that isn't a + // character (a surrogate) makes the whole component map to nothing. + let chars: Option = (0..digits.len()) + .step_by(4) + .map(|at| digits.get(at..at + 4).and_then(hex)) + .collect(); + text.extend(chars); } else if let Some(c) = part .strip_prefix('u') .filter(|d| (4..=6).contains(&d.len()) && d.bytes().all(|b| b.is_ascii_hexdigit())) @@ -709,6 +713,8 @@ pub(crate) fn glyph_name_to_text(name: &str) -> Option { /// 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