SeenRead
Prove, word by word and without OCR, whether the text a machine extracts from a PDF is the text a human sees.
PDFs can show people one text while giving machines another: a font whose character table says "9" where it draws "1", an edited text table (ToUnicode), white or invisible text, text outside the page. Copy-paste, search engines, screen readers and AI pipelines (RAG, CV screening, AI-assisted reviewing) all read the machine version. Many PDFs in Indian scripts are split the same way by accident: the page is right, the extracted text is garbled.
SeenRead never trusts the PDF's own font tables. It identifies every drawn glyph by its exact outline and advance in a trusted font you supply, then accepts a text for a word only if the shaping engine (HarfBuzz) lays that text out with the trusted font into the same glyphs, arranged identically inside each cluster, in the same order - so the two render identically by construction. For each word it reports:
SAME- the extracted text is what is drawn (proven)DIFFERENT- the extracted text is not what is drawn; the text a human sees is given (proven the same way)UNKNOWN- nothing could be proven (a glyph matches no trusted font, or no candidate lays out identically)
and lists text that machines read but humans cannot see (invisible render mode, transparent, under 1 pt, white, outside the page).
Results (synthetic documents, fresh blind sets, pre-registered)
Each blind set: 120 newly generated one-page documents (30 each in English, Hindi, Bengali, Tamil; about 40% clean controls, the rest with font-remap or ToUnicode lies and/or hidden text), generated with a new random seed after the code was frozen. Score = share of words whose human-visible text SeenRead gives exactly (aligned with the true visible text).
| Blind set | English | Hindi | Bengali | Tamil | Harmful errors | UNKNOWN | Hidden text found | False hidden alarms |
|---|---|---|---|---|---|---|---|---|
| seed 5303 (v2.6) | 99.88% | 100% | 100% | 100% | 1 of 4,386 (a missed lie) | 0 | 35/35 | 0/48 controls |
| seed 5304 (v2.7, released) | 100% | 99.93% | 100% | 100% | 0 of 4,317 | 1 | 28/28 | 0/53 controls |
The one miss in seed 5303 (Arial's small-caps lookups joined "o" and "O") was fixed in v2.7 and re-tested on seed 5304.
Two earlier blind sets with older versions scored lower and led to the fixes; all runs are in bench/results/.
Compared with OCR on the same files (seed 5303; Tesseract 5, better of two page modes per script):
| English | Hindi | Bengali | Tamil | |
|---|---|---|---|---|
| SeenRead - visible words exact | 99.88% | 100% | 100% | 100% |
| Tesseract OCR - visible words exact | 98.24% | 96.15% | 94.03% | 91.84% |
| Plain text extraction (PyMuPDF) | 91.53% | 58.19% | 24.59% | 26.79% |
| OCR-vs-extraction detector: lied/garbled words caught | 79.2% | 94.9% | 93.5% | 89.3% |
| OCR-vs-extraction detector: false alarms on clean words | 0.00% | 2.91% | 4.23% | 1.23% |
These are clean computer-generated pages - the best case for OCR. SeenRead caught all but one split word with no false alarms; an OCR comparison misses words where OCR "corrects" a swapped letter back to a dictionary word, and raises false alarms where OCR misreads.
Limits (read before relying on it)
- Tested on synthetic documents (made with PyMuPDF's writers). Real-world PDFs from other producers, mixed layouts and unknown fonts: only a first, partial check so far (next point).
- Needs the trusted font. A glyph that matches no trusted font gives
UNKNOWN(never a guess). An attacker who draws new glyph shapes getsUNKNOWN, notSAME. But text in an embedded font that matches no trusted font at all (under half its glyphs) is currently left out of the report, not counted asUNKNOWN- compare the word counts with plain extraction. In a first, partial real-world check (27 genuine web PDFs, mostly Indian-language; Windows system fonts as trusted fonts), the report covered about 42% of the words plain extraction finds, and about half of those wereUNKNOWN. One PDF with a malformed font table stopped the run with an error. Both are being fixed. - Not handled yet: coloured backgrounds (white text is assumed invisible on a white page), clipping, objects covering text, Type 1 / CFF fonts (e.g. many LaTeX PDFs).
- Speed: seconds per page; lookup tables are built once per font and script.
- Built on the Unshape decoder (same author) for Indian scripts.
Use
pip install seenread
seenread file.pdf --trusted Arial.ttf Lohit-Devanagari.ttf # human-readable report
seenread file.pdf --trusted ... --json # per-word verdicts, visible text, hidden text
Reproduce the benchmark: bench/generator.py (set SR_SEED, font paths via SR_ARIAL, SR_LOHIT, SR_NIRMALA),
bench/fastrun.py seenread.core <folder>, bench/summarize53d.py <folder>/results.jsonl. Fonts are not included.
Related work
- Split-view PDFs in document-to-LLM pipelines: Liu & Ming, arXiv 2606.15020 (IEEE S&P 2027) - 25 render/extract gaps, static screening; proposes dual-view consistency as future work.
- Font remapping attacks on LLM document readers: arXiv 2505.16957; Luo et al., IACR ePrint 2026/278 (OCR-based pipelines robust; recommends rendering + computer vision).
- Detection: CrackedPDFs (arXiv 2607.19396, learned hybrid detector); rule-based scanners (hidden-text-detector, PDF-Prompt-Injection-Toolkit); commercial scanners with ToUnicode heuristics and OCR comparison.
- We found no tool that proves the human-visible text from glyph identities and shaping layout without OCR; pointers to earlier work are welcome.
Licence and commercial use
AGPL-3.0-or-later, or a commercial licence for closed-source products, internal pipelines and hosted services -
see COMMERCIAL.md. Open for talks about commercial licences, integration into document-AI, RAG and
LLM-ingestion pipelines, security reviews and partnerships: Novain Shibu · georgeshibu380@gmail.com ·
github.com/novain-shibu · linkedin.com/in/novain-shibu.
bench/text/ and bench/results/ contain Wikipedia text (CC BY-SA 4.0). Parts of this work were produced with AI
assistance (Claude, Anthropic), directed and reviewed by the author. Cite via CITATION.cff.
Metadata
Release files for seenread 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| seenread-0.1.1.tar.gz | 26.7 kB | With provenance | Details for seenread-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| seenread-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for seenread-0.1.1-py3-none-any.whl |
Total release size: 51.8 kB
Release files / seenread-0.1.1.tar.gz
| Download URL | seenread-0.1.1.tar.gz |
|---|---|
| Size | 26.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
98d4f46c5f3d6f5212b5ae8fc2ecc61cc070294d196e539445373e69c492ff4a
|
|
BLAKE2b-256 checksum How to use checksums |
f6e8b3efd8f1968c861e8d7bdfef4575aace19da87046b0d98e0986321316006
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logRelease files / seenread-0.1.1-py3-none-any.whl
| Download URL | seenread-0.1.1-py3-none-any.whl |
|---|---|
| Size | 25.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
df509c06ffe73b77fe66c1d068b9792a48f37104bf049c38b19eeca53d5a58bb
|
|
BLAKE2b-256 checksum How to use checksums |
9031c6e7567649ff713f12b502c8c6a44f61309d6427a50ff3a4e3cc1a2ac380
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logSecurity for seenread 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for seenread-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.