fix: replace partial_cmp with total_cmp to prevent NaN sort panics (#20)
* fix sort panics on NaN values from bogus PDF font metrics Replace all `partial_cmp(...).unwrap_or(Ordering::Equal)` and bare `partial_cmp(...).unwrap()` with `total_cmp()` across the codebase. `partial_cmp` returns `None` for NaN, and mapping that to `Equal` violates total ordering: `a == NaN` and `NaN == b` but `a != b`. Rust 1.81+ detects this and panics in sort_by. `total_cmp` handles NaN deterministically (sorts to end) and guarantees total ordering. The critical crash was in `extract_text_in_regions` (lib.rs:478) where PDFs with bogus font ascent/descent values produced NaN in text item coordinates, causing process abort via NAPI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix missed partial_cmp in layout.rs and restore napi exports - Convert two remaining b.y.partial_cmp(&a.y) calls to total_cmp in group_single_column and column layout sorting - Restore missing napi exports: detectPdf, extractText, extractTextWithPositions, processPdf Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
732b1b1359
commit
fba0a644ef
+4
-4
@@ -68,9 +68,9 @@ where
|
||||
pub(crate) fn sort_line_items(items: &mut [TextItem]) {
|
||||
let rtl = is_rtl_text(items.iter().map(|i| &i.text));
|
||||
if rtl {
|
||||
items.sort_by(|a, b| b.x.partial_cmp(&a.x).unwrap_or(std::cmp::Ordering::Equal));
|
||||
items.sort_by(|a, b| b.x.total_cmp(&a.x));
|
||||
} else {
|
||||
items.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
|
||||
items.sort_by(|a, b| a.x.total_cmp(&b.x));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ fn compute_canva_join_threshold(items: &[TextItem]) -> f32 {
|
||||
}
|
||||
|
||||
let mut sorted: Vec<f32> = ratios;
|
||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||
sorted.sort_by(|a, b| a.total_cmp(b));
|
||||
|
||||
if sorted[sorted.len() - 1] < 0.40 || sorted[0] < 0.40 {
|
||||
return DEFAULT;
|
||||
@@ -478,7 +478,7 @@ fn compute_single_char_join_threshold(items: &[TextItem]) -> f32 {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
ratios.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||
ratios.sort_by(|a, b| a.total_cmp(b));
|
||||
|
||||
// If all gaps are tight (max < 0.40), use default — normal PDF
|
||||
let max_ratio = ratios[ratios.len() - 1];
|
||||
|
||||
Reference in New Issue
Block a user