ocring-pdfs · v0.1.0 · 2026-09-12 · sha256 74e8cf721e2c0d5f

ocring-pdfs v0.1.0A

Immutable. This exact content is served forever at /api/v1/blob/74e8cf721e2c0d5f.

---
name: ocring-pdfs
description: "Adds a searchable text layer to a scanned PDF with ocrmypdf. Installs the toolchain at runtime in ~18s. Use when a PDF's pages are images and the deliverable is a file to keep, grep, or hand to another tool: 'make this PDF searchable', 'OCR this scan', 'I can't select the text in this PDF', 'search across these scanned pages', 'the pdf skill returned nothing'. Handles non-English scans via tesseract language packs. NOT for charts, diagrams, handwriting, or slide layouts. Route those to transcribing-images."
metadata:
  version: 0.1.0
---

# OCRing PDFs

`ocrmypdf` writes an invisible text layer over the original page images, so the
output file is both the scan you can look at and a document `pdftotext`, `grep`,
and `pdfplumber` can read. Rasterize-then-tesseract gives you a `.txt` divorced
from the pages; page numbers and coordinates are gone.

The toolchain is not in the base container. It installs in 18 seconds
(measured 2026-09-12: apt 3s, pip 15s), so install it when a scan shows up
rather than carrying it in a container layer.

## Probe before installing

```bash
pdftotext in.pdf - | tr -d '\f \n' | wc -c
```

Nonzero means the PDF already has a text layer and is not a scan. Extract with
`pdftotext` or `pdfplumber` and stop. Running OCR on it wastes a minute, and with
`--force-ocr` it replaces exact embedded text with a lossy reading of a raster of
itself.

A small nonzero count (tens of characters across many pages) is the mixed case:
a born-digital cover page in front of scanned body pages, or a scan whose
producer stamped a header. `--skip-text` handles it.

## Install

```bash
sh scripts/ensure_ocr.sh              # English
sh scripts/ensure_ocr.sh nor deu      # plus Norwegian and German
```

Idempotent: 0.8s when everything is already present, 2.6s to add one more
language pack. Installs `ghostscript`, `pngquant`, `poppler-utils`, `tesseract`
and its language packs via apt, then `ocrmypdf` via pip.

## Run

```bash
ocrmypdf --skip-text --deskew --rotate-pages --output-type pdf in.pdf out.pdf
pdftotext out.pdf - | wc -w        # verify: zero words means it failed quietly
```

About 2s per page for a single dense page at 200 DPI on one core. A 300-page
scan is therefore a background job, not a single bash call — launch it detached
with a sentinel file per the external-call pattern in `bash-tool-timeout`.

## Which text-layer mode

| flag | use it when |
|---|---|
| `--skip-text` | Default. Pages that already carry text are passed through untouched; image-only pages get OCR. The safe choice for anything mixed. |
| `--force-ocr` | Every page is rasterized and re-OCRed, discarding any existing text. Correct for a scan carrying a junk text layer, and for pages with text-over-image that `--skip-text` would skip. Destroys real embedded text, so probe first. |
| `--redo-ocr` | Replaces a previous OCR layer while leaving born-digital text alone. Narrower than `--force-ocr` and slower to fail on odd inputs. |

`--output-type pdf` skips PDF/A conversion. Drop it when the output is going into
an archive that requires PDF/A; ghostscript does the conversion either way.

## Languages

`-l eng+nor` for a mixed-language document, `-l nor` for a monolingual one. Order
does not matter. Every code needs its `tesseract-ocr-<code>` pack installed.
Pass the codes to `ensure_ocr.sh` and it handles them. Accuracy drops noticeably
when the language is wrong, and tesseract will not tell you; it returns
confident garbage instead.

## Container facts (measured 2026-09-12)

- `apt-get update` exits 100 here. A preconfigured nodesource repo is off the
  egress allowlist and returns 403, and the nonzero exit aborts any `&&` chain
  behind it. The Ubuntu mirrors are reachable without an update. Run
  `apt-get install` directly.
- `unpaper` is absent, so `--clean` and `--clean-final` fail. Don't pass them.
- One core, so `--jobs` buys nothing on claude.ai. CCotw has four.
- `ocrmypdf --version` prints to stderr. Capture with `2>&1` or a version check
  reads as empty.
- `jbig2` is absent; output uses CCITT/JPEG instead, which costs some file size
  and nothing else.

## When to use transcribing-images instead

This skill produces glyphs. It does not read a chart, describe a diagram, or
recover handwriting. Tesseract on those pages returns nothing useful and gives no sign that it lost
anything.

Route to `transcribing-images` when the meaningful content is a picture, or when
the deliverable is a reading rather than a file. Both is a normal answer: OCR the
document so it is greppable, then send the pages that carry figures to a vision
model.

In an interactive session, native vision beats both for a handful of pages:
rasterize with `pdftoppm -r 200 -png` and `view` the images. Reach for OCR when
the document is longer than context will hold, or when the text has to outlive
the conversation as a file.