Skip to content

PDF

The text lane and the scan lane, page ranges, the JSON additions, and the coordinate spaces.

Terminal window
cheapshot report.pdf
cheapshot --pages 3-5 report.pdf
cheapshot --force-ocr report.pdf # ignore the text layer, OCR every page

Every page takes one of two lanes, chosen per page.

A page whose text layer checks out is read through PDFKit. The text is exact, and the fonts come with it, so fixed-pitch runs are detected and fenced without any measurement. Most born-digital PDFs are entirely this lane.

To check the layer, every page is rendered at 2x and read with Vision first. An OCR line of four or more words counts as missing when more than half of its word pairs appear nowhere in the layer. One missing line is enough to reject the layer. This catches both failures seen on real papers: a layer of symbols such as ! " % & in place of words, and a layer that reads fine but silently leaves lines out. It costs one OCR pass per page, about a quarter of a second on Apple silicon.

A page takes the scan lane when its text layer holds fewer than 20 non-whitespace characters, when the check above rejects the layer, or when --force-ocr is given. It gets the Vision OCR the check already ran, the same path as a screenshot. The lane is recorded per page, so a downstream tool can route scanned pages for review instead of trusting them like native text.

--pages <N|N-M> limits the run to one page or a range. The default is every page. Plain output separates pages with a marker line:

--- page 1 ---
Quarterly report
...
--- page 2 ---
...

--json adds two things to the payload for a PDF. source carries the file path, the page count, and sha256, so a citation can be pinned to the exact bytes that were read. pages is an array, and each page object carries:

  • n, the 1-based page number
  • lane, either text or scan
  • width and height, the rendered pixel size at 2x after rotation
  • lines, one object per line with n, text, bbox and confidence

A line’s bbox is [minX, minY, maxX, maxY] with a top-left origin, but the unit depends on the lane.

In the text lane the box is in PDF points, in the page’s own unrotated coordinate space. In the scan lane the box is in pixels of the rendered image.

The page width and height are always the rendered pixel size after rotation, so on a rotated page they do not describe the space the text-lane boxes live in. Read the lane before you trust a box.

A password-protected PDF is an error. Cheapshot does not prompt for a password and does not read a partial document: the file fails, its --json entry carries "error", and the run exits 1.

Both apply exactly as they do for images. The ledger records mode: "pdf" and counts what an agent would have paid to read each page as an image, which makes the PDF figure an estimate of avoided cost rather than a measured saving. See Ledger.