A PDF's text layer and its scanned images
Most PDFs are neither pure text nor pure scan. Read both, in one pipeline, into one Document per page.
A PDF's text layer covers its vector text. The scanned table pasted into the
middle of the page is a bitmap, and getTextContent cannot see a word of it.
The usual advice — decide per page whether to trust the text layer or OCR the whole raster — gets the hybrid page wrong both ways. The text layer misses the scan; whole-page OCR re-reads clean vector text worse than the PDF already states it.
Read both
import { Pipeline } from '@stabrise/scaledp'
import { PdfToDocument, PdfEmbeddedImages, PdfMergeImageText } from '@stabrise/scaledp/pdf'
import { PaddleTextRecognizer } from '@stabrise/scaledp/ocr'
const pipeline = new Pipeline([
new PdfToDocument({ resolution: 300 }),
new PdfEmbeddedImages({ resolution: 300 }),
new PaddleTextRecognizer({ inputCol: 'image', outputCol: 'image_text' }),
new PdfMergeImageText(),
])
const pages = await pipeline.transform(file)One row per page comes out, and document.bboxes holds the text-layer words and
the words read out of the page's images together, in one coordinate space, in
reading order.
What each stage does
PdfToDocumentreads the text layer into word boxes and sets the page index on the row.PdfEmbeddedImagesextracts that page's raster images at their own pixel size — not a crop of the rendered page, so nothing is resampled — and emits a row each, with aplacementsaying where the image sits.- Any recognizer reads them. They are ordinary images; nothing about this step is PDF-specific.
PdfMergeImageTextmaps each image's boxes back onto the page through its placement, merges them with the text layer, and reduces the page's rows back to one.
The row count goes 1 → pages → images → pages. Every PDF stage honours a page index the row already carries, so the two readers do not multiply.
Overlapping readings
A searchable scan — a photograph with an invisible OCR layer over it — describes the same words twice. Two things handle it:
PdfEmbeddedImagesskips an image that already hasminCoveringBoxestext-layer boxes inside it, so the wasted recognizer pass never happens.PdfMergeImageText'sstrategydecides the rest.text-layer-winsis the default;ocr-winsis for corpora whose text layer extracts as mojibake.
When you only want the cheap answer
If your corpus really is either-or, the two-pass split is still cheaper — see Skip OCR when the PDF already has text.