A PDF Parser from the 80s Beats Claude (and They Use It Internally) A developer found that Tesseract, a PDF parser first created by HP Labs in 1985, outperforms Anthropic's Claude AI for parsing scanned PDFs, being 10x faster and more accurate. Anthropic's own internal Claude skills use PyTesseract for OCR on scanned documents, revealing that the company relies on the decades-old tool rather than the AI model's native vision capabilities. A PDF Parser from the 80s Beats Claude And They Use It Internally My journey to parse a CIA PDF , only to discover picking the right tool for the job is still a thing CIA PDF When you give a PDF to an AI, you picture the model reading it. However, for certain kinds of PDFs, the actual model barely sees the text, they only call tools previously dictated by their actual software engineers. I spent days trying to parse a badly scanned CIA PDF don't ask me why, otherwise I'll have to kill you . I tried using the most powerful Anthropic and Google models but I ended up concluding Tesseract a 1985 tool , does a better job and much faster by 10x. Not just that, but I found out that Claude uses PyTesseract in their PDF https://readplace.com/view/github.com/anthropics/skills/blob/main/skills/pdf/SKILL.md parser skill under the hood. They pick that by going through a heuristic, rasterising the most difficult scanned pages and completing missing sentences with hallucinated words when streaming their tokens back. Claude Code AI model is not better than anyone else, they just pay skilled engineers to give them the right tools to call. Claude ships with skills, small Markdown instruction files that tell it how to handle a task. Anthropic published the format and a set of these skills in the open https://readplace.com/view/github.com/anthropics/skills , so you can read them. The reading strategy part is apparently not open source and reads like a runbook that a tired engineer wrote at 2am. Choosing your reading strategy Text-heavy documents reports, articles, books :→ Text extraction is primary. Rasterize only for specific figures or pages where layout matters. Scanned documents pdffonts shows no fonts :→ pdftotext will return nothing - don't run it. Rasterize pages at 150 DPI and Read them visually. For bulk text extraction, use OCR pytesseract after converting pages to images - see REFERENCE.md for a complete example . Slide-deck PDFs exported presentations :→ Every page is primarily visual. Rasterize individual pages on demand. Text extraction gives you bullet-point text but loses all layout. Form-heavy documents :→ Extract form field values programmatically first see below . Rasterize the form page for visual context if needed. Data-heavy documents tables, charts, figures :→ Use pdfplumber for tables. Rasterize pages with charts/figures. Extract text for surrounding narrative. Consider both text AND image for the same page when precision matters. For a PDF, the first move is not the model. It is a diagnostic. The skill runs pdffonts and checks whether the file carries a text layer. Two cases follow, and they get different tools. A born-digital PDF holds its text inside it. pdftotext pulls that text out in milliseconds, with no model involved. The characters were sitting there the whole time. A scanned PDF is pixels. A photocopier flattened the words into an image, and there is nothing to pull out. Here the skill reaches for OCR, and the tool it names is pytesseract . pytesseract is the Python binding to Tesseract. HP Labs started Tesseract in 1985. HP open-sourced it in 2005, and Google kept it going after that. The engine is older than most of the people shipping AI products today. render every page to a PNG at 300 DPI I tried 150dpi, shittier results pdftoppm -r 300 -png input.pdf page OCR one page BAM tesseract --psm 1 --oem 1 -l eng page-01.png - --psm 1 runs orientation and script detection before recognition. --oem 1 selects the LSTM https://readplace.com/view/en.wikipedia.org/wiki/Long short-term memory engine. It needs no API key and makes no network call, so there is no rate limit to negotiate to access a walled garden. This is the part that matters. The skill could have told the model to look at each page and read it off. The model can do that. A scanned page is an image, and the model can describe an image. Reading clean letters off a page is an old, settled problem. Tesseract handled a version of it decades ago. It is fast, it runs locally, and it costs nothing to call. It returns the same text on every run but some garbage here and then. A model pointed at the same page is slower, burns tokens $$ , needs a network round trip, and can quietly invent words that were not there. So a careful engineer reaches for the narrow tool. The skill makes the same call. Claude is not the thing reading your scan. A character engine from the 1980s is. The model does get pointed at a page sometimes, when layout or a chart matters. The skill rasterizes that page to an image and lets the model look. Even then the split holds. The model handles the visual judgment, not the plain reading of characters. The skill file is basically an engineer’s reflex, written down. I learned this the slow way. So I built this reader app readplace.com http://readplace.com/?utm source=fagnerbrack.com&utm medium=post-body-link&utm content=8ee45 that turns saved PDFs into clean text. Early on, I pointed a Google Vision model at scanned pages and asked it for the words. It worked on easy scans and fell apart on a dense one, taking up to 15 minutes AWS Lambda max timeout setting to process 212 pages event with each page getting its own lambda in parallel. Many pages timed out on their own and some came back half empty or half full depending on how you're feeling today . It cost real money and patience. I tore that path out and said "F$$$ IT", just run Tesseract instead, locally, in the same container. The same document finished in a fraction of the time, cost nothing, and left no page empty. Bumping render DPI from 150 to 300 and pinning the LSTM engine lifted recognized words from 21,335 to 23,719 on the same file. Wall clock went from 317s to 48s for that quality. Get Fayner Brack’s stories in your inbox Join Medium for free to get updates from this writer. Going multilingual needed no language detection and no per-language model. Tesseract ships script bundles, one model per script family: one invocation reads whatever scripts are presenttesseract --psm 1 -l 'script/Latin+script/Arabic+script/HanS+script/Cyrillic' page.png - --psm 1 runs OSD per page and picks the right bundle per region. About 35 script packs cover 100+ languages. There is no detection step in the app code and no per-language branch. Unfortunately going multi-language increased the time to process significantly, so I disabled it.. a problem for another day. Then I made the opposite mistake. With Tesseract working, I cut the model out completely. The reader filled with correct words and no shape, every heading, list, and column flattened into one gray block. Tesseract reads letters. It does not know a heading from a sentence. That judgment is fuzzy, and fuzzy is the work an Artificial Intelligence model is good at ; . So the model came back, for the job AFTER recognition. The residual errors were the probabilistic kind: cross-page hyphenations Veposi- then tory , and substitutions like V↔D and m↔rn that survived because the misread was still a real word. Those are the shape a model is good at. Letter recognition from pixels is not. The work split into three calls, each scoped tight. Stage 1 — Per-Page Cleanup One chat completion per page. The prompt changes a word only above about 90% confidence, and leaves digits and proper nouns alone. A Lambda guardrail caps length delta at 30% and preserves the digit multiset, and on any rejection the original Tesseract text passes through unchanged. Stage 2 - Document Diff Review One call per document. It sees the per-page word-level diff plus the full cleaned text, and emits APPROVE, REJECT, MODIFY, or NEW per change. A fix that landed on one page out of twelve gets rejected when eleven other pages show the original word correct. Stage 3 — semantic HTML for Readability.js One call per page. It emits a sanitized fragment of h2 , h3 , ul , ol , pre , code , blockquote , and table , with text-pattern rules standing in for the visual cues a vision model would have used. A guardrail requires 70% visible-text retention, or the page falls back to plain