pdf-reader
Turn a PDF into clean, position-aware Markdown that an AI model can read
without misreading the layout. Backed by
pdf-inspector (Firecrawl's Rust
engine), which handles columns, tables, headings, lists, and code blocks, and
tells you exactly which pages (if any) need OCR.
When to use
- The user shares or points at a PDF and asks you to read, summarize, extract,
or quote from it — and your model cannot read PDFs natively.
- The PDF contains tables, multi-column layouts, or dense formatting where a
naive text dump would corrupt the meaning.
- You need to cite specific pages of a document.
Do NOT use for image-only/scanned PDFs expecting embedded text: pdf-inspector
is not an OCR engine. It will classify them as scanned/image_based and
flag every page — that is the signal to route to OCR (--ocr-cmd) instead.
Prerequisites
- Python 3 (≥ 3.8) with
pip install pdf-inspector (prebuilt wheels for
macOS Intel/ARM, Linux x86_64/aarch64, Windows x64 — no Rust toolchain).
- The core CLI is
scripts/pdf_read.py — the scripts/ folder sits NEXT
TO this SKILL.md. Resolve it relative to this file:
<this-skill-folder>/scripts/pdf_read.py (and
<this-skill-folder>/scripts/install.py for one-shot installation).
Reference docs live in references/ (api-guide.md, rules.md).
If the folder is missing, clone or copy it from the skill repo, or follow
README.md step-by-step installation.
Procedure
PDF_READ below stands for <this-skill-folder>/scripts/pdf_read.py —
the folder next to this SKILL.md.
- Locate the wrapper — resolve
scripts/pdf_read.py next to this
file. If it is not present, tell the user it must be installed with the
skill (README.md has the steps).
- Classify first (fast, ~10–50 ms):
python3 "$PDF_READ" <document.pdf> --classify
Read the output: type=text_based|scanned|image_based|mixed,
confidence, pages_needing_ocr=[...].
- Convert (default writes
<document>.md + <document>.md.meta.json):python3 "$PDF_READ" <document.pdf>
For a quick answer you may pipe instead:python3 "$PDF_READ" <document.pdf> --stdout --pages 1-10
- Read the Markdown, honoring these rules:
<!-- Page N --> markers = source pages. When you quote or cite, say
which page a passage came from.
| tables are real Markdown tables — read them as tables, with headers
and cells, not as prose.
- Headings are
#/##/### (font-size-based from the original PDF).
<!-- OCR REQUIRED: page N ... --> means page N has no embedded
text: do not guess its content. Either report that page N needs OCR,
or run the OCR hook (step 5).
- OCR fallback for flagged pages — run a local OCR engine per page:
python3 "$PDF_READ" <document.pdf> --ocr-cmd 'tesseract {input} stdout -l eng'
The hook output is spliced into the Markdown at the page's position.
Placeholders ({input} {page} {out} {stem} {outdir}) are
shell-quoted automatically — use them bare, never wrapped in quotes.
- Long documents / small context windows — split into chunks:
python3 "$PDF_READ" <document.pdf> --chunks 8000
Reads <document>.chunk-001.md, .chunk-002.md, ... in order; the
manifest <document>.md.chunks.json lists sizes and page ranges. Do not
feed more than fits the model's context; keep the page markers when
quoting.
- Machine-readable use (pipelines, tools):
python3 "$PDF_READ" <document.pdf> --json --stdout
Markdown → stdout, JSON envelope → stderr. Exit codes: 0 ok (OCR pages
reported, not fatal), 1 error, 2 usage, 3 --fail-on-ocr: pages still
lack reliable text after the OCR hook (nothing is written on exit 3).
Quality checks (do these before answering from the output)
- Spot-check 2–3 headings and 1 table against the original document's known
structure; if the Markdown looks broken (garbled text, missing sections),
mention it to the user instead of silently working from bad data.
- If
pages_needing_ocr is non-empty, say so — never invent content for
those pages.
- For huge PDFs (hundreds of pages), prefer
--pages or --chunks over
reading everything at once.
Troubleshooting
| Symptom |
Cause / fix |
pdf_inspector is not installed |
python3 -m pip install pdf-inspector |
| Exit 1, "password-protected or malformed" |
encrypted PDF — unlock it first |
type=scanned with all pages flagged |
genuinely scanned; run --ocr-cmd or tell the user OCR is needed |
confidence low (< 0.7) on mixed docs |
check pages_needing_ocr and treat flagged pages as unreadable |
| Math looks odd |
equations extract as inline glyphs; preserve them but don't "fix" silently |
1---2name: pdf-reader3description: Convert PDFs to accurate Markdown for AI reading using pdf-inspector (Rust) — smart text/scanned classification, per-page OCR routing, page markers, and context-size chunking. Cross-platform (macOS/Linux/Windows).4---56# pdf-reader78Turn a PDF into clean, position-aware Markdown that an AI model can read9*without misreading the layout*. Backed by10[pdf-inspector](https://github.com/firecrawl/pdf-inspector) (Firecrawl's Rust11engine), which handles columns, tables, headings, lists, and code blocks, and12tells you exactly which pages (if any) need OCR.1314## When to use1516- The user shares or points at a PDF and asks you to read, summarize, extract,17 or quote from it — and your model cannot read PDFs natively.18- The PDF contains tables, multi-column layouts, or dense formatting where a19 naive text dump would corrupt the meaning.20- You need to cite specific pages of a document.2122Do NOT use for image-only/scanned PDFs expecting embedded text: pdf-inspector23is not an OCR engine. It will classify them as `scanned`/`image_based` and24flag every page — that is the signal to route to OCR (`--ocr-cmd`) instead.2526## Prerequisites2728- Python 3 (≥ 3.8) with `pip install pdf-inspector` (prebuilt wheels for29 macOS Intel/ARM, Linux x86_64/aarch64, Windows x64 — no Rust toolchain).30- The core CLI is `scripts/pdf_read.py` — the `scripts/` folder sits NEXT31 TO this SKILL.md. Resolve it relative to this file:32 `<this-skill-folder>/scripts/pdf_read.py` (and33 `<this-skill-folder>/scripts/install.py` for one-shot installation).34 Reference docs live in `references/` (`api-guide.md`, `rules.md`).35 If the folder is missing, clone or copy it from the skill repo, or follow36 `README.md` step-by-step installation.3738## Procedure3940> `PDF_READ` below stands for `<this-skill-folder>/scripts/pdf_read.py` —41> the folder next to this SKILL.md.42431. **Locate the wrapper** — resolve `scripts/pdf_read.py` next to this44 file. If it is not present, tell the user it must be installed with the45 skill (`README.md` has the steps).462. **Classify first** (fast, ~10–50 ms):47 ```bash48 python3 "$PDF_READ" <document.pdf> --classify49 ```50 Read the output: `type=text_based|scanned|image_based|mixed`,51 `confidence`, `pages_needing_ocr=[...]`.523. **Convert** (default writes `<document>.md` + `<document>.md.meta.json`):53 ```bash54 python3 "$PDF_READ" <document.pdf>55 ```56 For a quick answer you may pipe instead:57 ```bash58 python3 "$PDF_READ" <document.pdf> --stdout --pages 1-1059 ```604. **Read the Markdown**, honoring these rules:61 - `<!-- Page N -->` markers = source pages. When you quote or cite, say62 which page a passage came from.63 - `|` tables are real Markdown tables — read them as tables, with headers64 and cells, not as prose.65 - Headings are `#`/`##`/`###` (font-size-based from the original PDF).66 - `<!-- OCR REQUIRED: page N ... -->` means page N has **no embedded67 text**: do not guess its content. Either report that page N needs OCR,68 or run the OCR hook (step 5).695. **OCR fallback for flagged pages** — run a local OCR engine per page:70 ```bash71 python3 "$PDF_READ" <document.pdf> --ocr-cmd 'tesseract {input} stdout -l eng'72 ```73 The hook output is spliced into the Markdown at the page's position.74 Placeholders (`{input}` `{page}` `{out}` `{stem}` `{outdir}`) are75 **shell-quoted automatically** — use them bare, never wrapped in quotes.766. **Long documents / small context windows** — split into chunks:77 ```bash78 python3 "$PDF_READ" <document.pdf> --chunks 800079 ```80 Reads `<document>.chunk-001.md`, `.chunk-002.md`, ... in order; the81 manifest `<document>.md.chunks.json` lists sizes and page ranges. Do not82 feed more than fits the model's context; keep the page markers when83 quoting.847. **Machine-readable use** (pipelines, tools):85 ```bash86 python3 "$PDF_READ" <document.pdf> --json --stdout87 ```88 Markdown → stdout, JSON envelope → stderr. Exit codes: 0 ok (OCR pages89 reported, not fatal), 1 error, 2 usage, 3 `--fail-on-ocr`: pages still90 lack reliable text after the OCR hook (nothing is written on exit 3).9192## Quality checks (do these before answering from the output)9394- Spot-check 2–3 headings and 1 table against the original document's known95 structure; if the Markdown looks broken (garbled text, missing sections),96 mention it to the user instead of silently working from bad data.97- If `pages_needing_ocr` is non-empty, say so — never invent content for98 those pages.99- For huge PDFs (hundreds of pages), prefer `--pages` or `--chunks` over100 reading everything at once.101102## Troubleshooting103104| Symptom | Cause / fix |105|---|---|106| `pdf_inspector is not installed` | `python3 -m pip install pdf-inspector` |107| Exit 1, "password-protected or malformed" | encrypted PDF — unlock it first |108| `type=scanned` with all pages flagged | genuinely scanned; run `--ocr-cmd` or tell the user OCR is needed |109| `confidence` low (< 0.7) on mixed docs | check `pages_needing_ocr` and treat flagged pages as unreadable |110| Math looks odd | equations extract as inline glyphs; preserve them but don't "fix" silently |