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
+52
-52
@@ -77,8 +77,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-android-arm64')
|
const binding = require('firecrawl-pdf-inspector-android-arm64')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-android-arm64/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-android-arm64/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -93,8 +93,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-android-arm-eabi')
|
const binding = require('firecrawl-pdf-inspector-android-arm-eabi')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-android-arm-eabi/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-android-arm-eabi/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -114,8 +114,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-win32-x64-gnu')
|
const binding = require('firecrawl-pdf-inspector-win32-x64-gnu')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-x64-gnu/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-x64-gnu/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -130,8 +130,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-win32-x64-msvc')
|
const binding = require('firecrawl-pdf-inspector-win32-x64-msvc')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-x64-msvc/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-x64-msvc/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -147,8 +147,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-win32-ia32-msvc')
|
const binding = require('firecrawl-pdf-inspector-win32-ia32-msvc')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-ia32-msvc/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-ia32-msvc/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -163,8 +163,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-win32-arm64-msvc')
|
const binding = require('firecrawl-pdf-inspector-win32-arm64-msvc')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-arm64-msvc/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-win32-arm64-msvc/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -182,8 +182,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-darwin-universal')
|
const binding = require('firecrawl-pdf-inspector-darwin-universal')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-darwin-universal/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-darwin-universal/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -198,8 +198,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-darwin-x64')
|
const binding = require('firecrawl-pdf-inspector-darwin-x64')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-darwin-x64/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-darwin-x64/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -214,8 +214,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-darwin-arm64')
|
const binding = require('firecrawl-pdf-inspector-darwin-arm64')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-darwin-arm64/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-darwin-arm64/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -234,8 +234,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-freebsd-x64')
|
const binding = require('firecrawl-pdf-inspector-freebsd-x64')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-freebsd-x64/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-freebsd-x64/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -250,8 +250,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-freebsd-arm64')
|
const binding = require('firecrawl-pdf-inspector-freebsd-arm64')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-freebsd-arm64/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-freebsd-arm64/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -271,8 +271,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-x64-musl')
|
const binding = require('firecrawl-pdf-inspector-linux-x64-musl')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-x64-musl/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-x64-musl/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -287,8 +287,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-x64-gnu')
|
const binding = require('firecrawl-pdf-inspector-linux-x64-gnu')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-x64-gnu/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-x64-gnu/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -305,8 +305,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-arm64-musl')
|
const binding = require('firecrawl-pdf-inspector-linux-arm64-musl')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm64-musl/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm64-musl/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -321,8 +321,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-arm64-gnu')
|
const binding = require('firecrawl-pdf-inspector-linux-arm64-gnu')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm64-gnu/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm64-gnu/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -339,8 +339,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-arm-musleabihf')
|
const binding = require('firecrawl-pdf-inspector-linux-arm-musleabihf')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm-musleabihf/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm-musleabihf/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -355,8 +355,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-arm-gnueabihf')
|
const binding = require('firecrawl-pdf-inspector-linux-arm-gnueabihf')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm-gnueabihf/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-arm-gnueabihf/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -373,8 +373,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-loong64-musl')
|
const binding = require('firecrawl-pdf-inspector-linux-loong64-musl')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-loong64-musl/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-loong64-musl/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -389,8 +389,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-loong64-gnu')
|
const binding = require('firecrawl-pdf-inspector-linux-loong64-gnu')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-loong64-gnu/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-loong64-gnu/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -407,8 +407,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-riscv64-musl')
|
const binding = require('firecrawl-pdf-inspector-linux-riscv64-musl')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-riscv64-musl/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-riscv64-musl/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -423,8 +423,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-riscv64-gnu')
|
const binding = require('firecrawl-pdf-inspector-linux-riscv64-gnu')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-riscv64-gnu/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-riscv64-gnu/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -440,8 +440,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-ppc64-gnu')
|
const binding = require('firecrawl-pdf-inspector-linux-ppc64-gnu')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-ppc64-gnu/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-ppc64-gnu/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -456,8 +456,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-linux-s390x-gnu')
|
const binding = require('firecrawl-pdf-inspector-linux-s390x-gnu')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-s390x-gnu/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-linux-s390x-gnu/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -476,8 +476,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-openharmony-arm64')
|
const binding = require('firecrawl-pdf-inspector-openharmony-arm64')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-openharmony-arm64/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-openharmony-arm64/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -492,8 +492,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-openharmony-x64')
|
const binding = require('firecrawl-pdf-inspector-openharmony-x64')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-openharmony-x64/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-openharmony-x64/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -508,8 +508,8 @@ function requireNative() {
|
|||||||
try {
|
try {
|
||||||
const binding = require('firecrawl-pdf-inspector-openharmony-arm')
|
const binding = require('firecrawl-pdf-inspector-openharmony-arm')
|
||||||
const bindingPackageVersion = require('firecrawl-pdf-inspector-openharmony-arm/package.json').version
|
const bindingPackageVersion = require('firecrawl-pdf-inspector-openharmony-arm/package.json').version
|
||||||
if (bindingPackageVersion !== '0.2.2' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
if (bindingPackageVersion !== '0.2.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||||
throw new Error(`Native binding package version mismatch, expected 0.2.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
throw new Error(`Native binding package version mismatch, expected 0.2.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||||
}
|
}
|
||||||
return binding
|
return binding
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
+9
-17
@@ -218,7 +218,7 @@ fn columns_have_prose(columns: &[ColumnRegion], items: &[&TextItem]) -> bool {
|
|||||||
|
|
||||||
// Sort by Y descending (top of page = higher Y in PDF coords)
|
// Sort by Y descending (top of page = higher Y in PDF coords)
|
||||||
let mut sorted: Vec<&TextItem> = col_items;
|
let mut sorted: Vec<&TextItem> = col_items;
|
||||||
sorted.sort_by(|a, b| b.y.partial_cmp(&a.y).unwrap_or(std::cmp::Ordering::Equal));
|
sorted.sort_by(|a, b| b.y.total_cmp(&a.y));
|
||||||
|
|
||||||
// Group into lines by Y-proximity and measure fill + item count
|
// Group into lines by Y-proximity and measure fill + item count
|
||||||
let mut full_lines = 0usize;
|
let mut full_lines = 0usize;
|
||||||
@@ -616,7 +616,7 @@ fn identify_spanning_lines(items: &[TextItem], columns: &[ColumnRegion]) -> Vec<
|
|||||||
// Build (original_index, y) pairs sorted by Y descending for grouping
|
// Build (original_index, y) pairs sorted by Y descending for grouping
|
||||||
let mut indexed: Vec<(usize, f32)> =
|
let mut indexed: Vec<(usize, f32)> =
|
||||||
items.iter().enumerate().map(|(i, it)| (i, it.y)).collect();
|
items.iter().enumerate().map(|(i, it)| (i, it.y)).collect();
|
||||||
indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
indexed.sort_by(|a, b| b.1.total_cmp(&a.1));
|
||||||
|
|
||||||
// Group by Y-proximity into rough lines (as index sets)
|
// Group by Y-proximity into rough lines (as index sets)
|
||||||
let mut groups: Vec<Vec<usize>> = Vec::new();
|
let mut groups: Vec<Vec<usize>> = Vec::new();
|
||||||
@@ -778,7 +778,7 @@ pub(crate) fn is_newspaper_layout(
|
|||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
let mut ys: Vec<f32> = lines.iter().map(|l| l.y).collect();
|
let mut ys: Vec<f32> = lines.iter().map(|l| l.y).collect();
|
||||||
ys.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
ys.sort_by(|a, b| a.total_cmp(b));
|
||||||
let span = ys.last().unwrap() - ys.first().unwrap();
|
let span = ys.last().unwrap() - ys.first().unwrap();
|
||||||
span / (lines.len() as f32 - 1.0)
|
span / (lines.len() as f32 - 1.0)
|
||||||
};
|
};
|
||||||
@@ -845,7 +845,7 @@ fn split_column_stragglers(lines: Vec<TextLine>) -> (Vec<TextLine>, Vec<TextLine
|
|||||||
|
|
||||||
// Median gap = typical line spacing
|
// Median gap = typical line spacing
|
||||||
let mut sorted_gaps = gaps.clone();
|
let mut sorted_gaps = gaps.clone();
|
||||||
sorted_gaps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
sorted_gaps.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_gap = sorted_gaps[sorted_gaps.len() / 2];
|
let median_gap = sorted_gaps[sorted_gaps.len() / 2];
|
||||||
|
|
||||||
// A gap > 3× median (min 30pt) indicates a break between content clusters
|
// A gap > 3× median (min 30pt) indicates a break between content clusters
|
||||||
@@ -1078,9 +1078,8 @@ pub(crate) fn group_into_lines_with_thresholds(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
above.sort_by(|a, b| b.y.partial_cmp(&a.y).unwrap_or(std::cmp::Ordering::Equal));
|
above.sort_by(|a, b| b.y.total_cmp(&a.y));
|
||||||
below_spanning
|
below_spanning.sort_by(|a, b| b.y.total_cmp(&a.y));
|
||||||
.sort_by(|a, b| b.y.partial_cmp(&a.y).unwrap_or(std::cmp::Ordering::Equal));
|
|
||||||
|
|
||||||
all_lines.extend(above);
|
all_lines.extend(above);
|
||||||
for col in core_columns {
|
for col in core_columns {
|
||||||
@@ -1101,15 +1100,12 @@ pub(crate) fn group_into_lines_with_thresholds(
|
|||||||
|
|
||||||
// Sort by Y descending (top-first), then by X for same-Y lines
|
// Sort by Y descending (top-first), then by X for same-Y lines
|
||||||
all_page_lines.sort_by(|a, b| {
|
all_page_lines.sort_by(|a, b| {
|
||||||
b.y.partial_cmp(&a.y)
|
b.y.total_cmp(&a.y).then(
|
||||||
.unwrap_or(std::cmp::Ordering::Equal)
|
|
||||||
.then(
|
|
||||||
a.items
|
a.items
|
||||||
.first()
|
.first()
|
||||||
.map(|i| i.x)
|
.map(|i| i.x)
|
||||||
.unwrap_or(0.0)
|
.unwrap_or(0.0)
|
||||||
.partial_cmp(&b.items.first().map(|i| i.x).unwrap_or(0.0))
|
.total_cmp(&b.items.first().map(|i| i.x).unwrap_or(0.0)),
|
||||||
.unwrap_or(std::cmp::Ordering::Equal),
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1185,11 +1181,7 @@ fn group_single_column(items: Vec<TextItem>, adaptive_threshold: f32) -> Vec<Tex
|
|||||||
let items = if use_y_sorting {
|
let items = if use_y_sorting {
|
||||||
// Sort by Y descending (top to bottom in PDF coords)
|
// Sort by Y descending (top to bottom in PDF coords)
|
||||||
let mut sorted = items;
|
let mut sorted = items;
|
||||||
sorted.sort_by(|a, b| {
|
sorted.sort_by(|a, b| b.y.total_cmp(&a.y).then(a.x.total_cmp(&b.x)));
|
||||||
b.y.partial_cmp(&a.y)
|
|
||||||
.unwrap_or(std::cmp::Ordering::Equal)
|
|
||||||
.then(a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal))
|
|
||||||
});
|
|
||||||
sorted
|
sorted
|
||||||
} else {
|
} else {
|
||||||
items
|
items
|
||||||
|
|||||||
@@ -334,17 +334,14 @@ pub(crate) fn merge_text_items(items: Vec<TextItem>) -> Vec<TextItem> {
|
|||||||
for (_, _, group) in &mut line_groups {
|
for (_, _, group) in &mut line_groups {
|
||||||
let rtl = is_rtl_text(group.iter().map(|i| &i.text));
|
let rtl = is_rtl_text(group.iter().map(|i| &i.text));
|
||||||
if rtl {
|
if rtl {
|
||||||
group.sort_by(|a, b| b.x.partial_cmp(&a.x).unwrap_or(std::cmp::Ordering::Equal));
|
group.sort_by(|a, b| b.x.total_cmp(&a.x));
|
||||||
} else {
|
} else {
|
||||||
group.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
|
group.sort_by(|a, b| a.x.total_cmp(&b.x));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort groups by page then Y descending (top of page first)
|
// Sort groups by page then Y descending (top of page first)
|
||||||
line_groups.sort_by(|a, b| {
|
line_groups.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| b.1.total_cmp(&a.1)));
|
||||||
a.0.cmp(&b.0)
|
|
||||||
.then_with(|| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal))
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut merged = Vec::new();
|
let mut merged = Vec::new();
|
||||||
|
|
||||||
@@ -452,7 +449,7 @@ pub(crate) fn merge_subscript_items(items: Vec<TextItem>) -> Vec<TextItem> {
|
|||||||
|
|
||||||
for (_, _, mut group) in line_groups {
|
for (_, _, mut group) in line_groups {
|
||||||
// Sort by X position
|
// Sort by X position
|
||||||
group.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
|
group.sort_by(|a, b| a.x.total_cmp(&b.x));
|
||||||
|
|
||||||
// Find the dominant (most common) font size in this group
|
// Find the dominant (most common) font size in this group
|
||||||
let max_fs = group.iter().map(|i| i.font_size).fold(0.0_f32, f32::max);
|
let max_fs = group.iter().map(|i| i.font_size).fold(0.0_f32, f32::max);
|
||||||
|
|||||||
+21
-8
@@ -477,16 +477,29 @@ fn collect_text_in_region(
|
|||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort top→bottom (descending Y in bottom-left coords), then left→right
|
// Sort top→bottom (descending Y in bottom-left coords), then left→right.
|
||||||
|
// Uses total_cmp to avoid panics on NaN values from bogus font metrics.
|
||||||
matched.sort_by(|a, b| {
|
matched.sort_by(|a, b| {
|
||||||
let line_threshold = a.font_size.max(b.font_size) * 0.5;
|
let fs_a = if a.font_size.is_finite() {
|
||||||
let y_diff = b.y - a.y; // descending Y = top to bottom
|
a.font_size
|
||||||
if y_diff.abs() < line_threshold {
|
|
||||||
a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal)
|
|
||||||
} else {
|
} else {
|
||||||
y_diff
|
0.0
|
||||||
.partial_cmp(&0.0_f32)
|
};
|
||||||
.unwrap_or(std::cmp::Ordering::Equal)
|
let fs_b = if b.font_size.is_finite() {
|
||||||
|
b.font_size
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
let line_threshold = fs_a.max(fs_b) * 0.5;
|
||||||
|
let ay = if a.y.is_finite() { a.y } else { 0.0 };
|
||||||
|
let by = if b.y.is_finite() { b.y } else { 0.0 };
|
||||||
|
let y_diff = by - ay; // descending Y = top to bottom
|
||||||
|
if y_diff.abs() < line_threshold {
|
||||||
|
let ax = if a.x.is_finite() { a.x } else { 0.0 };
|
||||||
|
let bx = if b.x.is_finite() { b.x } else { 0.0 };
|
||||||
|
ax.total_cmp(&bx)
|
||||||
|
} else {
|
||||||
|
by.total_cmp(&ay)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ pub(crate) fn compute_paragraph_threshold(lines: &[TextLine], base_size: f32) ->
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
gaps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
gaps.sort_by(|a, b| a.total_cmp(b));
|
||||||
|
|
||||||
let median = gaps[gaps.len() / 2];
|
let median = gaps[gaps.len() / 2];
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ pub(crate) fn compute_heading_tiers(lines: &[TextLine], base_size: f32) -> Vec<f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort descending
|
// Sort descending
|
||||||
heading_sizes.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
heading_sizes.sort_by(|a, b| b.total_cmp(a));
|
||||||
|
|
||||||
// Cluster sizes within 0.5pt into same tier (use first value as representative)
|
// Cluster sizes within 0.5pt into same tier (use first value as representative)
|
||||||
let mut tiers: Vec<f32> = Vec::new();
|
let mut tiers: Vec<f32> = Vec::new();
|
||||||
|
|||||||
+4
-4
@@ -42,7 +42,7 @@ pub(crate) fn split_side_by_side(items: &[TextItem]) -> Vec<(f32, f32)> {
|
|||||||
|
|
||||||
// Sort items by left edge
|
// Sort items by left edge
|
||||||
let mut xs: Vec<f32> = items.iter().map(|i| i.x).collect();
|
let mut xs: Vec<f32> = items.iter().map(|i| i.x).collect();
|
||||||
xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
xs.sort_by(|a, b| a.total_cmp(b));
|
||||||
|
|
||||||
// Find all candidate gaps: ≥30pt, in the middle 60% of the X range,
|
// Find all candidate gaps: ≥30pt, in the middle 60% of the X range,
|
||||||
// with ≥20 items on each side.
|
// with ≥20 items on each side.
|
||||||
@@ -118,7 +118,7 @@ pub(crate) fn split_side_by_side(items: &[TextItem]) -> Vec<(f32, f32)> {
|
|||||||
})
|
})
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
balanced_positions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
balanced_positions.sort_by(|a, b| a.total_cmp(b));
|
||||||
balanced_positions.dedup_by(|a, b| (*a - *b).abs() < 50.0);
|
balanced_positions.dedup_by(|a, b| (*a - *b).abs() < 50.0);
|
||||||
if balanced_positions.len() > 1 {
|
if balanced_positions.len() > 1 {
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -209,7 +209,7 @@ fn split_from_hint_regions(items: &[TextItem], rects: &[PdfRect], page: u32) ->
|
|||||||
|
|
||||||
// Width outlier filter (same as detect_tables_from_rects)
|
// Width outlier filter (same as detect_tables_from_rects)
|
||||||
let mut widths: Vec<f32> = page_rects.iter().map(|&(_, _, w, _)| w).collect();
|
let mut widths: Vec<f32> = page_rects.iter().map(|&(_, _, w, _)| w).collect();
|
||||||
widths.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
widths.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_width = widths[widths.len() / 2];
|
let median_width = widths[widths.len() / 2];
|
||||||
page_rects.retain(|&(_, _, w, _)| w <= median_width * 10.0);
|
page_rects.retain(|&(_, _, w, _)| w <= median_width * 10.0);
|
||||||
|
|
||||||
@@ -1037,7 +1037,7 @@ pub(crate) fn to_markdown_from_items_with_rects_and_lines(
|
|||||||
}
|
}
|
||||||
// Sort by Y descending (top to bottom) so left and right
|
// Sort by Y descending (top to bottom) so left and right
|
||||||
// band lines interleave in visual reading order.
|
// band lines interleave in visual reading order.
|
||||||
page_lines.sort_by(|a, b| b.y.partial_cmp(&a.y).unwrap_or(std::cmp::Ordering::Equal));
|
page_lines.sort_by(|a, b| b.y.total_cmp(&a.y));
|
||||||
all_lines.extend(page_lines);
|
all_lines.extend(page_lines);
|
||||||
}
|
}
|
||||||
all_lines
|
all_lines
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ pub(crate) fn strip_repeated_lines(lines: Vec<TextLine>, page_count: u32) -> Vec
|
|||||||
page_sorted_ys.entry(line.page).or_default().push(line.y);
|
page_sorted_ys.entry(line.page).or_default().push(line.y);
|
||||||
}
|
}
|
||||||
for ys in page_sorted_ys.values_mut() {
|
for ys in page_sorted_ys.values_mut() {
|
||||||
ys.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
ys.sort_by(|a, b| a.total_cmp(b));
|
||||||
ys.dedup();
|
ys.dedup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub(crate) fn merge_adjacent_items(items: &[TextItem]) -> (Vec<TextItem>, Vec<Ve
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort groups by Y descending (top of page first)
|
// Sort groups by Y descending (top of page first)
|
||||||
line_groups.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
|
line_groups.sort_by(|a, b| b.0.total_cmp(&a.0));
|
||||||
|
|
||||||
let mut merged_items = Vec::new();
|
let mut merged_items = Vec::new();
|
||||||
let mut index_map: Vec<Vec<usize>> = Vec::new();
|
let mut index_map: Vec<Vec<usize>> = Vec::new();
|
||||||
@@ -284,7 +284,7 @@ fn find_table_regions(items: &[(usize, &TextItem)]) -> Vec<(f32, f32)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut y_positions: Vec<f32> = items.iter().map(|(_, i)| i.y).collect();
|
let mut y_positions: Vec<f32> = items.iter().map(|(_, i)| i.y).collect();
|
||||||
y_positions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
y_positions.sort_by(|a, b| a.total_cmp(b));
|
||||||
|
|
||||||
// Find clusters of Y positions (table regions)
|
// Find clusters of Y positions (table regions)
|
||||||
let mut regions = Vec::new();
|
let mut regions = Vec::new();
|
||||||
@@ -347,7 +347,7 @@ fn find_table_regions_strict(items: &[(usize, &TextItem)]) -> Vec<(f32, f32, f32
|
|||||||
let mut qualifying_rows: Vec<(f32, Vec<f32>)> = Vec::new(); // (y, cluster_starts)
|
let mut qualifying_rows: Vec<(f32, Vec<f32>)> = Vec::new(); // (y, cluster_starts)
|
||||||
for (y, x_positions) in &row_groups {
|
for (y, x_positions) in &row_groups {
|
||||||
let mut sorted_xs = x_positions.clone();
|
let mut sorted_xs = x_positions.clone();
|
||||||
sorted_xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
sorted_xs.sort_by(|a, b| a.total_cmp(b));
|
||||||
|
|
||||||
if sorted_xs.is_empty() {
|
if sorted_xs.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@@ -379,14 +379,14 @@ fn find_table_regions_strict(items: &[(usize, &TextItem)]) -> Vec<(f32, f32, f32
|
|||||||
// Step 3: Find contiguous runs of qualifying rows.
|
// Step 3: Find contiguous runs of qualifying rows.
|
||||||
// Use adaptive gap: median spacing × 3 (handles wrapped cells where
|
// Use adaptive gap: median spacing × 3 (handles wrapped cells where
|
||||||
// qualifying rows are spaced further apart), with a floor of 25pt.
|
// qualifying rows are spaced further apart), with a floor of 25pt.
|
||||||
qualifying_rows.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
|
qualifying_rows.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||||
|
|
||||||
let max_gap = if qualifying_rows.len() >= 3 {
|
let max_gap = if qualifying_rows.len() >= 3 {
|
||||||
let mut gaps: Vec<f32> = qualifying_rows
|
let mut gaps: Vec<f32> = qualifying_rows
|
||||||
.windows(2)
|
.windows(2)
|
||||||
.map(|w| (w[1].0 - w[0].0).abs())
|
.map(|w| (w[1].0 - w[0].0).abs())
|
||||||
.collect();
|
.collect();
|
||||||
gaps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
gaps.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_gap = gaps[gaps.len() / 2];
|
let median_gap = gaps[gaps.len() / 2];
|
||||||
(median_gap * 3.0).max(25.0)
|
(median_gap * 3.0).max(25.0)
|
||||||
} else {
|
} else {
|
||||||
@@ -566,11 +566,9 @@ fn detect_table_in_region(items: &[(usize, &TextItem)], mode: TableDetectionMode
|
|||||||
// Sort by X position (direction-aware)
|
// Sort by X position (direction-aware)
|
||||||
let rtl = is_rtl_text(col_items.iter().map(|i| &i.text));
|
let rtl = is_rtl_text(col_items.iter().map(|i| &i.text));
|
||||||
if rtl {
|
if rtl {
|
||||||
col_items
|
col_items.sort_by(|a, b| b.x.total_cmp(&a.x));
|
||||||
.sort_by(|a, b| b.x.partial_cmp(&a.x).unwrap_or(std::cmp::Ordering::Equal));
|
|
||||||
} else {
|
} else {
|
||||||
col_items
|
col_items.sort_by(|a, b| a.x.total_cmp(&b.x));
|
||||||
.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join items with subscript-aware spacing
|
// Join items with subscript-aware spacing
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ pub fn detect_tables_from_lines(items: &[TextItem], lines: &[PdfLine], page: u32
|
|||||||
|
|
||||||
// Row edges need to be in descending order (top of page = higher Y first)
|
// Row edges need to be in descending order (top of page = higher Y first)
|
||||||
let mut row_edges_desc = row_edges;
|
let mut row_edges_desc = row_edges;
|
||||||
row_edges_desc.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
row_edges_desc.sort_by(|a, b| b.total_cmp(a));
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"detect_lines p{}: {} row_edges, {} col_edges, table=({:.0},{:.0})-({:.0},{:.0}), spanning_h={}, spanning_v={}",
|
"detect_lines p{}: {} row_edges, {} col_edges, table=({:.0},{:.0})-({:.0},{:.0}), spanning_h={}, spanning_v={}",
|
||||||
|
|||||||
+17
-17
@@ -144,7 +144,7 @@ fn split_wide_cluster(
|
|||||||
|
|
||||||
// Build sorted list of X-intervals (x_left, x_right) from each rect
|
// Build sorted list of X-intervals (x_left, x_right) from each rect
|
||||||
let mut intervals: Vec<(f32, f32)> = rects.iter().map(|&(x, _, w, _)| (x, x + w)).collect();
|
let mut intervals: Vec<(f32, f32)> = rects.iter().map(|&(x, _, w, _)| (x, x + w)).collect();
|
||||||
intervals.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
|
intervals.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||||
|
|
||||||
// Merge overlapping intervals to find contiguous X-bands
|
// Merge overlapping intervals to find contiguous X-bands
|
||||||
let mut merged: Vec<(f32, f32)> = Vec::new();
|
let mut merged: Vec<(f32, f32)> = Vec::new();
|
||||||
@@ -262,7 +262,7 @@ pub fn detect_tables_from_rects(
|
|||||||
// background fills stand out clearly.
|
// background fills stand out clearly.
|
||||||
if page_rects.len() >= 6 {
|
if page_rects.len() >= 6 {
|
||||||
let mut widths: Vec<f32> = page_rects.iter().map(|&(_, _, w, _)| w).collect();
|
let mut widths: Vec<f32> = page_rects.iter().map(|&(_, _, w, _)| w).collect();
|
||||||
widths.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
widths.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_width = widths[widths.len() / 2];
|
let median_width = widths[widths.len() / 2];
|
||||||
let width_threshold = median_width * 10.0;
|
let width_threshold = median_width * 10.0;
|
||||||
let before = page_rects.len();
|
let before = page_rects.len();
|
||||||
@@ -333,7 +333,7 @@ pub fn detect_tables_from_rects(
|
|||||||
// cluster they overlap, so grid detection still has their edges.
|
// cluster they overlap, so grid detection still has their edges.
|
||||||
let is_page_bg = {
|
let is_page_bg = {
|
||||||
let mut heights: Vec<f32> = page_rects.iter().map(|&(_, _, _, h)| h).collect();
|
let mut heights: Vec<f32> = page_rects.iter().map(|&(_, _, _, h)| h).collect();
|
||||||
heights.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
heights.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_height = heights[heights.len() / 2];
|
let median_height = heights[heights.len() / 2];
|
||||||
let height_threshold = median_height * 20.0;
|
let height_threshold = median_height * 20.0;
|
||||||
let flags: Vec<bool> = page_rects
|
let flags: Vec<bool> = page_rects
|
||||||
@@ -621,7 +621,7 @@ fn merge_overlapping_hints(mut hints: Vec<RectHintRegion>) -> Vec<RectHintRegion
|
|||||||
return hints;
|
return hints;
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
hints.sort_by(|a, b| a.x_left.partial_cmp(&b.x_left).unwrap());
|
hints.sort_by(|a, b| a.x_left.total_cmp(&b.x_left));
|
||||||
let mut merged: Vec<RectHintRegion> = Vec::new();
|
let mut merged: Vec<RectHintRegion> = Vec::new();
|
||||||
let mut any_merged = false;
|
let mut any_merged = false;
|
||||||
for hint in &hints {
|
for hint in &hints {
|
||||||
@@ -685,7 +685,7 @@ fn extract_hint_region(group_rects: &[(f32, f32, f32, f32)]) -> Option<RectHintR
|
|||||||
|
|
||||||
// Compute median height to identify cell-sized rects
|
// Compute median height to identify cell-sized rects
|
||||||
let mut heights: Vec<f32> = group_rects.iter().map(|&(_, _, _, h)| h).collect();
|
let mut heights: Vec<f32> = group_rects.iter().map(|&(_, _, _, h)| h).collect();
|
||||||
heights.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
heights.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_h = heights[heights.len() / 2];
|
let median_h = heights[heights.len() / 2];
|
||||||
|
|
||||||
// Keep only cell-sized rects (height ≤ 4× median)
|
// Keep only cell-sized rects (height ≤ 4× median)
|
||||||
@@ -857,9 +857,9 @@ fn try_build_grid(
|
|||||||
|
|
||||||
// Sort column edges left-to-right, row edges top-to-bottom (highest Y first for PDF)
|
// Sort column edges left-to-right, row edges top-to-bottom (highest Y first for PDF)
|
||||||
let mut col_edges = x_edges;
|
let mut col_edges = x_edges;
|
||||||
col_edges.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
col_edges.sort_by(|a, b| a.total_cmp(b));
|
||||||
let mut row_edges = y_edges;
|
let mut row_edges = y_edges;
|
||||||
row_edges.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
row_edges.sort_by(|a, b| b.total_cmp(a));
|
||||||
|
|
||||||
let num_cols = col_edges.len() - 1;
|
let num_cols = col_edges.len() - 1;
|
||||||
let num_rows = row_edges.len() - 1;
|
let num_rows = row_edges.len() - 1;
|
||||||
@@ -1048,7 +1048,7 @@ fn try_build_grid(
|
|||||||
/// Deduplicate nearby edge values within a tolerance, returning sorted unique edges.
|
/// Deduplicate nearby edge values within a tolerance, returning sorted unique edges.
|
||||||
pub(crate) fn snap_edges(values: &[f32], tolerance: f32) -> Vec<f32> {
|
pub(crate) fn snap_edges(values: &[f32], tolerance: f32) -> Vec<f32> {
|
||||||
let mut sorted: Vec<f32> = values.to_vec();
|
let mut sorted: Vec<f32> = values.to_vec();
|
||||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
sorted.sort_by(|a, b| a.total_cmp(b));
|
||||||
|
|
||||||
let mut snapped: Vec<f32> = Vec::new();
|
let mut snapped: Vec<f32> = Vec::new();
|
||||||
for &v in &sorted {
|
for &v in &sorted {
|
||||||
@@ -1204,7 +1204,7 @@ fn is_row_stripe_pattern(rects: &[(f32, f32, f32, f32)]) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut widths: Vec<f32> = rects.iter().map(|&(_, _, w, _)| w).collect();
|
let mut widths: Vec<f32> = rects.iter().map(|&(_, _, w, _)| w).collect();
|
||||||
widths.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
widths.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_width = widths[widths.len() / 2];
|
let median_width = widths[widths.len() / 2];
|
||||||
|
|
||||||
// Must be page-spanning (>200pt)
|
// Must be page-spanning (>200pt)
|
||||||
@@ -1252,7 +1252,7 @@ fn detect_row_stripe_table(
|
|||||||
|
|
||||||
// Sort row edges top-to-bottom (highest Y first for PDF)
|
// Sort row edges top-to-bottom (highest Y first for PDF)
|
||||||
let mut row_edges = y_edges;
|
let mut row_edges = y_edges;
|
||||||
row_edges.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
row_edges.sort_by(|a, b| b.total_cmp(a));
|
||||||
|
|
||||||
// Compute the bounding box of the stripe region for filtering items
|
// Compute the bounding box of the stripe region for filtering items
|
||||||
let y_top = row_edges[0];
|
let y_top = row_edges[0];
|
||||||
@@ -1476,7 +1476,7 @@ fn detect_row_stripe_table_from_cell_rects(
|
|||||||
// bounding box to scope items and derive rows from text Y-positions.
|
// bounding box to scope items and derive rows from text Y-positions.
|
||||||
let row_edges = if y_edges.len() >= 4 {
|
let row_edges = if y_edges.len() >= 4 {
|
||||||
let mut edges = y_edges;
|
let mut edges = y_edges;
|
||||||
edges.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
edges.sort_by(|a, b| b.total_cmp(a));
|
||||||
edges
|
edges
|
||||||
} else {
|
} else {
|
||||||
// Fall back: gather items in the rect region and cluster by Y
|
// Fall back: gather items in the rect region and cluster by Y
|
||||||
@@ -1508,11 +1508,11 @@ fn detect_row_stripe_table_from_cell_rects(
|
|||||||
// Cluster Y positions using median font height as threshold
|
// Cluster Y positions using median font height as threshold
|
||||||
let median_h = {
|
let median_h = {
|
||||||
let mut hs: Vec<f32> = region_items.iter().map(|i| i.height).collect();
|
let mut hs: Vec<f32> = region_items.iter().map(|i| i.height).collect();
|
||||||
hs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
hs.sort_by(|a, b| a.total_cmp(b));
|
||||||
hs[hs.len() / 2]
|
hs[hs.len() / 2]
|
||||||
};
|
};
|
||||||
let mut ys: Vec<f32> = region_items.iter().map(|i| i.y).collect();
|
let mut ys: Vec<f32> = region_items.iter().map(|i| i.y).collect();
|
||||||
ys.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
ys.sort_by(|a, b| b.total_cmp(a));
|
||||||
let mut edges = Vec::new();
|
let mut edges = Vec::new();
|
||||||
let threshold = median_h * 0.8;
|
let threshold = median_h * 0.8;
|
||||||
let mut cluster_start = ys[0];
|
let mut cluster_start = ys[0];
|
||||||
@@ -1536,7 +1536,7 @@ fn detect_row_stripe_table_from_cell_rects(
|
|||||||
edges.push(center - median_h * 0.5);
|
edges.push(center - median_h * 0.5);
|
||||||
let _ = cluster_start; // suppress unused warning
|
let _ = cluster_start; // suppress unused warning
|
||||||
edges = snap_edges(&edges, 3.0);
|
edges = snap_edges(&edges, 3.0);
|
||||||
edges.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
edges.sort_by(|a, b| b.total_cmp(a));
|
||||||
if edges.len() < 4 {
|
if edges.len() < 4 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -1546,7 +1546,7 @@ fn detect_row_stripe_table_from_cell_rects(
|
|||||||
// Compute bounding box from non-full-page rects
|
// Compute bounding box from non-full-page rects
|
||||||
let median_h = {
|
let median_h = {
|
||||||
let mut heights: Vec<f32> = group_rects.iter().map(|&(_, _, _, h)| h).collect();
|
let mut heights: Vec<f32> = group_rects.iter().map(|&(_, _, _, h)| h).collect();
|
||||||
heights.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
heights.sort_by(|a, b| a.total_cmp(b));
|
||||||
heights[heights.len() / 2]
|
heights[heights.len() / 2]
|
||||||
};
|
};
|
||||||
let content_rects: Vec<_> = group_rects
|
let content_rects: Vec<_> = group_rects
|
||||||
@@ -1724,7 +1724,7 @@ fn detect_merged_cluster_table(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut row_edges = y_edges;
|
let mut row_edges = y_edges;
|
||||||
row_edges.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
row_edges.sort_by(|a, b| b.total_cmp(a));
|
||||||
|
|
||||||
// Bounding box of all rects
|
// Bounding box of all rects
|
||||||
let y_top = row_edges[0];
|
let y_top = row_edges[0];
|
||||||
@@ -1890,7 +1890,7 @@ fn detect_merged_cluster_table(
|
|||||||
/// (no need for anti-paragraph safeguards).
|
/// (no need for anti-paragraph safeguards).
|
||||||
fn cluster_x_positions(items: &[(usize, &TextItem)], min_threshold: f32) -> Vec<f32> {
|
fn cluster_x_positions(items: &[(usize, &TextItem)], min_threshold: f32) -> Vec<f32> {
|
||||||
let mut x_positions: Vec<f32> = items.iter().map(|(_, i)| i.x).collect();
|
let mut x_positions: Vec<f32> = items.iter().map(|(_, i)| i.x).collect();
|
||||||
x_positions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
x_positions.sort_by(|a, b| a.total_cmp(b));
|
||||||
|
|
||||||
if x_positions.is_empty() {
|
if x_positions.is_empty() {
|
||||||
return vec![];
|
return vec![];
|
||||||
|
|||||||
+4
-4
@@ -9,7 +9,7 @@ pub(crate) fn find_column_boundaries(
|
|||||||
mode: TableDetectionMode,
|
mode: TableDetectionMode,
|
||||||
) -> Vec<f32> {
|
) -> Vec<f32> {
|
||||||
let mut x_positions: Vec<f32> = items.iter().map(|(_, i)| i.x).collect();
|
let mut x_positions: Vec<f32> = items.iter().map(|(_, i)| i.x).collect();
|
||||||
x_positions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
x_positions.sort_by(|a, b| a.total_cmp(b));
|
||||||
|
|
||||||
if x_positions.is_empty() {
|
if x_positions.is_empty() {
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -43,7 +43,7 @@ pub(crate) fn find_column_boundaries(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if consec_gaps.len() > 2 {
|
if consec_gaps.len() > 2 {
|
||||||
consec_gaps.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
consec_gaps.sort_by(|a, b| a.total_cmp(b));
|
||||||
// Find the biggest jump in the sorted gap sequence — natural break
|
// Find the biggest jump in the sorted gap sequence — natural break
|
||||||
// between within-column jitter and between-column spacing.
|
// between within-column jitter and between-column spacing.
|
||||||
// Require at least 3 values on each side to avoid outlier-dominated
|
// Require at least 3 values on each side to avoid outlier-dominated
|
||||||
@@ -151,7 +151,7 @@ pub(crate) fn find_column_boundaries(
|
|||||||
/// Find row boundaries by clustering Y positions
|
/// Find row boundaries by clustering Y positions
|
||||||
pub(crate) fn find_row_boundaries(items: &[(usize, &TextItem)]) -> Vec<f32> {
|
pub(crate) fn find_row_boundaries(items: &[(usize, &TextItem)]) -> Vec<f32> {
|
||||||
let mut y_positions: Vec<f32> = items.iter().map(|(_, i)| i.y).collect();
|
let mut y_positions: Vec<f32> = items.iter().map(|(_, i)| i.y).collect();
|
||||||
y_positions.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)); // Descending
|
y_positions.sort_by(|a, b| b.total_cmp(a)); // Descending
|
||||||
|
|
||||||
if y_positions.is_empty() {
|
if y_positions.is_empty() {
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -162,7 +162,7 @@ pub(crate) fn find_row_boundaries(items: &[(usize, &TextItem)]) -> Vec<f32> {
|
|||||||
// inter-row gaps (≥1× font size), preventing row merging in uniform-spaced PDFs.
|
// inter-row gaps (≥1× font size), preventing row merging in uniform-spaced PDFs.
|
||||||
let cluster_threshold = {
|
let cluster_threshold = {
|
||||||
let mut font_sizes: Vec<f32> = items.iter().map(|(_, i)| i.font_size).collect();
|
let mut font_sizes: Vec<f32> = items.iter().map(|(_, i)| i.font_size).collect();
|
||||||
font_sizes.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
font_sizes.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_font = font_sizes[font_sizes.len() / 2];
|
let median_font = font_sizes[font_sizes.len() / 2];
|
||||||
(median_font * 0.8).max(4.0)
|
(median_font * 0.8).max(4.0)
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-6
@@ -33,7 +33,7 @@ pub(crate) fn try_build_rect_guided_table(
|
|||||||
|
|
||||||
// 1. Derive column boundaries from rect X positions (snapped to 2pt tolerance)
|
// 1. Derive column boundaries from rect X positions (snapped to 2pt tolerance)
|
||||||
let mut x_lefts: Vec<f32> = cluster_rects.iter().map(|&(x, _, _, _)| x).collect();
|
let mut x_lefts: Vec<f32> = cluster_rects.iter().map(|&(x, _, _, _)| x).collect();
|
||||||
x_lefts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
x_lefts.sort_by(|a, b| a.total_cmp(b));
|
||||||
// Snap: deduplicate within 2pt tolerance
|
// Snap: deduplicate within 2pt tolerance
|
||||||
let mut col_boundaries: Vec<f32> = Vec::new();
|
let mut col_boundaries: Vec<f32> = Vec::new();
|
||||||
for x in &x_lefts {
|
for x in &x_lefts {
|
||||||
@@ -54,7 +54,7 @@ pub(crate) fn try_build_rect_guided_table(
|
|||||||
// boundaries so every day gets a column.
|
// boundaries so every day gets a column.
|
||||||
if col_boundaries.len() >= 2 {
|
if col_boundaries.len() >= 2 {
|
||||||
let mut spacings: Vec<f32> = col_boundaries.windows(2).map(|w| w[1] - w[0]).collect();
|
let mut spacings: Vec<f32> = col_boundaries.windows(2).map(|w| w[1] - w[0]).collect();
|
||||||
spacings.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
spacings.sort_by(|a, b| a.total_cmp(b));
|
||||||
let median_spacing = spacings[spacings.len() / 2];
|
let median_spacing = spacings[spacings.len() / 2];
|
||||||
let threshold = median_spacing * 1.5;
|
let threshold = median_spacing * 1.5;
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ pub(crate) fn try_build_rect_guided_table(
|
|||||||
|
|
||||||
// 3. Derive row boundaries from item Y positions (5pt tolerance)
|
// 3. Derive row boundaries from item Y positions (5pt tolerance)
|
||||||
let mut y_values: Vec<f32> = expanded_items.iter().map(|(item, _)| item.y).collect();
|
let mut y_values: Vec<f32> = expanded_items.iter().map(|(item, _)| item.y).collect();
|
||||||
y_values.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)); // descending
|
y_values.sort_by(|a, b| b.total_cmp(a)); // descending
|
||||||
let mut row_boundaries: Vec<f32> = Vec::new();
|
let mut row_boundaries: Vec<f32> = Vec::new();
|
||||||
for y in &y_values {
|
for y in &y_values {
|
||||||
if row_boundaries
|
if row_boundaries
|
||||||
@@ -296,7 +296,7 @@ pub(crate) fn try_build_table_from_columns(items: &[TextItem], page: u32) -> Opt
|
|||||||
|
|
||||||
// Find the top-most row with items in multiple columns (likely the header)
|
// Find the top-most row with items in multiple columns (likely the header)
|
||||||
let mut ys: Vec<f32> = page_items.iter().map(|i| i.y).collect();
|
let mut ys: Vec<f32> = page_items.iter().map(|i| i.y).collect();
|
||||||
ys.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
ys.sort_by(|a, b| b.total_cmp(a));
|
||||||
ys.dedup_by(|a, b| (*a - *b).abs() < y_tol);
|
ys.dedup_by(|a, b| (*a - *b).abs() < y_tol);
|
||||||
|
|
||||||
for &header_y in ys.iter().take(5) {
|
for &header_y in ys.iter().take(5) {
|
||||||
@@ -318,7 +318,7 @@ pub(crate) fn try_build_table_from_columns(items: &[TextItem], page: u32) -> Opt
|
|||||||
if col_items.len() >= 2 {
|
if col_items.len() >= 2 {
|
||||||
// Sort by X and find the split point
|
// Sort by X and find the split point
|
||||||
let mut sorted: Vec<f32> = col_items.iter().map(|i| i.x).collect();
|
let mut sorted: Vec<f32> = col_items.iter().map(|i| i.x).collect();
|
||||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
sorted.sort_by(|a, b| a.total_cmp(b));
|
||||||
// Split at the midpoint between the two items
|
// Split at the midpoint between the two items
|
||||||
let split_x = (sorted[0]
|
let split_x = (sorted[0]
|
||||||
+ col_items.iter().find(|i| i.x == sorted[0]).unwrap().width
|
+ col_items.iter().find(|i| i.x == sorted[0]).unwrap().width
|
||||||
@@ -411,7 +411,7 @@ pub(crate) fn try_build_table_from_columns(items: &[TextItem], page: u32) -> Opt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
row_ys.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
row_ys.sort_by(|a, b| b.total_cmp(a));
|
||||||
|
|
||||||
if row_ys.len() < 3 || row_ys.len() > 40 {
|
if row_ys.len() < 3 || row_ys.len() > 40 {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
+4
-4
@@ -68,9 +68,9 @@ where
|
|||||||
pub(crate) fn sort_line_items(items: &mut [TextItem]) {
|
pub(crate) fn sort_line_items(items: &mut [TextItem]) {
|
||||||
let rtl = is_rtl_text(items.iter().map(|i| &i.text));
|
let rtl = is_rtl_text(items.iter().map(|i| &i.text));
|
||||||
if rtl {
|
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 {
|
} 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;
|
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 {
|
if sorted[sorted.len() - 1] < 0.40 || sorted[0] < 0.40 {
|
||||||
return DEFAULT;
|
return DEFAULT;
|
||||||
@@ -478,7 +478,7 @@ fn compute_single_char_join_threshold(items: &[TextItem]) -> f32 {
|
|||||||
return DEFAULT;
|
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
|
// If all gaps are tight (max < 0.40), use default — normal PDF
|
||||||
let max_ratio = ratios[ratios.len() - 1];
|
let max_ratio = ratios[ratios.len() - 1];
|
||||||
|
|||||||
Reference in New Issue
Block a user