Skip to content

Commit 3548423

Browse files
fix sorting
1 parent 25ec142 commit 3548423

1 file changed

Lines changed: 56 additions & 5 deletions

File tree

src/extractor.rs

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -526,21 +526,72 @@ pub fn group_into_lines(items: Vec<TextItem>) -> Vec<TextLine> {
526526
all_lines
527527
}
528528

529+
/// Determine if Y-sorting should be used instead of stream order.
530+
/// Returns true if the stream order appears chaotic (items jump around in Y position).
531+
fn should_use_y_sorting(items: &[TextItem]) -> bool {
532+
if items.len() < 5 {
533+
return false; // Not enough items to judge
534+
}
535+
536+
// Sample Y positions from stream order
537+
let y_positions: Vec<f32> = items.iter().map(|i| i.y).collect();
538+
539+
// Count "order violations" - cases where Y increases (going up) when it should decrease
540+
// In proper reading order, Y should generally decrease (top to bottom)
541+
let mut large_jumps_up = 0;
542+
let mut large_jumps_down = 0;
543+
let jump_threshold = 50.0; // Significant Y jump
544+
545+
for window in y_positions.windows(2) {
546+
let delta = window[1] - window[0];
547+
if delta > jump_threshold {
548+
large_jumps_up += 1; // Y increased significantly (jumped up on page)
549+
} else if delta < -jump_threshold {
550+
large_jumps_down += 1; // Y decreased significantly (normal reading direction)
551+
}
552+
}
553+
554+
// If there are many upward jumps relative to downward jumps, order is chaotic
555+
// A well-ordered document should have mostly downward progression
556+
let total_jumps = large_jumps_up + large_jumps_down;
557+
if total_jumps < 3 {
558+
return false; // Not enough jumps to judge
559+
}
560+
561+
// If more than 40% of large jumps are upward, use Y-sorting
562+
let chaos_ratio = large_jumps_up as f32 / total_jumps as f32;
563+
chaos_ratio > 0.4
564+
}
565+
529566
/// Group items from a single column into lines
530-
/// Preserves PDF stream order (which is typically reading order) and only groups
531-
/// consecutive items on the same line by their X position.
567+
/// Uses heuristics to decide between PDF stream order and Y-position sorting.
532568
fn group_single_column(items: Vec<TextItem>) -> Vec<TextLine> {
533569
if items.is_empty() {
534570
return Vec::new();
535571
}
536572

537-
// DO NOT sort by Y - preserve PDF stream order which is usually reading order
538-
// Only merge consecutive items that are on the same line (same Y within tolerance)
573+
// Decide whether to use stream order or Y-sorting
574+
let use_y_sorting = should_use_y_sorting(&items);
575+
576+
let items = if use_y_sorting {
577+
// Sort by Y descending (top to bottom in PDF coords)
578+
let mut sorted = items;
579+
sorted.sort_by(|a, b| {
580+
b.y.partial_cmp(&a.y)
581+
.unwrap_or(std::cmp::Ordering::Equal)
582+
.then(a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal))
583+
});
584+
sorted
585+
} else {
586+
items
587+
};
588+
589+
// Group items into lines
539590
let mut lines: Vec<TextLine> = Vec::new();
540591
let y_tolerance = 3.0;
541592

542593
for item in items {
543-
// Only check the most recent line for merging (to preserve stream order)
594+
// Only check the most recent line for merging
544595
let should_merge = lines.last().map_or(false, |last_line| {
545596
last_line.page == item.page && (last_line.y - item.y).abs() < y_tolerance
546597
});

0 commit comments

Comments
 (0)