Merge PR #3639: Pair /ActualText correctly past 256 levels of marked content

This commit is contained in:
aiongg committed 2026-10-22 01:45:33 +00:00
commit abef812bfa
2 files changed
+24 -1

No files matched your search

+21
View File
@@ -5218,3 +5218,24 @@ fn a_clipping_copy_on_the_same_baseline_is_refused() {
assert!(matches!(&block, Err(EditError::Invalid(m)) if m.contains("clipping path")), "{block:?}");
assert_eq!(text::text_lines(&doc, 0).unwrap().len(), 2, "refused means untouched");
}
/// #3573: past the 256-level marked-content cap every `EMC` must still close its own sequence,
/// so `/ActualText` nested that deep is neither dropped nor applied to too little text.
#[test]
fn actual_text_pairs_past_the_marked_content_cap() {
let span = "/Span <</ActualText (REPLACED)>> BDC BT /F1 24 Tf 72 700 Td (abc) Tj ET EMC";
let outer = "/Span <</ActualText (OUTER)>> BDC BT /F1 24 Tf 72 700 Td /Y BMC (abc) Tj EMC (def) Tj ET EMC";
for (content, expected) in [
(span.to_string(), "REPLACED"),
(format!("{}{span}{}", "/X BMC ".repeat(256), " EMC".repeat(256)), "REPLACED"),
(format!("{}{span}{}", "/X BMC ".repeat(1000), " EMC".repeat(1000)), "REPLACED"),
(outer.to_string(), "OUTER"),
(format!("{}{outer}{}", "/X BMC ".repeat(255), " EMC".repeat(255)), "OUTER"),
] {
let doc = text_page(&content);
let lines = text::text_lines(&doc, 0).unwrap();
assert_eq!(lines.iter().map(|l| l.text.as_str()).collect::<Vec<_>>(), [expected]);
let reading: Vec<String> = text::reading_blocks(&doc, 0).unwrap().into_iter().map(|b| b.text).collect();
assert_eq!(reading, [expected]);
}
}
+3 -1
View File
@@ -829,7 +829,9 @@ fn interpret(
let mut actual: Vec<Option<(usize, usize, String)>> = Vec::new();
for (i, op) in ops.iter().enumerate() {
match op.op.as_slice() {
b"BMC" | b"BDC" if marked.len() < 256 => {
// No depth cap: every `EMC` pops, so a capped push would pair it with the enclosing
// sequence (#3573). Both stacks stay bounded by the number of operators.
b"BMC" | b"BDC" => {
// Marked text paragraphs/headings are authoritative boundaries. Ignore
// artifacts and inner spans, which inherit their enclosing region.
let region = matches!(op.name(0), Some(b"P" | b"H" | b"H1" | b"H2" | b"H3" | b"H4" | b"H5" | b"H6" | b"LI" | b"TD" | b"TH"));