OCR It – pull text out of un-copyable documents for your LLM

Aug 24, 2026 01:25 PM - 2 hours ago 2

Pin a region once. Hit a hotkey connected each page. Get the full book arsenic text.

Manifest V3 Chrome 116+ Runs offline No install-time tract access MIT

 the tally parameter astatine the apical of the page and a per-page confirmation toast successful the corner

A Chrome hold for reference a paginated archive trapped successful a viewer — a scanned book, a descent deck, a PDF, a scholar that won't fto you prime text.

You resistance retired the seizure region once. After that each property of the hotkey screenshots that nonstop rectangle, OCRs it, and appends the matter to a running transcript. Or manus the full occupation over: ⌥⇧A starts a tally that captures, turns the page, and repeats until the archive ends.

Then paste the consequence wherever it's useful — an LLM being the evident one, since a fewer 100 pages you couldn't prime are now a matter record you tin manus to Claude aliases ChatGPT to summarise, hunt aliases inquire questions about.

OCR runs locally pinch a bundled Tesseract build. No API key, nary network, no images leaving your machine — the hold makes nary outbound requests astatine all.


  1. Download this repo aliases git clone it
  2. Open chrome://extensions and move connected Developer mode
  3. Load unpacked → prime the folder
  4. Pin the hold — the toolbar icon doubles arsenic the page counter

Everything needed is committed. There's nary build step: npm instal is only for running the tests aliases re-vendoring Tesseract.

Then cheque chrome://extensions/shortcuts and corroborate the hotkeys landed — Chrome silently leaves them blank erstwhile thing other already claims them.

It asks for nary tract entree astatine install. Single captures thrust connected activeTab, which Chrome hands complete erstwhile you property the hotkey aliases unfastened the popup. Two things need a durable assistance — an auto-run that outlives a page load, and turning pages inside a cross-origin iframe — and the popup offers an Allow fastener for the tract you're connected erstwhile it matters.

⌥⇧S Capture the region once
⌥⇧A Start / extremity an automatic run
⌥⇧R Draw aliases redraw the region

 a dimmed page pinch a agleam action box, resize handles, a unrecorded size readout, and a hint bar

⌥⇧R, past resistance a container complete the text. Before redeeming you tin resistance it around, propulsion the handles, aliases nudge it a pixel astatine a clip pinch the arrow keys (hold ⇧ to resize). Enter keeps it.

Draw a small inside the matter margins — everything successful the rectangle gets read, page numbers and moving headers included.

Press ⌥⇧S erstwhile per page. The screenshot is taken instantly and OCR runs successful the background, truthful you ne'er hold betwixt pages — captures queue up and the badge counts what's still being read.

Set up a next-page power (below) and ⌥⇧A takes complete completely: capture, turn, capture, turn, until the archive ends. Esc connected the page stops it.

The popup listing captured pages pinch thumbnails, characteristic counts and OCR confidence

Every page is listed pinch a thumbnail of exactly what was cropped, truthful a drifted region is evident astatine a glimpse alternatively of eighty pages later. Text is editable successful place; a bad publication tin beryllium re-run connected its own.

Copy all and Download .txt emit the pages successful bid pinch --- page N --- separators.

A page marked DUPLICATE had matter identical to the 1 earlier it — nearly always because the archive didn't really turn.



 language, layout, sharpening, auto-advance and auto-run options

Enable Turn the page automatically aft capture, then:

  • Click a control — deed Pick control and click the viewer's next-page button. What gets stored is simply a point, not a CSS selector.
  • Press a key — dispatches a keyboard arena (default ArrowRight) into whichever framework owns the mediate of your seizure region, truthful the scholar gets it rather than the big page.

Test now fires an beforehand immediately, without capturing, and reports what happened — worthy utilizing earlier starting a agelong run.


Why a constituent alternatively than a selector

A stored constituent survives the DOM re-renders that routinely invalidate a CSS selector, and it reaches 2 places a selector cannot:

  • Cross-origin iframes. Most embedded readers are iframes, and thing the top framework tin definitive addresses an constituent wrong one.
  • Shadow DOM. document.querySelector can't spot into a protector root.

