Pop PDF
Extract

Image-Only Pages

Pdf::isImageOnlyDocument() and its companions answer one question — does a page carry a real text layer, or is it nothing but a single scanned or drawn image — without running any OCR. Extract\Content\PageClassifier reads each page's content stream operator by operator, the same walk Extracting Text runs, and disqualifies a page the moment it finds:

  • a text-showing operator
  • a path-painting operator (a fill, a stroke, or both)
  • a second image
  • an inline image drawn directly in the stream rather than referenced as an XObject

Real scan-to-PDF output places one full-page image XObject and draws nothing else, whatever a scanner or producer's own metadata claims about it. An unresolved image reference — a missing resource entry, a broken reference — is treated the same way: not provably image-only, so the answer is false rather than a guess.

Asking About a Whole Document#

isImageOnlyDocument($file, $pages = null, $pageLimit = null) returns one boolean for the whole document — true only if every page checked is a lone image with nothing else on it:

PHP
use Pop\Pdf\Pdf;

Pdf::isImageOnlyDocument(__DIR__ . '/scan.pdf');  // true — two pages, each one full-page image
Pdf::isImageOnlyDocument(__DIR__ . '/report.pdf'); // false — real text on every page

A document where only some pages are scans comes back false too — the whole-document check has no partial answer:

PHP
use Pop\Pdf\Pdf;

// page 1 and page 3 are scanned images, page 2 is real text
Pdf::isImageOnlyDocument(__DIR__ . '/mixed.pdf'); // false

Asking Page by Page tells the two image pages in that document apart from the text page between them. A page with OCR text already burned onto it, even invisibly, is not image-only either — the text-showing operator that write puts down disqualifies it, the same as any other real content sharing space with the image.

Asking Page by Page#

getImageOnlyPages($file, $pages = null, $pageLimit = null) classifies each page individually and returns an array of booleans, one per page:

PHP
use Pop\Pdf\Pdf;

$pages = Pdf::getImageOnlyPages(__DIR__ . '/mixed.pdf');

On the three-page mixed document above, getImageOnlyPages() returns [0 => true, 1 => false, 2 => true]:

  • page 1 is a scan, page 2 is text, page 3 is a scan
  • the array is keyed from zero, not the page number — index 0 is page 1's result, index 1 is page 2's, and so on
  • that's the opposite of extractAsImages(), covered on Pages as Images, whose array is keyed by the source document's own 1-based page numbers

Selecting the same two pages from both methods on the same document makes the difference concrete:

PHP
use Pop\Pdf\Pdf;

$classified = Pdf::getImageOnlyPages(__DIR__ . '/mixed.pdf', pages: [1, 3]);
$rasterized = Pdf::extractAsImages(__DIR__ . '/mixed.pdf', __DIR__ . '/pages', format: 'png', resolution: 150, pages: [1, 3]);

array_keys($classified); // [0, 1]      — position in the result, zero-based
array_keys($rasterized); // [1, 3]      — the source page number, one-based

Both calls select the same two pages and both return two entries — the keys mean different things. Reading $classified for "is page 3 a scan" means checking whichever position page 3 landed at, not looking up key 3; reading $rasterized for page 3's file means looking up key 3 directly.

The Raw-Data Forms#

isImageOnlyData($data, ...) and getImageOnlyPagesFromData($data, ...) are the same two checks against a string of PDF bytes instead of a path, for a document that arrived over HTTP or was never written to disk:

PHP
use Pop\Pdf\Pdf;

$data = file_get_contents(__DIR__ . '/scan.pdf');

Pdf::isImageOnlyData($data);         // true
Pdf::getImageOnlyPagesFromData($data); // [0 => true, 1 => true]

Both agree with their file-path counterparts on the same bytes — identical boolean, matching per-page arrays entry for entry. Reach for the data forms exactly when you'd reach for extractTextFromData() over extractTextFromFile(): the bytes are already in memory.

Page Selection and Limits#

$pages and $pageLimit work identically here to every other extract-style method on this site — an explicit $pages list, an array of 1-based numbers or a single number on its own, wins outright, and $pageLimit only applies when $pages is null:

PHP
use Pop\Pdf\Pdf;

$firstTwo = Pdf::getImageOnlyPages(__DIR__ . '/mixed.pdf', pageLimit: 2);
// [0 => true, 1 => false] — pages 1 and 2 only, page 3 never classified

$oneAndThree = Pdf::getImageOnlyPages(__DIR__ . '/mixed.pdf', pages: [1, 3]);
// [0 => true, 1 => true] — the pair from Asking Page by Page, above

Selecting Pages and Limiting How Much Is Walked cover the full rule against extractTextFromFile() — the two methods share the same selection code, so everything said there about page numbering, out-of-range pages and the explicit-list-wins rule applies here unchanged.

Routing a Scanned Document to OCR#

isImageOnlyDocument() is the gate in front of Pages as Images's extractAsImages(): a document with no text layer has nothing for Extracting Text to pull out, and the only way to get its content is to rasterize each page and hand it to OCR.

PHP
use Pop\Pdf\Pdf;

$file = __DIR__ . '/scan.pdf';

if (Pdf::isImageOnlyDocument($file)) {
    $images = Pdf::extractAsImages($file, __DIR__ . '/pages', format: 'png', resolution: 300);
    // hand each $images[$pageNumber] to an OCR engine
}

A mixed document routes each page on its own terms instead of an all-or-nothing check, using getImageOnlyPages() to decide per page and extractTextFromFile() for whichever pages already have text:

PHP
use Pop\Pdf\Pdf;

$file = __DIR__ . '/mixed.pdf';

foreach (Pdf::getImageOnlyPages($file) as $i => $isScan) {
    $pageNumber = $i + 1; // the array is zero-keyed; the document's pages are not

    if ($isScan) {
        $images = Pdf::extractAsImages($file, __DIR__ . '/pages', format: 'png', resolution: 300, pages: [$pageNumber]);
        // hand $images[$pageNumber] to an OCR engine
    } else {
        $text = Pdf::extractTextFromFile($file, pages: [$pageNumber]);
    }
}

That $i + 1 is the zero-to-one conversion Asking Page by Page covers — carry it forward the moment a page index needs to reach a page-number-keyed call like extractAsImages().

See Also#