Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e482978008 | ||
|
|
b88eb24e94 |
@@ -216,6 +216,7 @@ pub(crate) fn extract_page_text_items(
|
|||||||
let mut marked_content_stack: Vec<MarkedContentEntry> = Vec::new();
|
let mut marked_content_stack: Vec<MarkedContentEntry> = Vec::new();
|
||||||
let mut suppress_glyph_extraction = false;
|
let mut suppress_glyph_extraction = false;
|
||||||
let mut actual_text_start_tm: Option<[f32; 6]> = None; // text matrix at BDC entry
|
let mut actual_text_start_tm: Option<[f32; 6]> = None; // text matrix at BDC entry
|
||||||
|
let mut actual_text_glyph_tm: Option<[f32; 6]> = None; // text matrix at first glyph inside BDC
|
||||||
/// Get the innermost MCID from the marked content stack.
|
/// Get the innermost MCID from the marked content stack.
|
||||||
fn current_mcid(stack: &[MarkedContentEntry]) -> Option<i64> {
|
fn current_mcid(stack: &[MarkedContentEntry]) -> Option<i64> {
|
||||||
stack.iter().rev().find_map(|e| e.mcid)
|
stack.iter().rev().find_map(|e| e.mcid)
|
||||||
@@ -349,8 +350,15 @@ pub(crate) fn extract_page_text_items(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
// ActualText: suppress glyph extraction, just advance text matrix
|
// ActualText: suppress glyph extraction, just advance text matrix.
|
||||||
|
// Capture the FIRST glyph's text matrix as the rendering position
|
||||||
|
// for the ActualText item. Td ops between BDC and the first Tj
|
||||||
|
// may have moved the position to the correct line — the BDC-entry
|
||||||
|
// position (actual_text_start_tm) can be on the previous line.
|
||||||
if suppress_glyph_extraction {
|
if suppress_glyph_extraction {
|
||||||
|
if actual_text_glyph_tm.is_none() {
|
||||||
|
actual_text_glyph_tm = Some(text_matrix);
|
||||||
|
}
|
||||||
if let Some(w_ts) = w_ts_opt {
|
if let Some(w_ts) = w_ts_opt {
|
||||||
text_matrix[4] += w_ts * text_matrix[0];
|
text_matrix[4] += w_ts * text_matrix[0];
|
||||||
text_matrix[5] += w_ts * text_matrix[1];
|
text_matrix[5] += w_ts * text_matrix[1];
|
||||||
@@ -425,6 +433,10 @@ pub(crate) fn extract_page_text_items(
|
|||||||
let font_info = font_widths.get(¤t_font);
|
let font_info = font_widths.get(¤t_font);
|
||||||
let is_invisible = (text_rendering_mode == 3 && !include_invisible)
|
let is_invisible = (text_rendering_mode == 3 && !include_invisible)
|
||||||
|| suppress_glyph_extraction;
|
|| suppress_glyph_extraction;
|
||||||
|
// Capture first-glyph position for ActualText
|
||||||
|
if suppress_glyph_extraction && actual_text_glyph_tm.is_none() {
|
||||||
|
actual_text_glyph_tm = Some(text_matrix);
|
||||||
|
}
|
||||||
|
|
||||||
// Compute space threshold based on font metrics when available
|
// Compute space threshold based on font metrics when available
|
||||||
let space_threshold = if let Some(font_info) = font_info {
|
let space_threshold = if let Some(font_info) = font_info {
|
||||||
@@ -700,6 +712,7 @@ pub(crate) fn extract_page_text_items(
|
|||||||
if actual_text.is_some() {
|
if actual_text.is_some() {
|
||||||
suppress_glyph_extraction = true;
|
suppress_glyph_extraction = true;
|
||||||
actual_text_start_tm = Some(text_matrix);
|
actual_text_start_tm = Some(text_matrix);
|
||||||
|
actual_text_glyph_tm = None; // reset — will be captured at first Tj/TJ
|
||||||
}
|
}
|
||||||
marked_content_stack.push(MarkedContentEntry { actual_text, mcid });
|
marked_content_stack.push(MarkedContentEntry { actual_text, mcid });
|
||||||
}
|
}
|
||||||
@@ -707,8 +720,13 @@ pub(crate) fn extract_page_text_items(
|
|||||||
// End Marked Content — emit ActualText item with correct width
|
// End Marked Content — emit ActualText item with correct width
|
||||||
if let Some(entry) = marked_content_stack.pop() {
|
if let Some(entry) = marked_content_stack.pop() {
|
||||||
if let Some(at) = entry.actual_text {
|
if let Some(at) = entry.actual_text {
|
||||||
// Compute width from text matrix advancement during BDC..EMC
|
// Use the first-glyph position (if available) instead of the
|
||||||
if let Some(start_tm) = actual_text_start_tm.take() {
|
// BDC-entry position. Td operators between BDC and the first
|
||||||
|
// Tj may have moved the text position to the correct line —
|
||||||
|
// the BDC-entry position can be on the previous line.
|
||||||
|
let glyph_tm = actual_text_glyph_tm.take();
|
||||||
|
let entry_tm = actual_text_start_tm.take();
|
||||||
|
if let Some(start_tm) = glyph_tm.or(entry_tm) {
|
||||||
let combined = multiply_matrices(&start_tm, &ctm);
|
let combined = multiply_matrices(&start_tm, &ctm);
|
||||||
if combined[0].abs() >= combined[1].abs() {
|
if combined[0].abs() >= combined[1].abs() {
|
||||||
rotation_votes.horizontal += 1;
|
rotation_votes.horizontal += 1;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
// Rust 1.95 introduced collapsible_match for `if` inside match arms.
|
||||||
|
// The content-stream parsers use this pattern extensively (match on operator
|
||||||
|
// name, then check `in_text_block && !op.operands.is_empty()`). Collapsing
|
||||||
|
// these into match guards would hurt readability. Allow crate-wide.
|
||||||
|
#![allow(clippy::collapsible_match)]
|
||||||
|
|
||||||
//! Smart PDF detection and text extraction using lopdf
|
//! Smart PDF detection and text extraction using lopdf
|
||||||
//!
|
//!
|
||||||
//! # Quick start
|
//! # Quick start
|
||||||
|
|||||||
Reference in New Issue
Block a user