At beforehand clip the constituent is offered to each framework and the 1 that actually owns it acts. A framework useful retired wherever it sits wrong the top-level viewport by walking up its same-origin ancestors; crossed an root bound the genitor hands the offset down by postMessage. (window.screenX is nary thief — wrong an iframe it reports the browser window, not the frame.) The owning framework resolves the constituent done immoderate protector roots, walks up to the nearest existent control, and emits the afloat pointerdown → mousedown → pointerup → mouseup → click sequence, so viewers that page connected pointerdown behave for illustration those listening for click.

Every effort records a verdict, shown successful the popup and arsenic an on-page toast:

Verdict Meaning
no next-page power picked yet Auto-advance is connected but thing was picked
an embedded spectator owns that point Chrome's PDF spectator aliases a plugin — unreachable by immoderate extension
only the page inheritance is astatine that point The power moved; prime it again
a nested framework owns that point A framework that couldn't beryllium injected into

Because the target is simply a fixed constituent connected screen, resizing the model aliases changing zoom mid-run breaks it, precisely arsenic it breaks the seizure region.


⌥⇧A — aliases Start auto-run — captures, turns, and repeats connected its own.

Each rhythm waits for that page's OCR to travel backmost earlier turning. That costs nothing successful believe (OCR is faster than a page turn) and buys the 1 point an unattended loop needs: reliable end-detection. A tally that only fired screenshots connected a timer would sail past the past page and capable the transcript with copies of it.

Stop it pinch Esc connected the page, the hotkey, aliases the popup. It besides stops itself when:

Condition Default
The matter stops changing after 2 identical pages — you've deed the end
The page can't beryllium turned immediately, quoting the reason
OCR fails aliases stalls immediately
Page headdress reached 300 pages
The tab closes, aliases Chrome restarts immediately

Whatever ended it is reported successful the popup, truthful a tally you walked distant from never just stops being mysterious. A tally refuses to commencement without a moving next-page control alternatively than spinning connected 1 page.


Chrome's built-in PDF spectator useful — matter comes consecutive retired of it. Draw the region complete the page area (not the thumbnail sidebar) and page pinch your own ↓ / PageDown.

Auto-advance does not activity wrong the PDF viewer, successful either mode: the viewer is a plugin nary hold tin inject into, truthful a click lands connected the <embed>, and its paging is autochthonal scrolling that synthetic cardinal events can't drive. Since you're already pressing a hotkey per page, pressing your ain page-down cardinal costs nothing.

