Repository navigation
feat(pdf): read PDF files, including two-column papers (OXD-060) - #13
Merged
Merged
Conversation
`oxidiris paper.pdf` now works the same way `oxidiris paper.md` does. Detection is by file header rather than extension, so `cat paper.pdf | oxidiris -` works and a mis-named file still reads. The spike overturned its own premise. OXD-060 assumed shelling out to Poppler's `pdftotext -layout` would be the pragmatic answer; measured on five real two-column papers, the pure-Rust `pdf-extract` beat it on both reading order and speed, so there is no external dependency and no subprocess. Where the two extractors disagreed most (Omega, 0.699 ordered overlap) it was Poppler that was wrong, dropping figure labels into the middle of the abstract. Measurements and the rejected alternatives are in ADR 003. Everything PDF lives in a new `oxidiris-pdf` crate. `oxidiris-core` is untouched and still compiles for wasm32, which is the reason the split exists at all. Extracted text is tidied before the reader sees it: ligatures expanded, hyphenated line breaks welded back into words, isolated short numbers dropped. That last rule was written for running page numbers; measuring the corpus showed it mostly catches chart axis labels instead, and the code now says so. Failures explain themselves — encrypted, damaged, or a scan with no text layer. `pdf-extract` panics rather than returning `Err` on some malformed files, so the panic is caught and converted (spec §4.4). Known gaps, tracked on OXD-061: figure and table text still lands in the body stream (Poppler has the same defect), a PDF yields no heading structure, and a compound breaking at its own hyphen comes back welded shut. `deny.toml` now ignores RUSTSEC-2026-0192 (`ttf-parser` unmaintained), reached through lopdf -> pdf-extract. The advisory says no upgrade exists; the entry names it and says to delete it when lopdf moves to skrifa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0189gxRgTxfvzjNYezCfS2iR
The windows-latest job failed on this branch while ubuntu and macos passed. The fixtures are almost all ASCII, so git's own heuristic reads them as text and rewrote their line endings on checkout. One added byte per line shifts every offset in the cross-reference table, and the file stops parsing — reported as `InvalidTrailer`, on a file nobody edited. Reproduced on Linux by replacing LF with CRLF in `pdf_typography.pdf`: same error, exactly. `*.pdf binary` in `.gitattributes` is the fix, and it is the same trap `*.snap text eol=lf` was already there to avoid (OXD-036). The regression test is the other half. Without it the next fixture to be mangled reports a trailer problem, which points nowhere near the cause; now it says which file was rewritten and which `.gitattributes` line prevents it. Verified by CRLF-ifying a fixture and watching the guard fire before restoring it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0189gxRgTxfvzjNYezCfS2iR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes
OXD-060. Re-scopesOXD-061from XL to M.oxidiris paper.pdfnow works the same wayoxidiris paper.mddoes. Detection is by file headerrather than extension, so
cat paper.pdf | oxidiris -works and a mis-named file still reads.The spike overturned its own premise
OXD-060was written expecting to shell out to Poppler'spdftotext -layout. Measured on fivereal two-column papers — Borg, Omega, MapReduce, Bigtable, Spanner — the pure-Rust
pdf-extractwon on both axes, so there is no external dependency and no subprocess.
Reading order. There is no ground truth to diff against, so the two extractors were compared
to each other (word sequences, NFKC, de-hyphenated, matched in order by LCS ratio):
Omega is where they disagree, and Poppler is the one that is wrong: it lifts the Figure 1
labels (
Monolithic,Two-level,scheduling logic) out of the figure and drops them into themiddle of the abstract.
pdf-extractkeeps the body running clean.-layoutwas rejectedoutright — it puts the two columns side by side on every line.
Speed, best of three, release build.
oxidirisruns the whole pipeline (extract, tidy, parse,tokenize, print);
pdftotextonly extracts:Full write-up, including why
lopdfon its own was dropped before benchmarking, inADR 003.
Shape of the change
New crate
oxidiris-pdf, holding the entire PDF dependency tree.oxidiris-coreis untouched andstill compiles for
wasm32-unknown-unknown, which is the whole reason for the split. Output isplain text rather than a
Document: a PDF has no reliable block structure, and the plain-textparser already rejoins hard-wrapped lines.
Text is tidied before the reader sees it — ligatures expanded, hyphenated line breaks welded,
isolated short numbers dropped. Every step is a pure
&str -> Stringfunction, tested without aPDF in sight.
One rule was renamed after measurement. It was written to remove running page numbers.
Measuring the corpus showed the isolated numeric lines are mostly chart axis tick labels pulled
out of figures; Bigtable produces none at all. The function and its comments now say what it
actually does.
Errors
Encrypted, damaged, and no-text-layer files each explain themselves.
pdf-extractpanics ratherthan returning
Erron some malformed input, so the panic is caught and converted (spec §4.4) —the only place in the project that catches a panic.
Fixtures
testdata/make_pdf_fixtures.pygenerates three small PDFs with no third-party library. The realpapers are not committed — they are under publisher copyright and a fixture has to be
redistributable.
Known gaps, tracked on OXD-061
after the first-page footer. Poppler has the same defect, so there is no tool to defer to.
over-/commitment) comes back welded shut.Separating those needs a dictionary.
hand was judged not worth the cost. Left unticked in the acceptance list rather than claimed.
New advisory ignore
deny.tomlnow ignores RUSTSEC-2026-0192 (ttf-parserunmaintained), reached throughlopdf->pdf-extract. The advisory states no upgrade exists —skrifais a change lopdf has tomake upstream. It is a maintenance notice, not a vulnerability. The entry names it and says to
delete it the moment lopdf switches; also recorded as carried debt on
OXD-061.Verification
make checkgreen: 291 tests, fmt, clippy, wasm build.cargo deny check: advisories, bans, licenses, sources all ok.clean messages.
make msrvnot run — the 1.88 toolchain is not installed locally. The highest MSRV in thenew dependency tree is 1.85, so this should pass, but CI is what confirms it.
🤖 Generated with Claude Code
https://claude.ai/code/session_0189gxRgTxfvzjNYezCfS2iR