Skip to main content

SeenRead

DOI

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 gets UNKNOWN, not SAME. 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 as UNKNOWN - 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 were UNKNOWN. 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.

  • 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)

Source distribution for seenread 0.1.1
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)

Table of built distributions (wheels) for seenread 0.1.1
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page