← ALL FIELD NOTES

How to Classify PDFs Before Extraction With an Intake Pipeline

Build a PDF preflight stage that validates files, inspects pages, measures text and layout risk, and routes each document to the right extraction path.

Sending every uploaded PDF to the same extractor is a convenient default—and a costly one. A file named invoice.pdf may not be a PDF at all. A real PDF may be encrypted, structurally damaged, image-only, a mix of scanned and native-text pages, or difficult to read because its apparent text order does not match its visual layout.

A more dependable architecture treats intake as a distinct preflight and classification phase. This phase does not try to produce the final business fields. Instead, it creates evidence about the file and decides which processing path is appropriate:

  • native text extraction
  • OCR
  • repair followed by re-inspection
  • a specialized layout-aware path
  • quarantine or human review

The goal is not perfect prediction. It is to make downstream extraction deliberate, observable, and easier to operate at scale.

Why PDF preflight belongs before extraction

PDF is a container format, not a guarantee of semantic usability. A document can have a text layer but still extract badly: lines may be emitted in an unexpected order, columns may merge, and words may be fragmented. PyMuPDF specifically notes that extracted text can diverge from natural reading order and recommends using positional block or word data when layout matters. PyMuPDF FAQ

Likewise, a page that appears to be “a scan” can contain more than one type of content. OCRmyPDF describes PDF pages as combinations of raster images, vector graphics, and text. That is why a file-level label alone is often too coarse for packets, statements, or forms with appended scans. OCRmyPDF Introduction

Preflight creates three practical benefits:

  1. Better routing. Native text does not need OCR by default; image-only pages probably do.
  2. Earlier failures. Password gates and invalid structures can be surfaced before a downstream job fails with an ambiguous error.
  3. Traceability. Operators can see why a document was routed, retried, repaired, or held for review.

Design the intake pipeline as a sequence of gates

A useful intake service should write an immutable original object, generate a manifest, and route work based on that manifest. Avoid making a routing decision from filename, upload metadata, or a single extraction attempt.

A typical flow looks like this:

upload
  -> store original bytes and assign document ID
  -> identify media type from bytes
  -> structural and encryption preflight
  -> inspect document and sampled pages
  -> classify text, language, geometry, and layout risk
  -> check route compatibility
  -> emit manifest + routing decision
  -> dispatch to extraction, OCR, repair, or review

Each stage should be able to return a terminal decision. For example, a password-protected document should not proceed to an extractor that cannot open it. A byte-level type mismatch should be quarantined before a PDF parser sees it.

1. Identify the file from bytes, not its extension

Treat Content-Type headers and extensions as user-provided hints. They are useful for telemetry, but they should not establish trust.

Content detectors can inspect byte signatures near the beginning of an input stream. Apache Tika’s MagicDetector, for example, performs media-type detection using type-specific byte patterns. MagicDetector

Store both the declared and detected types in the manifest:

{
  "filename": "statement.pdf",
  "declared_media_type": "application/pdf",
  "detected_media_type": "application/pdf",
  "media_type_match": true
}

If the detector identifies something else, route to a quarantine state rather than attempting PDF extraction. That makes the event diagnosable and prevents parsers from becoming the first line of validation.

2. Validate structure and gate encrypted documents

A document that starts with a PDF signature may still be malformed or encrypted. Run a dedicated preflight before text extraction, OCR, or rendering.

For example, qpdf provides structural checks as well as encryption and password checks. Its command-line documentation also describes exit statuses that distinguish clean results, warnings, and errors. Running qpdf

Model these separately. “Readable with warnings” is not the same as “valid,” and “encrypted” is not the same as “requires a password.” An intake record could include:

{
  "pdf_preflight": {
    "structure": "valid_with_warnings",
    "encrypted": false,
    "password_required": false,
    "repair_attempted": false
  }
}

If you repair a file, preserve the original bytes and record the repair as a separate transformation. OCRmyPDF explains that real-world PDFs may require processing through pikepdf/QPDF and that reconstruction can occur during processing; a readable recovered file should not silently be treated as identical to its source. OCRmyPDF PDF Security and Processing

