From 1b2e2c76d66be9a01478f831051ab670982d70ff Mon Sep 17 00:00:00 2001 From: Abimael Martell <1450169+abimaelmartell@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:27:27 -0600 Subject: [PATCH] fix(extractor): make trace previews unicode-safe (#113) --- src/extractor/layout.rs | 6 +----- src/extractor/mod.rs | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/extractor/layout.rs b/src/extractor/layout.rs index 06ae3c5..0a4530e 100644 --- a/src/extractor/layout.rs +++ b/src/extractor/layout.rs @@ -1234,11 +1234,7 @@ pub(crate) fn group_into_lines_with_thresholds( ci, item.x, item.y, - if item.text.len() > 60 { - &item.text[..60] - } else { - &item.text - } + super::trace_text_preview(&item.text, 60) ); } } diff --git a/src/extractor/mod.rs b/src/extractor/mod.rs index a657332..2f293d9 100644 --- a/src/extractor/mod.rs +++ b/src/extractor/mod.rs @@ -33,6 +33,13 @@ pub(crate) use layout::ColumnRegion; // Public API // --------------------------------------------------------------------------- +pub(crate) fn trace_text_preview(text: &str, max_chars: usize) -> &str { + match text.char_indices().nth(max_chars) { + Some((idx, _)) => &text[..idx], + None => text, + } +} + /// Extract text from PDF file as plain string pub fn extract_text>(path: P) -> Result { crate::validate_pdf_file(&path)?; @@ -195,11 +202,7 @@ fn extract_positioned_text_impl( item.width, item.font_size, item.font, - if item.text.len() > 80 { - &item.text[..80] - } else { - &item.text - } + trace_text_preview(&item.text, 80) ); } } @@ -574,6 +577,16 @@ mod tests { } } + #[test] + fn trace_text_preview_truncates_on_char_boundary() { + let text = format!("{}{}tail", "a".repeat(79), '\u{FFFD}'); + let preview = trace_text_preview(&text, 80); + + assert_eq!(preview.chars().count(), 80); + assert!(text.is_char_boundary(preview.len())); + assert!(preview.ends_with('\u{FFFD}')); + } + #[test] fn merge_items_no_space_before_period() { // Simulate Tc/Tw-adjusted width: "date" width is smaller than the gap