Edit text: do not group or join by an empty font name (#3661)

Review follow-up. `Metrics::from_dict` leaves `base_font` empty when a font
has no `/BaseFont`, and a bare subset tag such as `/ABCDEF+` left an empty
name after stripping. Two consequences:

- `same_style` split one paragraph into separate reading blocks for the
  exporters when both names were empty, though they grouped before subset
  joining;
- `/ABCDEF+` and `/GHIJKL+` compared equal after stripping, so unrelated
  malformed fonts could join into one line.

`same_base_font` now strips both tags first and requires a non-empty, equal
name, so a missing name or a bare tag matches nothing. `same_style` keeps
grouping lines whose fonts both lack a name, since the resource or fallback
face `same_font` already matched is their whole identity.

Tests: a page in a font without `/BaseFont` still reads as one block (it
split into two before this change), and a bare tag matches no other name.
This commit is contained in:
Aman Mehtar
2026-10-20 22:28:08 +05:30
parent 5eae8d6e90
commit 4a618e8dcb
3 changed files with 38 additions and 3 deletions
+15 -1
View File
@@ -1466,8 +1466,10 @@ fn same_font(line: &TextLine, font: &[u8], base_font: &str, face: Option<&str>)
/// Whether two `/BaseFont` names name the same font: a subset's six-letter tag and `+` prefix do
/// not change the font (ISO 32000-2 §9.6.4), so `ABCDEF+Arial` and `GHIJKL+Arial` are one font.
/// A name that is missing, or only a tag (`ABCDEF+`), names no font and matches nothing.
fn same_base_font(a: &str, b: &str) -> bool {
!a.is_empty() && (a == b || strip_subset_tag(a) == strip_subset_tag(b))
let (a, b) = (strip_subset_tag(a), strip_subset_tag(b));
!a.is_empty() && a == b
}
/// `ABCDEF+Arial` → `Arial`. A name without a subset tag is returned unchanged.
@@ -2804,6 +2806,18 @@ pub(crate) fn translate_blocks(doc: &mut Document, page: usize, indexes: &[usize
mod tests {
use super::*;
#[test]
fn a_subset_tag_is_the_only_difference_between_subsets_of_one_font() {
assert!(same_base_font("ABCDEF+Arial", "GHIJKL+Arial"));
assert!(same_base_font("Arial", "ABCDEF+Arial"));
assert!(same_base_font("Arial", "Arial"));
assert!(!same_base_font("ABCDEF+Arial", "GHIJKL+Helvetica"));
// 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("", ""));
assert!(!same_base_font("Arial", ""));
}
#[test]
fn paragraph_wrapping_keeps_ideographic_spaces() {
let width = |s: &str| s.chars().count() as f64;