Improve text extraction with visual reading order and header detection
Major changes: - Switch from lopdf.extract_text() to position-aware extraction - Text is now sorted by visual reading order (top→bottom, left→right) - Fixed font size calculation to account for text matrix scaling - Headers are now detected based on font size ratios - Skip very short text (≤3 chars) for header detection to avoid drop caps This significantly improves output quality for PDFs with complex layouts. Before: Title appeared at end, no structure detected After: Correct reading order, headers marked with # syntax Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
f99d155c52
commit
c14e26495d
+22
-6
@@ -175,14 +175,15 @@ fn extract_page_text_items(
|
||||
extract_text_from_operand(&op.operands[0], doc, &fonts, ¤t_font)
|
||||
{
|
||||
if !text.trim().is_empty() {
|
||||
let rendered_size = effective_font_size(current_font_size, &text_matrix);
|
||||
items.push(TextItem {
|
||||
text,
|
||||
x: text_matrix[4],
|
||||
y: text_matrix[5],
|
||||
width: 0.0, // Would need glyph widths
|
||||
height: current_font_size,
|
||||
height: rendered_size,
|
||||
font: current_font.clone(),
|
||||
font_size: current_font_size,
|
||||
font_size: rendered_size,
|
||||
page: page_num,
|
||||
});
|
||||
}
|
||||
@@ -202,14 +203,15 @@ fn extract_page_text_items(
|
||||
}
|
||||
}
|
||||
if !combined_text.trim().is_empty() {
|
||||
let rendered_size = effective_font_size(current_font_size, &text_matrix);
|
||||
items.push(TextItem {
|
||||
text: combined_text,
|
||||
x: text_matrix[4],
|
||||
y: text_matrix[5],
|
||||
width: 0.0,
|
||||
height: current_font_size,
|
||||
height: rendered_size,
|
||||
font: current_font.clone(),
|
||||
font_size: current_font_size,
|
||||
font_size: rendered_size,
|
||||
page: page_num,
|
||||
});
|
||||
}
|
||||
@@ -225,14 +227,15 @@ fn extract_page_text_items(
|
||||
extract_text_from_operand(&op.operands[0], doc, &fonts, ¤t_font)
|
||||
{
|
||||
if !text.trim().is_empty() {
|
||||
let rendered_size = effective_font_size(current_font_size, &text_matrix);
|
||||
items.push(TextItem {
|
||||
text,
|
||||
x: text_matrix[4],
|
||||
y: text_matrix[5],
|
||||
width: 0.0,
|
||||
height: current_font_size,
|
||||
height: rendered_size,
|
||||
font: current_font.clone(),
|
||||
font_size: current_font_size,
|
||||
font_size: rendered_size,
|
||||
page: page_num,
|
||||
});
|
||||
}
|
||||
@@ -255,6 +258,19 @@ fn get_number(obj: &Object) -> Option<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute effective font size from base size and text matrix
|
||||
/// Text matrix is [a, b, c, d, tx, ty] where a,d are scale factors
|
||||
fn effective_font_size(base_size: f32, text_matrix: &[f32; 6]) -> f32 {
|
||||
// The scale factor is typically the magnitude of the transformation
|
||||
// For most PDFs, text_matrix[0] (a) is the horizontal scale
|
||||
// and text_matrix[3] (d) is the vertical scale
|
||||
let scale_x = (text_matrix[0].powi(2) + text_matrix[1].powi(2)).sqrt();
|
||||
let scale_y = (text_matrix[2].powi(2) + text_matrix[3].powi(2)).sqrt();
|
||||
// Use the larger of the two scales (usually they're equal for non-rotated text)
|
||||
let scale = scale_x.max(scale_y);
|
||||
base_size * scale
|
||||
}
|
||||
|
||||
/// Extract text from a text operand, handling encoding
|
||||
fn extract_text_from_operand(
|
||||
obj: &Object,
|
||||
|
||||
Reference in New Issue
Block a user