From 1d76fcbc3b76d4fcebdc7edab83e046c2f968d92 Mon Sep 17 00:00:00 2001 From: Aman Mehtar Date: Wed, 21 Oct 2026 00:46:01 +0530 Subject: [PATCH] Edit text: tests for re-encoded subsets and five-letter prefixes (#3661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From @mtlcl410's independent run of this PR: - a second subset of the same font can be re-encoded with `/Differences`, so "-l" has different codes from the first subset's; the line still joins, reads in coding order, and editing it replaces every run, including the re-encoded subset's; - `ABCDE+Helvetica-Bold` has a five-letter prefix, not the six-letter subset tag of ISO 32000-2 §9.6.4, so it stays a different font from `Helvetica-Bold` and the runs stay apart. The first case (and its edit) fails before the subset fix; the second guards the tag-length rule against stripping any `+` prefix. --- crates/edit/src/tests.rs | 45 ++++++++++++++++++++++++++++++++++++++++ crates/edit/src/text.rs | 2 ++ 2 files changed, 47 insertions(+) diff --git a/crates/edit/src/tests.rs b/crates/edit/src/tests.rs index 65292daa3..46cfa4eec 100644 --- a/crates/edit/src/tests.rs +++ b/crates/edit/src/tests.rs @@ -1778,6 +1778,51 @@ fn lines_in_a_font_without_a_base_font_stay_one_block() { assert_eq!(reading[0].text, "First second"); } +/// Two subsets of one font can carry different encodings: here the second names `-` and `l` +/// through `/Differences` (codes 33 and 34), so the same letters have different codes. The visual +/// line still joins and reads in coding order (#3661). +#[test] +fn a_reencoded_subset_of_the_same_font_stays_one_line() { + let content = "BT /F1 16 Tf 72 700 Td (Two) Tj /F2 16 Tf (!\") Tj /F1 16 Tf (ine heading) Tj ET"; + let mut doc = build(&[ + "<< /Type /Catalog /Pages 2 0 R >>", + "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", + "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 800] /Contents 4 0 R /Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> >>", + &stream("", content), + "<< /Type /Font /Subtype /Type1 /BaseFont /ABCDEF+Helvetica-Bold >>", + "<< /Type /Font /Subtype /Type1 /BaseFont /GHIJKL+Helvetica-Bold /Encoding << /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [33 /hyphen /l] >> >>", + ]); + let lines = text::text_lines(&doc, 0).unwrap(); + assert_eq!(lines.len(), 1, "{:?}", lines.iter().map(|l| &l.text).collect::>()); + assert_eq!(lines[0].text, "Two-line heading"); + let reading = text::reading_blocks(&doc, 0).unwrap(); + assert_eq!(reading.len(), 1); + assert_eq!(reading[0].text, "Two-line heading"); + // Editing the one line replaces every run, including the re-encoded subset's. + text::replace_line(&mut doc, 0, 0, "Two-line heading done").unwrap(); + let reopened = reopen(&doc); + let lines = text::text_lines(&reopened, 0).unwrap(); + assert_eq!(lines.len(), 1, "{:?}", lines.iter().map(|l| &l.text).collect::>()); + assert_eq!(lines[0].text, "Two-line heading done"); +} + +/// `ABCDE+` is five letters, not the six-letter subset tag of §9.6.4, so it stays part of the +/// name: `ABCDE+Helvetica-Bold` and `Helvetica-Bold` are different fonts and their runs stay apart. +#[test] +fn a_five_letter_prefix_is_not_a_subset_tag() { + let content = "BT /F1 16 Tf 72 700 Td (Two) Tj /F2 16 Tf (-l) Tj /F1 16 Tf (ine heading) Tj ET"; + let doc = build(&[ + "<< /Type /Catalog /Pages 2 0 R >>", + "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", + "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 800] /Contents 4 0 R /Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> >>", + &stream("", content), + "<< /Type /Font /Subtype /Type1 /BaseFont /ABCDE+Helvetica-Bold >>", + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>", + ]); + let lines = text::text_lines(&doc, 0).unwrap(); + assert_eq!(lines.len(), 3, "{:?}", lines.iter().map(|l| &l.text).collect::>()); +} + #[test] fn text_lines_are_found_and_replaced_in_place() { let mut doc = diff --git a/crates/edit/src/text.rs b/crates/edit/src/text.rs index c7adfdb36..5f8866bf4 100644 --- a/crates/edit/src/text.rs +++ b/crates/edit/src/text.rs @@ -2812,6 +2812,8 @@ mod tests { assert!(same_base_font("Arial", "ABCDEF+Arial")); assert!(same_base_font("Arial", "Arial")); assert!(!same_base_font("ABCDEF+Arial", "GHIJKL+Helvetica")); + // Five letters are part of the name, not a subset tag. + assert!(!same_base_font("ABCDE+Helvetica-Bold", "Helvetica-Bold")); // A tag with no name after it, or no name at all, names no font. assert!(!same_base_font("ABCDEF+", "GHIJKL+")); assert!(!same_base_font("", ""));