3. Inspect pages, not only documents

Once a file passes structural preflight, inspect it at page level. At minimum, collect:

  • page count
  • width and height
  • rotation
  • native text length
  • text block and word counts
  • image count or image coverage signal
  • a blank-page indicator
  • rendering or parsing errors

PyMuPDF exposes page text, image inspection, rendering, and related APIs that make this style of inspection possible. PyMuPDF Page API

A simple page classifier can use an explicit state model:

Page stateTypical evidenceDefault route
native_textMeaningful extracted text and low image dependenceNative extractor
image_onlyNo usable text with a page image or rendered contentOCR
mixedNative text plus substantial image contentNative extraction plus targeted OCR or review
blankLittle text and little visible contentPreserve and skip extraction
unreadableParser or render failureRepair or review

The exact thresholds should be configured from your document population. Do not hard-code a claim such as “fewer than N characters always means OCR.” A sparse invoice, a signature page, and a low-quality scan can all have low text counts for different reasons.

Classify text-layer quality, not merely text-layer presence

The question is not simply “does this PDF contain text?” The useful question is “is native text likely to satisfy this extraction task?”

Start with a sampled extraction from every page or from a representative subset for large files. Capture character count, non-whitespace character ratio, replacement-character frequency, word count, and whether the text appears repeated or empty. Then compare those signals with images, rendered page dimensions, and block geometry.

OCRmyPDF distinguishes scanned image PDFs from born-digital documents and adds a searchable text layer to scanned documents. OCRmyPDF Introduction That distinction is a helpful routing concept, but mixed files require a page-level plan. For example:

Pages 1–2: native_text
Pages 3–5: image_only
Page 6: blank

Route: extract native text from 1–2, OCR 3–5, preserve page 6 as blank

This avoids OCRing an entire packet just because one attachment is scanned. It also avoids trusting native text on a page where the visual content is only an embedded scan.

Measure layout risk before selecting a plain-text parser

Text availability is not a layout-quality guarantee. A multi-column report may yield plenty of text while placing the right column between lines from the left. Tables, headers, footers, rotated labels, and overlapping text can cause similar failures.

Use positional output—blocks, lines, or words—to calculate coarse layout signals. PyMuPDF’s text recipes document block- and word-level outputs with coordinates, which are useful inputs for layout-aware analysis. PyMuPDF Text Recipes

Examples of interpretable risk signals include:

  • multiple dense vertical bands of text, suggesting columns
  • high variation in text direction or rotation
  • many short fragments with overlapping bounding boxes
  • repeated top or bottom blocks, suggesting headers and footers
  • word positions arranged in grids, suggesting tables

You do not need a sophisticated machine-learning model to begin. A rule-based layout_risk of low, medium, or high is enough to choose between plain-text extraction, coordinate-preserving extraction, and review.

Detect language carefully and preserve uncertainty

Language routing is useful for OCR selection, downstream validation, and provider compatibility. But language identification needs enough valid text to be meaningful.

CLD3 returns language predictions with a probability and reliability signal, and it can report multiple frequent languages and their proportions. CLD3 Language Identifier

That supports a manifest such as:

{
  "language": {
    "source": "native_text_sample",
    "primary": "es",
    "probability": 0.91,
    "reliable": true,
    "alternatives": [
      {"language": "en", "proportion": 0.08}
    ]
  }
}

For short, corrupted, or image-only samples, keep primary as unknown rather than forcing a label. After OCR, you can run language classification again and record that the source changed.

Build routing you can inspect, not just routing that seems to work. Create a PagePith workspace to turn source material and pipeline decisions into a documented workflow. Start building

Make compatibility a routing rule

Every downstream extraction service or library has constraints: accepted formats, encryption behavior, page or file limits, supported scripts, orientation handling, and form technology support. Keep this knowledge in a versioned route policy rather than scattering it through worker code.

For example:

