OCR It
Pin a region once. Hit a hotkey on every page. Get the whole book as text.
A **Chrome and Firefox** extension for reading a **paginated document trapped in
a viewer** — a scanned book, a slide deck, a PDF, a reader that won't let you
select text.
You drag out the capture region **once**. After that every press of the hotkey
screenshots that exact rectangle, OCRs it, and appends the text to a running
transcript. Or hand the whole job over: ⌥⇧A starts a run that
captures, turns the page, and repeats until the document ends.
Then paste the result wherever it's useful — an LLM being the obvious one, since
a few hundred pages you couldn't select are now a text file you can hand to
Claude or ChatGPT to summarise, search or ask questions about.
OCR runs locally with a bundled Tesseract build. **No API key, no network, no
images leaving your machine** — the extension makes no outbound requests at all.
---
## Install
Install from your browser's store — one click, and it updates itself:
| | |
| --- | --- |
| **Chrome** 116+ | [Chrome Web Store](https://chromewebstore.google.com/detail/ocr-it/pekiamimgjbjgpggeilimnaplklceabh) |
| **Firefox** 140+ | [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/ocr-it/) |
After installing on Chrome, check `chrome://extensions/shortcuts`: Chrome
silently leaves a hotkey blank when something else already claims it. Firefox
has no navigable shortcut editor, so the popup's hotkey buttons point you at
**about:addons → gear icon → Manage Extension Shortcuts** instead.
To build it yourself instead — one source tree, two targets. `npm run build`
writes a loadable directory per browser into `build/`:
```sh
git clone https://github.com/thiagotigaz/ocr-it.git
cd ocr-it
npm run build # -> build/chrome, build/firefox
```
Chrome (116+)
1. Open `chrome://extensions` and turn on **Developer mode**
2. **Load unpacked** → select `build/chrome` (or the repo root — it is a valid
Chrome extension as checked in, so no build step is strictly needed)
3. Pin the extension — the toolbar icon doubles as the page counter
Firefox (140+)
1. Open `about:debugging#/runtime/this-firefox`
2. **Load Temporary Add-on…** → pick `build/firefox/manifest.json`
3. Pin the extension from the toolbar's overflow menu
Or, with Mozilla's tooling, `npm run start:firefox` launches a scratch profile
with the extension already installed and reloads it on every edit.
A temporary add-on is unloaded when Firefox quits — only Mozilla-signed add-ons
install permanently, which is what the store listing above is for. This route is
for working on the code.
Everything needed is committed — `npm install` is only for the tests, the
Firefox linter, or re-vendoring Tesseract.
**It asks for no site access at install.** Single captures ride on `activeTab`,
which the browser hands over when you press the hotkey or open the popup. Two
things need a durable grant — an auto-run that outlives a page load, and turning
pages inside a **cross-origin iframe** — and the popup offers an **Allow** button
for the site you're on when it matters. Firefox exposes the same grant under
**about:addons → OCR It → Permissions**.
| | |
| --- | --- |
| ⌥⇧S | Capture the region once |
| ⌥⇧A | Start / stop an automatic run |
| ⌥⇧R | Draw or redraw the region |
---
## Using it
### 1. Pin the region
⌥⇧R, then drag a box over the text. Before saving you can drag it
around, pull the handles, or nudge it a pixel at a time with the arrow keys
(hold ⇧ to resize). Enter keeps it.
Draw a little *inside* the text margins — everything in the rectangle gets read,
page numbers and running headers included.
### 2. Capture
Press ⌥⇧S once per page. The screenshot is taken immediately and OCR
runs in the background, so you never wait between pages — captures queue up and
the badge counts what's still being read.
### 3. Or let it run
Set up a next-page control (below) and ⌥⇧A takes over completely:
capture, turn, capture, turn, until the document ends. Esc on the
page stops it.
### 4. Export
Every page is listed with a thumbnail of **exactly what was cropped**, so a
drifted region is obvious at a glance instead of eighty pages later. Text is
editable in place; a bad read can be re-run on its own.
**Copy all** and **Download .txt** emit the pages in order with `--- page N ---`
separators.
A page marked `DUPLICATE` had text identical to the one before it — nearly
always because the document didn't actually turn.
---
## Turning pages for you
Enable **Turn the page automatically after capture**, then:
- **Click a control** — hit **Pick control** and click the viewer's next-page
button. What gets stored is a **point**, not a CSS selector.
- **Press a key** — dispatches a keyboard event (default `ArrowRight`) into
whichever frame owns the middle of your capture region, so the reader gets it
rather than the host page.
**Test now** fires an advance immediately, without capturing, and reports what
happened — worth using before starting a long run.
### Why a point rather than a selector
A stored point survives the DOM re-renders that routinely invalidate a CSS
selector, and it reaches two places a selector cannot:
- **Cross-origin iframes.** Most embedded readers are iframes, and nothing the
top frame can express addresses an element inside one.
- **Shadow DOM.** `document.querySelector` can't see into a shadow root.
At advance time the point is offered to every frame and the one that actually
owns it acts. A frame works out where it sits inside the top-level viewport by
walking up its same-origin ancestors; across an origin boundary the parent hands
the offset down by `postMessage`. (`window.screenX` is no help — inside an
iframe it reports the browser window, not the frame.) The owning frame resolves
the point through any shadow roots, walks up to the nearest real control, and
emits the full `pointerdown → mousedown → pointerup → mouseup → click` sequence,
so viewers that page on `pointerdown` behave like those listening for `click`.
### When it doesn't turn
Every attempt records a verdict, shown in the popup and as an on-page toast:
| Verdict | Meaning |
| --- | --- |
| `no next-page control picked yet` | Auto-advance is on but nothing was picked |
| `an embedded viewer owns that point` | Chrome's PDF viewer or a plugin — unreachable by any extension |
| `only the page background is at that point` | The control moved; pick it again |
| `a nested frame owns that point` | A frame that couldn't be injected into |
Because the target is a fixed point on screen, **resizing the window or changing
zoom mid-run breaks it**, exactly as it breaks the capture region.
---
## Hands-off runs
⌥⇧A — or **Start auto-run** — captures, turns, and repeats on its own.
Each cycle waits for that page's OCR to come back before turning. That costs
nothing in practice (OCR is faster than a page turn) and buys the one thing an
unattended loop needs: **reliable end-detection**. A run that only fired
screenshots on a timer would sail past the last page and fill the transcript
with copies of it.
Stop it with Esc on the page, the hotkey, or the popup. It also stops
itself when:
| Condition | Default |
| --- | --- |
| The text stops changing | after 2 identical pages — you've hit the end |
| The page can't be turned | immediately, quoting the reason |
| OCR fails or stalls | immediately |
| Page cap reached | 300 pages |
| The tab closes, or Chrome restarts | immediately |
Whatever ended it is reported in the popup, so a run you walked away from never
just stops being mysterious. A run refuses to start without a working next-page
control rather than spinning on one page.
---
## PDFs
Chrome's built-in PDF viewer works — text comes straight out of it. Draw the
region over the page area (not the thumbnail sidebar) and page with your own
↓ / PageDown.
Auto-advance does **not** work inside the PDF viewer, in either mode: the viewer
is a plugin no extension can inject into, so a click lands on the `