Commit Graph
47 Commits
Author SHA1 Message Date
Jase-Omeileo West 1c32e4bd69 fix(deps): bump lopdf to 0.42.0 for nesting-depth DoS (#198)
Bumps lopdf from 0.41.0 to 0.42.0 to fix RUSTSEC-2026-0187, preventing deeply nested PDFs from causing an unrecoverable stack-overflow process abort.
2026-08-03 11:08:34 -07:00
Abimael Martell 5e8f1570f6 chore(crate): bump pdf-inspector to 0.1.7 (#187) 2026-07-31 16:25:57 -06:00
Abimael Martell 5b287341a0 feat(wasm): add browser bindings (#180)
* feat(wasm): add browser bindings

* fix(wasm): address review feedback

* fix(wasm): preserve numeric plain text

* chore(wasm): prepare 0.1.2 release
2026-07-17 14:09:43 -07:00
Abimael MartellandClaude Fable 5 673fbe998f docs(registries): add Features and benchmark to crates.io/PyPI/npm pages (#155)
Concise Features list and the opendataloader-bench comparison table on
each registry readme, adapted per ecosystem. Bump all three versions
(crate 0.1.6, python 0.2.5, npm 1.11.1) to republish the pages.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:32:29 -07:00
Abimael MartellandClaude Fable 5 cffe253d1d fix(pypi): slim sdist inherited from crate allowlist; keep type stub (0.2.4) (#153)
The 0.2.3 sdist was 10.09 MiB (packaged tests/fixtures). maturin derives
the sdist file list from Cargo's include allowlist, so it's now 1.35 MiB
— but the allowlist dropped pdf_inspector.pyi, which would strip type
hints from wheels built from the sdist. Add it back and bump to 0.2.4.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 19:33:39 -07:00
Abimael MartellandClaude Fable 5 2cd1cf1b23 fix(crate): allowlist package contents to fit crates.io size cap (#152)
cargo publish of 0.1.5 failed with 413: the crate packaged everything
(260 files, 10.1MiB compressed) and tests/fixtures alone is 10.2MB.
Add an explicit include list (src, external/bcmaps which tounicode.rs
loads at runtime, readme, license) — 1.3MiB compressed.

Also add a workflow_dispatch fallback to publish-crate.yml so a failed
publish can be retried without a version bump (0.1.5 is already on
main, so a re-push won't register as a version change).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 19:24:40 -07:00
Abimael MartellandClaude Fable 5 2c97f4979e docs(crate): Rust-specific readme for crates.io (0.1.5) (#151)
crates.io showed the repo README, which leads with Python/Node quick
starts and repo-relative links. Point the crate readme at
docs/rust-api.md, refreshed with an intro, crates.io install, and CLI
install instructions. Bump to 0.1.5 to republish.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 19:15:42 -07:00
Abimael MartellandClaude Fable 5 3ed30d01e1 ci: add PyPI trusted publishing (abi3 wheels, v0.2.1) (#123)
* ci: add PyPI trusted publishing, abi3 wheels, bump to 0.2.1

Adds publish-pypi.yml mirroring the npm/crates.io pattern: triggers on
Cargo.toml version change, builds wheels for 5 platforms via maturin,
publishes with OIDC trusted publishing (no tokens). workflow_dispatch
serves as a manual fallback for the first run after the PyPI project
transfer.

Enables pyo3 abi3-py38 so one wheel per platform covers CPython >=3.8
(previous manual uploads were cp312-only). Bumps version to 0.2.1 since
PyPI already has 0.2.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(pypi): guard dispatch to main, support partial-release repair

Review feedback: trusted publishing doesn't match on branch, so
workflow_dispatch needed an explicit main-ref guard. Manual dispatch now
always rebuilds and publishes with skip-existing so a release that
failed after uploading only some wheels can be completed by re-running.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(pypi): version PyPI package from pyproject.toml, not Cargo.toml

Decouple the Python package version from the crate version, matching
how npm publishing keys off napi/package.json: bump [project] version
in pyproject.toml manually and CI publishes on merge. Reverts the
Cargo.toml bump so this PR no longer triggers a crates.io release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: remove accidentally committed uv.lock

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): tolerate missing version key in parent pyproject.toml

The first merge of this workflow has a parent commit where pyproject.toml
still used dynamic = ["version"], so the old-version read would KeyError
and the auto-publish would never fire. Treat a missing key as a change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:52:13 -07:00
Abimael MartellandClaude Fable 5 6e5e5849c8 Detect substitution-cipher garbled text from broken ToUnicode CMaps (#120)
* fix(lib): detect substitution-cipher garbled text from broken ToUnicode CMaps

ParseBench text_simple__att10k.pdf (issue #118) ships Type0/Identity-H
fonts whose ToUnicode CMaps are authored garbled: every bfrange maps with
a wrong constant delta, so text extracts as pure-ASCII ciphertext
("Certificate" -> "8VceZWZTReV"). The embedded subset font has no cmap
table and no glyph names, so no decode source can recover the real text
(poppler and mupdf emit the same ciphertext). The only correct behavior
is to flag the page for OCR instead of serving the garbage silently --
but the text is 100% printable ASCII with word-like tokens, so it slipped
past is_garbage_text and detect_encoding_issues.

Add CipherGarbleStats, a letter-statistics discriminator that flags a
Latin-dominant sample (>=200 ASCII letters) when vowels are starved
(<=30% of letters) AND either:
- lowercase->uppercase transitions inside words exceed 10% of letter
  bigrams (a shifted lowercase alphabet straddles the ASCII uppercase
  block), or
- the letter histogram's cosine similarity against English letter
  frequencies drops below 0.60 (catches shifts that stay within case
  blocks).

Wired into analyze_text_quality (per-page, item-level) and
detect_encoding_issues (markdown-level), so extract_pages_markdown
reports needs_ocr + suspected_garbled_text and suppresses the garbage.

Thresholds validated against the 380-document pdf-evals snapshot corpus
(Swedish, Finnish, Turkish, German, romaji, schematics, all-caps and
camelCase-heavy docs): zero false positives, and byte-identical eval
output vs main. Garbled page measures vowel ratio 0.245 / case-shift
rate 0.225 / cosine 0.532; closest legitimate document on each axis is
0.264 / 0.021 / 0.801.

Fixes #118

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump pdf-inspector to 0.1.4, npm package to 1.9.11

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lib): exempt uniform-case structured content from cipher detection

Address PR review (cubic P2): the frequency branch (english_cosine < 0.60)
fired on any Latin-dominant, low-vowel letter distribution unlike English,
so non-linguistic ASCII — DNA/protein sequences, ticker symbols, hex dumps —
could be suppressed and routed to OCR despite not being garbled. Measured:
DNA cosine 0.428 / vowel ratio 0.260, protein 0.738, tickers 0.747, hex
0.549 — all would have flagged.

Add a mixed-case guard to looks_garbled: garbled English is a permutation of
natural language and carries sentence capitalization (block-straddling shifts
invert the ratio — att10k is 60% uppercase; in-case Caesar shifts preserve it
at ~3%), so both keep some of each case. The exempted structured content is
uniform case (all upper or all lower). Requiring the minority case to be >=1%
of ASCII letters exempts single-case sequences while preserving both garble
signals, including the in-case-shift scenario the frequency branch exists for.

Strictly tightens the detector: it can only remove flags, so the eval corpus
stays at zero false positives (verified byte-identical to a baseline main
binary across all 185 PDFs) and att10k remains flagged. Adds regression tests
for DNA, protein, tickers, and an in-case Caesar shift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lib): make cipher detection case-agnostic via sorted-histogram shape

Address PR review follow-up: the mixed-case guard from the previous commit
returned before the vowel/frequency checks, creating a blind spot — a
uniform-case (all-lower or all-upper) substitution cipher is a plausible
broken-CMap output and would bypass OCR entirely.

Replace the case proxy with the actual invariant. A substitution cipher is
a bijection over a real language's alphabet, so it preserves the frequency
SHAPE (the sorted histogram) while scrambling letter POSITIONS (the unsorted
histogram). Signal 2 now flags when english_cosine < 0.60 (positions unlike
English) AND english_shape_cosine >= 0.90 (profile is still English-shaped).
This is independent of case, so it catches all-lower, all-upper, and
case-straddling shifts alike.

The exempted structured content fails one half: DNA/hex dumps have too steep
a profile (shape cosine 0.74 / 0.81 < 0.90), while protein sequences, ticker
symbols and base64 are not sufficiently unlike English in position (unsorted
cosine 0.74 / 0.75 / 0.77 >= 0.60). All stay out of OCR.

Still strictly corpus-safe: every real Latin document scores unsorted cosine
>= 0.70 (min 0.80), far above the 0.60 gate, so none can reach Signal 2.
Re-verified byte-identical to a baseline main binary across all 185 eval
PDFs; att10k remains flagged. Drops the now-unused case counters and adds
all-lowercase / all-uppercase shifted-prose regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: source Python package version from Cargo.toml via maturin

Address PR review (cubic P2): pyproject.toml pinned version = "0.1.0",
which overrides Cargo.toml, so a maturin build produced a 0.1.0 Python
artifact regardless of the crate version (it had drifted since the PyO3
bindings were added). Switch to dynamic = ["version"] so maturin sources
the version from Cargo.toml [package] version and the two can no longer
diverge. No workflow auto-publishes the Python package, so this is metadata
hygiene rather than a release-path fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:44:32 -07:00
Abimael Martell 1a5ba6f1e9 feat(api): expose OCR reason signal (#110) 2026-06-23 15:51:55 -07:00
Abimael Martell 57b98c6a5d fix(extractor): flag garbled text spans for OCR (#108)
* fix(extractor): flag garbled text spans for OCR

* fix(extractor): apply text quality checks to regions

* chore(napi): bump npm package version
2026-06-23 13:12:10 -07:00
Abimael Martell f25808e0a7 fix(extractor): restore CID font state for Chinese text (#106)
* fix Chinese CID text decoding

* bump package versions
2026-06-20 19:43:27 -06:00
Abimael Martell 85890648c9 chore: use crates.io lopdf (#101) 2026-06-05 10:50:05 -07:00
Abimael MartellandClaude Opus 4.6 b7ec80097b chore: update lopdf to latest commit
7a05512d831415b1f2b1ce522391d6beab8a1284

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:27:32 -07:00
Abimael MartellandClaude Opus 4.6 57673ebb69 perf: skip expensive TrueType fallback in extract_text_in_regions
FontCMaps::from_doc can spend 4.5+ seconds decompressing and parsing
large embedded TrueType fonts for CID fonts with sparse ToUnicode
CMaps. For extract_text_in_regions (hybrid OCR pipeline), this is
unnecessary — fonts that can't be decoded cheaply will produce
empty/garbage text, triggering needs_ocr=true and GPU OCR fallback.

Changes:
- Add FontCMaps::from_doc_pages_fast() that skips TrueType font
  fallback parsing (build_fallback_cmap_for_type0) and Identity-H/V
  second pass entirely
- Add FontCMaps::from_doc_pages() for filtered page sets
- extract_text_in_regions_mem uses fast mode
- Restructure fallback chain: try cheap fallbacks first, only attempt
  expensive TrueType parsing when needed and not in fast mode

Benchmark on nihms-1771367.pdf (19-page chemistry paper):
- FontCMaps fast:  201µs
- FontCMaps slow:  4.47s
- 22,000x speedup on font parsing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:13:11 -07:00
Abimael MartellandClaude Opus 4.6 506b2a0c70 unify NAPI and Python binding APIs for consistent surface
Both bindings now expose the same 6 function families: process, detect,
classify, extractText, extractTextWithPositions, and extractTextInRegions.
Bumps PyO3 from 0.22 to 0.25 for Python 3.14 support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 11:48:18 -07:00
Abimael Martell 5f829f5258 Merge branch 'main' into feat/python-bindings 2026-04-02 11:35:18 -07:00
Abimael MartellandClaude Opus 4.6 ad10f670bf chore: migrate from deprecated load_mem_with_password to load_mem_with_options
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 17:40:58 -07:00
Abimael MartellandClaude Opus 4.6 7f2995ad0c chore: update lopdf to firecrawl fork with uncompressed stream fix
Points to firecrawl/lopdf@edf6279 which fixes decompressed_content()
for streams without a /Filter entry. Our xobjects.rs workaround remains
as a defensive fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 21:15:36 -07:00
Abimael MartellandClaude Opus 4.6 d9c2143c32 feat: tagged PDF structure tree support (#4)
* feat: tagged PDF structure tree support for semantic markdown generation

Parse /StructTreeRoot from tagged PDFs and use semantic roles (H1-H6, P,
LI, BlockQuote, Code, Caption) to improve markdown output. Structure tree
headings add to font-size heuristics without suppressing them. Coverage
threshold (≥50%) ensures only properly tagged PDFs activate this path.

Phase 1: Parse structure tree with role maps, MCID collection, flattening
Phase 2: Capture MCIDs from BMC/BDC operators, tag TextItems
Phase 3: Structure-aware markdown generation in convert loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: accumulate consecutive code lines into single fenced block

Per-line code fencing produced broken markdown for multi-line code
blocks (separate ``` open/close per line). Unify struct-tree Code
role and font-based monospace detection into a single is_code_line
check with in_code_block state for proper accumulation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add tagged PDF fixture with Firecrawl docs content

Synthetic 7-page PDF with rich structure tree exercising H1, H2, H3,
P, Code, LI, Caption, TH, TD roles. Generated via fpdf2 script.
Integration test verifies struct tree parsing and code fence output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove python PDF generator script from repo

Keep the generated fixture PDF but don't track the generator script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle malformed bare-name struct types in tagged PDFs

Some PDF generators (e.g. fpdf2) write /S Code instead of /S /Code
in structure elements. lopdf silently drops these objects since bare
tokens are invalid PDF syntax. Add a pre-processor that scans for
known bare struct type names and prepends / before loading.

Unifies path and memory loading through the same fix pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update lopdf dependency to main branch

The firecrawl/zlib-checksum-encrypted branch was merged and deleted.
Point to main which includes all previously merged fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: switch lopdf to upstream repo pinned at 845cd3d

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:50:24 -07:00
Jacques DumoraandClaude Opus 4.6 0bf5463a1e feat: add Python bindings via PyO3
Expose the pdf-inspector Rust library as a Python package using PyO3 + maturin.
Python users can now `pip install` and use `import pdf_inspector` for PDF
classification, text extraction, and markdown conversion with native Rust speed.

Adds:
- src/python.rs: PyO3 bindings (process_pdf, detect_pdf, extract_text, etc.)
- pyproject.toml: maturin build configuration
- pdf_inspector.pyi: type stubs for IDE support
- tests/test_python.py: 21 pytest tests covering all Python API functions
- examples/basic_usage.py: example script demonstrating all features
- Updated README with Python quick start and API reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:38:53 +01:00
Abimael MartellandClaude Opus 4.6 ba1fbcbff6 fix(arabic): NFKC normalize presentation forms and reverse visual-order RTL text
Arabic PDFs using ToUnicode maps to presentation forms (U+FB50-FDFF,
U+FE70-FEFE) produced unreadable output. Apply NFKC normalization to
convert back to base Arabic (U+0600-06FF), then reverse character order
within items that had presentation forms (visual→logical order).

Improves Arabic PDF word overlap with Mistral from ~4.5% to ~78.6%.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:20:34 -07:00
Abimael MartellandClaude Opus 4.6 cc92e554d0 fix: use lopdf fork with zlib checksum fallback for encrypted PDFs
Points to firecrawl/lopdf branch that falls back to raw deflate when
zlib adler32 checksum is corrupted after RC4 decryption. Fixes 5
encrypted BrokerCheck PDFs that were misclassified as scanned.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:54:12 -07:00
Abimael MartellandClaude Opus 4.6 f2869fff30 chore(rust): revert lopdf to upstream main
The firecrawl/fix-cmap-optional-slash branch caused a regression on
the Italian PDF (garbled CMap-encoded text). The EPEVER fix was a
wrong diagnosis — the PDF extracts correctly on upstream main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:55:03 -08:00
Abimael MartellandClaude Opus 4.6 2a08386d62 chore(rust): update lopdf to fix-cmap-optional-slash branch
Switches to a new lopdf branch that includes the CMap parser fix for
PDFs with uncompressed ToUnicode streams that omit the leading slash
on metadata keys (CIDSystemInfo, CMapName, CMapType).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:10:30 -08:00
Abimael MartellandClaude Opus 4.6 5ab92e03e3 feat(tables): detect tables from clip-path rects via merged-cluster fallback
Extract axis-aligned rectangles from W/W* clip operators in content
streams — many PDFs define table cells as clipping paths instead of
stroked rects. Add merged-cluster fallback in detect_tables_from_rects()
that merges all cluster rects when per-cluster detection fails or only
produces narrow false-positives (≤3 columns). Uses rect Y-edges for
rows and text X-clustering for columns.

Also updates lopdf to firecrawl fork (fix-leading-whitespace branch).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:12:26 -08:00
Abimael Martell ead4030f31 chore(rust): update lopdf to latest upstream (dc2887a)
Picks up ObjStm xref conflict fix and clippy cleanup.
2026-03-05 11:40:16 -08:00
Abimael Martell 8f652b81a9 update lopdf 2026-03-02 10:12:59 -08:00
Abimael MartellandClaude Opus 4.6 0aa4e0a9bc Point lopdf at firecrawl/lopdf firecrawl-fixes branch
Includes %%EOF boundary fix and inline image skip fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:29:54 -08:00
Abimael MartellandClaude Opus 4.6 3a1ada1c6b fix(reader): point lopdf at fork with %%EOF boundary fix (abimaelmartell/lopdf@88f7d9e)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:07:06 -08:00
Abimael MartellandClaude Opus 4.6 4ed73f7c3b Point lopdf at upstream repo (J-F-Liu/lopdf@ae7c584)
The R6 encryption fix has been merged upstream, so switch from the
fork back to the canonical repository.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:05:38 -08:00
Abimael MartellandClaude Opus 4.6 93c5c21384 Point lopdf at fork with R6 padded /O /U fix
Use abimaelmartell/lopdf firecrawl/fix-r6-encryption branch which
accepts /O and /U values longer than 48 bytes in V5/R6 encrypted PDFs.
This unblocks 4 encrypted PDFs in the bench suite that were previously
rejected with InvalidHashLength.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:28:02 -08:00
Abimael MartellandClaude Opus 4.6 421690c7c4 fix(fonts): Fix decoding for custom encodings and CID Korean fonts
Merge CMap and Differences encoding at the byte level for single-byte
fonts, fixing garbled output when partial ToUnicode CMaps caused Latin-1
fallback to block the Differences path. Add Adobe-Korea1 CID-to-Unicode
predefined mapping for Identity-H CID fonts without ToUnicode streams,
and support TrueType cmap extraction via ttf-parser for embedded fonts.
Also handle suffixed glyph names (e.g. zero.tf, a.ss01) per Adobe spec.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 15:32:11 -08:00
Abimael MartellandClaude Opus 4.6 42c1d639f2 chore: Pin lopdf to commit 0387137b
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:35:03 -08:00
Abimael MartellandClaude Opus 4.6 a00ce46ab0 refactor(tounicode): Replace raw byte scanning with lopdf document model for CMap extraction
Eliminates double-parsing of PDFs by using the lopdf document model exclusively
for ToUnicode CMap extraction. The old raw byte scanner parsed PDFs separately
from lopdf and only handled FlateDecode, while lopdf handles FlateDecode + LZW +
ASCII85. The new approach walks page fonts and Form XObject fonts via the document
API, yielding ~7x speedup on text-heavy PDFs and ~1.2x overall.

- Add FontCMaps::from_doc() with recursive Form XObject font walking
- Remove ~270 lines of raw byte scanning code (from_pdf_bytes, extract_stream_from_raw_pdf, etc.)
- Remove flate2 dependency (lopdf handles decompression internally)
- Remove dead CMap lookup fallback branches (by_name, get_with_obj, base_font_name)
- Remove unused font_base_names parameter from extract_text_from_operand
- Skip U+FFFD replacement characters in CMap decode (PDF notdef glyph markers)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:33:32 -08:00
Abimael Martell 64d2438e2e chore(debug): Add debugging binary for position issues 2026-02-18 10:38:34 -08:00
Abimael Martell 5401354f2e add logging 2026-02-18 10:34:32 -08:00
Abimael MartellandClaude Opus 4.6 350bbc0086 feat(cli): Add --pages and --select-pages flags, fix CMap matching and whitespace tracking
- Add --pages flag to insert <!-- Page N --> markers between pages
- Add --select-pages flag to process only specific pages (e.g. 1,3,5-10)
- Wire MarkdownOptions and page filter through process_pdf_with_config
- Fix fuzzy CMap matching that caused Cyrillic substitution on Latin text
- Fix whitespace Tj items not advancing text matrix (broke gap detection)
- Tighten single-char fragment join threshold from 0.25 to 0.20
- Gitignore debug/diagnostic binaries and remove tracked ones

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 08:58:58 -08:00
Abimael MartellandClaude Opus 4.6 ec15fe0188 chore: Disable autobins to prevent untracked debug bins from affecting builds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 15:56:01 -08:00
Abimael MartellandClaude Opus 4.6 f77cbe9808 fix(markdown): Render image-only pages in order and collapse dot leaders
Image-only pages (e.g. scanned cover pages) were appended at the end of
output instead of appearing in page order. Now flushes tables/images for
intermediate pages with no text lines during page transitions.

Also collapses dot-leader runs (4+ dots) into " ... " for cleaner TOC
rendering, and adds debug_pages binary for inspecting per-page items.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 18:36:41 -08:00
Abimael MartellandClaude Opus 4.6 56cfd895b5 fix(tables): Prevent paragraph text from being falsely detected as tables
Add pairwise cross-row column alignment check to find_table_regions_strict().
Real tables have fixed column X positions that repeat across rows (score ~1.0),
while paragraph text has varying word positions (score ~0.3-0.4). Regions with
average pairwise alignment score < 0.5 are now rejected.

Also remove the num_cols >= 5 content-check bypass for BodyFont mode, requiring
body-font tables to always have ≥30% data-like cell content.

Fixes false table detection across 9+ PDFs including AI_Cultural_Collections
(-730 false lines), 131212888 (-740), Data-Processing-Agreement (-108),
CEP_DN_Interest_Rates (-93). No regressions on legitimate tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:10:10 -08:00
Abimael Martell 9078c7bd14 improve spacing detection 2026-02-11 14:12:16 -08:00
Abimael Martell ec96311a65 implement ToUnicode CMap support for proper text extraction from PDFs with custom font encodings 2026-02-09 09:46:07 -08:00
Abimael Martell c2c528ff23 url formatting, page no detection, additional cleanup 2026-02-07 14:12:19 -08:00
Abimael Martell 0ef7b2adfc add ci, plus new package 2026-02-06 21:39:21 -08:00
Abimael Martell ee09e96cc4 add tests and readme 2026-02-06 17:58:27 -08:00
Abimael MartellandClaude Opus 4.5 135ce518c1 Initial commit: Rust PDF-to-Markdown library
- Smart PDF type detection (text vs scanned) without full document load
- Text extraction using lopdf directly
- Markdown conversion with header/list/code detection
- CLI tools: detect-pdf, pdf2md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:51:41 -08:00