routes:
  native_text:
    require:
      structure: [valid, valid_with_warnings]
      encrypted: false
      page_state_any: [native_text, mixed]
    reject_when:
      layout_risk: high

  ocr:
    require:
      structure: [valid, valid_with_warnings]
      encrypted: false
      page_state_any: [image_only, mixed]

  manual_review:
    when:
      - structure: error
      - password_required: true
      - language_reliable: false
      - layout_risk: high

This separation has an operational advantage: a policy change does not require rewriting the inspectors. You can add or replace an extraction provider while keeping the manifest contract stable.

Use an auditable manifest and decision record

The manifest is the main product of preflight. It should contain raw measurements, derived classifications, tool versions where useful, and a final decision with reasons.

{
  "document_id": "doc_01JX...",
  "detected_media_type": "application/pdf",
  "sha256": "...",
  "preflight": {
    "structure": "valid",
    "encrypted": false,
    "password_required": false
  },
  "pages": [
    {
      "number": 1,
      "rotation": 0,
      "native_text_characters": 1834,
      "image_count": 0,
      "state": "native_text",
      "layout_risk": "medium"
    },
    {
      "number": 2,
      "rotation": 0,
      "native_text_characters": 0,
      "image_count": 1,
      "state": "image_only",
      "layout_risk": "unknown"
    }
  ],
  "routing": {
    "decision": "split_native_and_ocr",
    "reasons": ["mixed page states", "no encryption"]
  }
}

Keep measurement and decision separate. That lets you revise policies later, replay a manifest against a new route table, and explain why two documents were treated differently.

Run inspection in isolated workers

PDF inspection is not harmless metadata parsing. It requires opening and interpreting complex content, and it can involve rendering and repair. OCRmyPDF’s security guidance explicitly discusses the risks of PDF processing. OCRmyPDF PDF Security and Processing

Use isolated workers with bounded CPU, memory, disk, page count, and execution time. Keep original uploads out of shared temporary paths, clean up intermediate render artifacts, and make timeout outcomes first-class manifest states. A timeout is not proof that a file is invalid; it is a reason to route to a controlled retry or review path.

An honest PagePith demonstration

PagePith was asked to fetch Apache Tika’s MagicDetector.java source page. In the supplied fetch result, it returned the GitHub page title, reported a content length of 20,562, and produced a Markdown excerpt containing the file path and repository page metadata. This demonstrates a basic source-fetch step that can support research and documentation workflows.

That proof does not establish that PagePith executes PDF validation, detects MIME types from uploaded bytes, performs OCR, or makes extraction-routing decisions. Those functions still belong in your intake services and workers. The practical connection is documentation: use the evidence and manifest schema from your pipeline to maintain a clear, reviewable account of why routing rules exist.

Start with a small, high-signal preflight

You do not need to classify every possible PDF characteristic on day one. Start with these gates:

  1. Detect the media type from bytes.
  2. Check PDF structure, encryption, and password requirements.
  3. Record page count, geometry, rotation, and native text volume.
  4. Assign a page-level text state.
  5. Flag layout risk when positional text suggests columns or fragmentation.
  6. Preserve unknown states and route them safely.
  7. Write the evidence and routing reason to a manifest.

This turns extraction from a blind batch operation into a controlled system. Native-text documents can move quickly, scans can receive OCR, problematic files can be handled explicitly, and your team can investigate outcomes from recorded evidence instead of opaque failures.

Ready to document and operationalize your PDF intake workflow? Create your PagePith workspace.

Sources

  1. MagicDetectorApache Software Foundation
  2. Running qpdfQPDF
  3. OCRmyPDF IntroductionOCRmyPDF
  4. OCRmyPDF PDF Security and ProcessingOCRmyPDF
  5. PyMuPDF Page APIPyMuPDF
  6. PyMuPDF Text RecipesPyMuPDF
  7. PyMuPDF FAQPyMuPDF
  8. CLD3 Language IdentifierGoogle
Classify PDFs Before Extraction: Intake Guide · PagePith