For a PDF connected disk (file:///…), unfastened chrome://extensions → Details connected OCR It → alteration Allow entree to record URLs. Chrome withholds file:// from every extension until you do.


Setting What it does
Language English, Portuguese and Spanish vessel pinch it — spot beneath to adhd more
Layout Tesseract's page segmentation. Single block suits 1 file of assemblage text; Auto handles mixed layouts
Sharpen harvest earlier OCR Upscales the harvest to ~2× and flattens it to a stretched greyscale ramp. Helps a batch connected non-retina displays; time off it on
Flag pages identical to the erstwhile one Marks repeats arsenic DUPLICATE and, successful a run, ends it
Auto-run Pause betwixt pages, really galore repeats extremity a run, and the difficult page cap

Three vessel pinch the hold — English, Portuguese and Spanish. Any of Tesseract's different ~100 languages tin beryllium added, but thing is fetched at runtime, truthful the exemplary has to beryllium vendored into the hold first.

npm instal # once, for the tooling npm tally vendor -- fra deu jpn # immoderate tesseract connection codes

That pulls each <code>.traineddata.gz into vendor/lang/. Then adhd the codes to LANGUAGES successful src/shared.js truthful they look successful the popup's dropdown:

export const LANGUAGES = [ { code: 'eng', label: 'English' }, { code: 'por', label: 'Portuguese' }, { code: 'spa', label: 'Spanish' }, { code: 'fra', label: 'French' }, // added ];

Reload the hold astatine chrome://extensions and the caller introduction is there.

Codes are the three-letter ones Tesseract uses: fra French, deu German, ita Italian, nld Dutch, rus Russian, jpn Japanese, chi_sim simplified Chinese, ara Arabic. The afloat database lives successful the tessdata repository.

Two languages astatine once activity arsenic good — springiness a codification of eng+por and Tesseract loads some models into 1 worker, reference a page that mixes them:

{ code: 'eng+por', label: 'English + Portuguese' },

It costs a small velocity and a small accuracy, truthful for illustration a azygous connection when the archive only has one.

Size. Each connection adds astir 0.7–3 MB to the hold — English is the biggest astatine 2.9 MB, French 1 of the smallest astatine 0.7 MB. The models travel from @tesseract.js-data/<code>/4.0.0_best_int: the "best" models quantised to integers, meaningfully much meticulous than the accelerated variants.

To driblet a language, delete its .gz from vendor/lang/ and its introduction from LANGUAGES.


MV3 work workers person nary DOM and nary Worker, truthful the dense lifting lives in an offscreen document.

run loop ─┐ (⌥⇧A: seizure → move → repeat) hotkey ───┴▶ background.js ─▶ hide our ain HUD, hold for a paint ─▶ chrome.tabs.captureVisibleTab (whole viewport) ─▶ offscreen: harvest to the region, upscale, greyscale ─▶ shop the page + thumbnail, move the page ─▶ queue ─▶ offscreen: Tesseract ─▶ matter into storage
Path Role
src/background.js Hotkeys, seizure pipeline, serial OCR queue, auto-advance, the tally loop
src/offscreen/ Canvas cropping and the Tesseract worker
src/content/overlay.js Region picker, constituent picker, on-page HUD, cross-frame offset cascade
src/popup/ Page list, editing, settings, export
src/shared.js Storage schema and helpers shared by the worker and the popup
vendor/ Tesseract runtime + .traineddata, committed truthful there's nary build
tools/ Icon generator, vendoring, screenshots, end-to-end test

Details that matter:

  • The region is stored successful CSS pixels comparative to the viewport. At capture time the screenshot's ain width is divided by the unrecorded innerWidth, truthful zoom changes and retina/non-retina differences travel retired correct without trusting a stored DPR.
  • The HUD is hidden and fixed 2 animation frames to vanish before the screenshot, truthful the extension's ain toast tin ne'er extremity up wrong the crop.
  • Captures are serialised and OCR runs 1 occupation astatine a time, truthful mashing the hotkey queues activity alternatively of corrupting the page list.
  • Full-size crops are kept only until a page is publication successfully, then discarded; the thumbnail stays for verification.
  • A tally is cancelled by bumping a token the loop re-checks astatine each await, so stopping lands astatine a checkpoint alternatively than mid-write. Storage sounds wrong the loop double arsenic keep-alive for the work worker, and a one-minute alarm restarts the loop if the worker is recycled anyway.

npm install npm test # adhd -- --headed to watch it npm tally shots # regenerate the screenshots successful docs/

The suite installs the unpacked hold into a existent headless Chrome complete the DevTools protocol, serves fixture documents, and drives the existent product: it drags retired a region pinch synthetic rodent events, fires captures, checks the OCR text against what was rendered, verifies thing extracurricular the region leaked in, checks the shipped manifest requests nary big entree and that 1 toolbar click is capable for a plain capture, exercises copy detection, drives auto-advance against 3 DOM shapes — a plain page, a cross-origin iframe and an open protector root — confirms a misconfigured auto-advance reports itself alternatively of failing silently, runs an unattended loop to the extremity of a finite archive and asserts it stopped connected its own pinch each page successful order, and checks a tally stops dormant connected request.

Chrome 137+ ignores --load-extension, truthful the harness installs complete CDP with Extensions.loadUnpacked and --enable-unsafe-extension-debugging. Headless Chrome can't show the support punctual either, truthful the behaviour tests install a transcript of the hold pinch the assistance baked successful — the authorities of a personification who clicked Allow — while the permissions conception checks the existent manifest and proves the ungranted way still useful via Extensions.triggerAction, which is a genuine toolbar click.


  • chrome:// pages, the Web Store and different extensions' pages are disconnected limits to every extension, including this one.
  • Only the visible viewport tin beryllium captured — the region has to beryllium connected screen.
  • Chrome rate-limits screenshots to a mates per second; captures retry with backoff, truthful accelerated mashing conscionable queues.
  • Accuracy tracks the source. Crisp rendered matter sounds astatine 90 %+ confidence; low-resolution scans and handwriting will request cleanup.
  • Local file:/// documents request Allow entree to record URLs switched on.
  • activeTab does not scope cross-origin iframes. If your scholar lives successful one, grant the tract from the popup earlier mounting up auto-advance.

MIT — spot LICENSE. Bundled Tesseract components support their own licences: vendor/LICENSE.tesseract-core and vendor/tesseract.min.js.LICENSE.txt.

Built connected tesseract.js.

More