Back
Beyond OCR: How to Extract Tables, Forms, Images, and Meaning from Complex Documents

Author: Latha Shree

Editor: Vahe Aslanyan

Beyond OCR: How to Extract Tables, Forms, Images, and Meaning from Complex Documents

A practical engineering handbook on document intelligence - layout analysis, structured extraction, and semantic understanding beyond character recognition.

Introduction

OCR answers one question: “What characters are on this page?” Document intelligence answers harder questions:

  • Where are the tables, and what are their cell boundaries?
  • Is this a form with numbered fields and checkboxes?
  • Which text is a formula that should not be rewritten?
  • What terms appear repeatedly and need consistent handling?
  • How do images and vector graphics relate to adjacent text?

This handbook covers the document understanding layer that sits between raw PDF parsing and downstream processing - translation, data extraction, redaction, summarization, or any transformation that must preserve document structure. The patterns described here are drawn from production document-processing systems where computer vision, heuristic classification, and LLM-based extraction work together.

If OCR is the eyes, document intelligence is the brain that knows what it’s looking at.

Why OCR Alone Is Not Enough

Consider three documents:

  1. A scanned passport application form - dense numbered fields, checkboxes, bilingual labels, fixed layout
  2. A hardware operator manual - two-column text, embedded diagrams, product specification tables, table of contents with dot leaders
  3. An academic paper - multi-column layout, inline formulas, citation blocks, figure captions

Running a plain OCR engine on each page would produce text, but:

  • Form field 3’s label would merge with field 4’s value
  • Table columns would collapse into a single line of garbled characters
  • Figure captions would attach to the wrong diagram
  • Formulas would be handed to downstream processing as ordinary prose

Document intelligence prevents these failures by identifying structure before text is grouped for processing.

The Document Understanding Stack

A robust understanding layer consists of five components:

1. Layout Analysis (vision model)        ← Vision:    what regions exist?
2. Table Detection (OCR in table ROI)    ← OCR:       where are cell boundaries?
3. Paragraph Finding (spatial grouping)  ← Structure: what is a paragraph?
4. Styles & Formulas (expression detect) ← Semantics: what must not be altered?
5. Term Extraction (LLM glossary build)  ← Meaning:   what terms matter?

Each component operates on an Intermediate Language (IL) - a per-character representation with bounding boxes, fonts, and page geometry. Nothing in this stack produces final output; everything enriches the IL so downstream transformation and typesetting can make informed decisions.

Layout Analysis with a Document Layout Model

Model Architecture

Document layout models such as DocLayout-YOLO are object detection models trained on document layout datasets. A common deployment pattern is to export the model to ONNX and run it via ONNX Runtime on CPU:

class OnnxLayoutModel(DocLayoutModel):
   @staticmethod
   def from_pretrained():
       pth = get_layout_onnx_model_path()
       return OnnxLayoutModel(pth)

Preprocessing Pipeline

  1. Render page to image via a PDF library (respecting rotation)
  2. Resize longest edge to the model’s input size (e.g. 768 px), pad to square
  3. Normalize pixel values (ImageNet mean/std)
  4. Batch pages for inference
  5. Post-process: NMS, confidence threshold, coordinate rescaling

Detection Classes and Their Uses

ClassDownstream ImpactparagraphDefines text block boundaries for groupingheadingTriggers heading-specific typesetting (larger font, spacing)tableTriggers the table parser with targeted OCRlistPreserves bullet/number structurefooterIdentifies running footers (often skipped or handled separately)figure_captionGroups caption text with figure referencestitleDocument/chapter title detection

Layout-First Reading Order

Without layout analysis, text extraction follows PDF content stream order - which often differs from visual reading order. Layout regions define the correct grouping:

Page with two columns:
   Without layout: "Introduction Methods Results Discussion" (garbled)
   With layout: Col1: "Introduction Methods"
       Col2: "Results Discussion"

The paragraph finder respects layout region boundaries and never merges text across region edges.

Remote Layout Services

For GPU-accelerated inference, layout detection can be exposed as a remote RPC endpoint. Some deployments also return an OCR match score for quality feedback. This separates CPU-bound parsing from GPU-bound vision inference - a common production pattern for burst workloads.

Table Detection and Extraction

Tables are the most layout-sensitive element in documents. A transformed table that loses its grid structure is worse than an untouched one.

Two-Stage Table Pipeline

Stage 1: Region detection - the layout model identifies table class regions on the page.
Stage 2: Cell OCR - a lightweight OCR model runs only within detected table bounding boxes:

class TableOcrModel:
   # Uses a compact text-detection ONNX model
   # Detects text lines within the table ROI
   # Converts detections to layout-result format for IL integration

This targeted approach avoids running OCR on the entire page. Only table regions - typically 5-15% of page area - get cell-level detection.

Form Table Handling

Government and institutional forms often use table-like layouts for field grids. The form pipeline adds specialized logic:

def explode_form_table_rows(paragraph, layout_region):
   """Split a form table row into individual field labels."""
def clamp_form_line_paragraph_boxes(paragraph, region):
   """Prevent form labels from overflowing their grid cells."""
def assign_form_horizontal_option_boxes(paragraph, options):
   """Map checkbox/radio options to their label paragraphs."""

Form tables differ from data tables: each cell is a label-value pair, not a data row. The paragraph finder must explode rows into individual processable units while preserving the grid geometry for typesetting.

Typesetting Constraints for Tables

Form and table regions benefit from dedicated scale floors in typesetting:

FORM_SCALE_FLOOR = 0.65        # Minimum font scale for form labels
FORM_TABLE_SCALE_FLOOR = 0.55  # Even tighter for dense form grids

Without scale floors, rewritten labels (often longer than the source) shrink to illegible sizes to fit original cell dimensions.

Form Detection and Processing

Forms are the most challenging document type for transformation systems. They combine fixed layout, dense labels, checkboxes, and legal text that LLMs often refuse to process.

Automatic Form Detection

Before pipeline selection, sample the first couple of pages and run heuristic detection:

FORM_MARKERS = (
   "application form", "passport", "for official use only",
   "date of birth", "surname", "given names",
   # ... dozens of markers across multiple languages
)
def looks_like_form_pdf(path: Path) -> bool:
   text = extract_pdf_text_sample(path).lower()
   marker_hits = sum(1 for m in FORM_MARKERS if m in text)
   numbered_fields = len(NUMBERED_FIELD_RE.findall(text))
   checkbox_density = text.count("□") + text.count("☐")
   if marker_hits >= 3: return True
   if marker_hits >= 2 and numbered_fields >= 4: return True
   if numbered_fields >= 8 and checkbox_density >= 2: return True
   return False

Detection triggers a form pipeline profile with:

  • Minimum text length of 1 (process even single-character labels)
  • A form-specific system prompt (anti-refusal, preserve field structure)
  • Form typesetting with scale floors
  • Token protection for field numbers, dates, and IDs

Form Processing Challenges

ChallengeSolutionLLM refuses to process legal textForm-specific system prompt + commentary rejectionInvented [[WORD]] placeholdersUnwrap hallucinated bracket wrappers in post-processingOverlapping label textForm paragraph box clampingCheckbox symbols alteredToken protection for □, ☐, ☑Numbered field labels mergedForm table row explosionDense layout overflowForm scale floors in typesetting

Testing Form Pipelines

Form-specific tests should verify:

  • Marker detection accuracy across languages (e.g. Spanish, German, English passport forms)
  • Paragraph explosion preserves field boundaries
  • Typesetting respects scale floors
  • No LLM commentary leaks into output

Formula and Style Detection

Not all text should be transformed. Mathematical expressions, chemical formulas, code snippets, and styled inline spans need detection before the LLM call.

Expression-Level Formula Detection

The styles-and-formulas stage identifies:

  • Superscript/subscript spans (exponents, ion charges)
  • Mathematical operators and delimiters
  • Formula font patterns (Computer Modern, STIX, Cambria Math)
  • Corner marks and curve/form overlap regions

def is_formula_font(font_name: str) -> bool:
   """Detect math-specific fonts."""
def detect_inline_math_boundary(chars) -> tuple[int, int]:
   """Find start/end of inline math expression."""

Detected formulas become formula objects with {vN} placeholders. The LLM receives E=mc{v1} instead of E=mc², and the placeholder is restored during typesetting.

The Inline-vs-Formula Decision

A critical engineering decision: when is a token a math expression vs. a product code?

InputClassificationReasonE=mc²FormulaMath operators, formula fontT15Text (inline)Short alphanumeric product code04XText (inline)SKU fragment1–4Text (inline)Numeric range, not mathLiPo 2SMixed"LiPo" = protected token, "2S" = inline text∫f(x)dxFormulaIntegral notation, formula font

Getting this wrong produces the most visible output bugs. An explicit should-inline-formula-as-text decision function should encode these rules based on production experience with operator manuals and technical documents.

Rich Text and Style Preservation

Documents with mixed formatting (bold terms, italic emphasis, size changes) need style capture per character. Rich-text processing preserves inline styles through the transformation:

  • Source: “The X100 controller supports OpenFW firmware.”
  • With placeholders: “The [[STYLE_BOLD_START]]X100[[STYLE_BOLD_END]] controller supports [[STYLE_ITALIC_START]]OpenFW[[STYLE_ITALIC_END]] firmware.”

Rich-text processing should be disabled in OCR workaround mode (scanned documents) because styled span reconstruction is unreliable on image-layer text.

Image and Vector Graphics Handling

Preservation Strategy

Images and vector graphics are preserved from the original PDF - they are not re-rendered or re-OCR’d. The pipeline:

  1. Extracts embedded images during IL creation
  2. Preserves image coordinates in the IL
  3. Keeps original content streams for vector graphics
  4. Overlays processed text on top (not replacing images)

Image-Adjacent Text: The Hidden Problem

Text rendered as part of an image (diagram labels, screenshot annotations, watermark text) does not appear in the IL character stream. It bypasses the entire processing pipeline.

Detection signal: text appears in the final PDF but not in the pipeline’s processing log.

Common examples from production:

  • Model names printed on product photos
  • Axis labels (Pitch, Throttle, Roll, Yaw) embedded in diagram images
  • Running headers baked into page background images

Fix location: post-render cleanup, not the core pipeline. These require pattern-based replacement in the rendered PDF content streams.

OCR Workaround Mode for Image-Heavy Documents

When the vast majority of pages (e.g. >80%) are scanned images:

  1. The original page image is preserved as background
  2. The native text layer is cleared (unreliable extraction)
  3. Processed paragraph text is overlaid with a fill background
  4. Rich-text processing is disabled

This produces a readable output document where the original visual layout (photos, diagrams, logos) remains intact.

Term Extraction and Semantic Understanding

Automatic Glossary Building

Before main processing, an LLM pass extracts key terms from the entire document:

[
   {"src": "FPV goggles", "tgt": "<consistent target rendering>"},
   {"src": "bind procedure", "tgt": "<consistent target rendering>"},
   {"src": "LiPo battery", "tgt": "<consistent target rendering>"}
]

This stage can consume a significant share of pipeline time (~30% in some systems) but ensures:

  • Technical terms are handled consistently across hundreds of pages
  • Proper nouns are identified and protected
  • Domain vocabulary is available in every paragraph’s LLM prompt

Fast Glossary Matching

User-provided glossaries are enforced with a compiled pattern-matching engine (e.g. Hyperscan):

# Builds a compiled pattern database from glossary entries
# Matches terms in source paragraphs before the LLM call
# Injects enforced renderings into prompt context

For a legal contract with 200 defined terms, compiled pattern matching adds milliseconds. Linear scanning would add seconds per page.

Nomenclature and Index Sections

Technical documents often include symbol-definition lists:

FPV  - First Person View
ELRS - ExpressLRS
CRSF - Crossfire
LiPo - Lithium Polymer

These sections need special handling in text flow reconstruction:

DEFINITION_HEADING_RE = re.compile(r"(?i)(nomenclature|symbols|abbreviations|glossary)")
DEFINITION_ROW_RE = re.compile(r"^([A-Z]{2,})\s*[-–—]\s*(.+)$")

Known pitfall: two-column symbol lists can merge into one paragraph when proximity-based grouping crosses column boundaries. The usual fix path is line-level processing or improved table detection for nomenclature sections.

Table of Contents: A Structural Extraction Case Study

TOCs are deceptively complex. They combine hierarchical numbering, dot leaders, page references, and multi-line titles - all of which break during transformation.

TOC Failure Modes

FailureCauseExampleMerged page numbersLong rewritten title joins next entry’s number"372.3" instead of "37 ⋯ 2.3"Dot leader fragmentsLeaders become separate paragraphs". . . ." as a standalone text blockMissing section numbersOCR drops numbers during extraction"8.1" becomes "8 1" or missingDuplicated numbersOCR and the LLM both add numbers"818.1"Unprocessed entriesShort entries skipped by minimum text lengthSource-language titles left in the output TOC

Two-Layer TOC Fix

Processing layer: a TOC normalization pass collapses dot-only leader fragments, compresses runaway dotted leaders, and restores known missing section numbers.

Render layer: post-render cleanup detects TOC pages structurally (early-page position, numbered entries, dotted leader patterns) and redraws with measured layout:

  • Section/title text: left-aligned
  • Subsection rows: indented
  • Page references: right-aligned at a consistent x-position
  • Dotted leaders: fill the exact remaining width between title and page number

This produces visually justified TOCs that match the source document’s appearance while containing correctly processed entries.

Document Profile Routing

The understanding layer should behave differently based on document profile:

ComponentStandardPaperFormScan detectionSkipped (fast mode)EnabledEnabledFormula detectionBasicFullMinimalAuto-glossaryOffOnOffTable parserOnOnOn (form-aware)Min text lengthDefaultDefault1TypesettingAuto-fit fillAcademic spacingForm scale floorsSystem promptAnti-refusalAcademicForm-specific

Profile resolution:

def resolve_pipeline_type(requested, input_path, *, file_type="pdf"):
   if requested in {"paper", "form"}:
       return requested
   if file_type != "pdf" or input_path is None:
       return "standard"
   if looks_like_form_pdf(input_path):
       return "form"
   return "standard"

Users can force a profile via API/CLI, or rely on automatic detection.

Extraction Quality Metrics

How do you know document understanding is working? Measure these:

Layout Quality

  • Region coverage: Do detected regions cover >90% of page text area?
  • Class accuracy: Manual spot-check of 20 pages - are headings/tables/paragraphs correct?
  • Reading order: Extract text in layout order - does it read naturally?

Table Quality

  • Cell count: Compare detected cells vs. visual inspection
  • Column alignment: Do processed table columns maintain grid structure?
  • Header preservation: Are header rows identified and styled differently?

Form Quality

  • Field detection rate: What percentage of numbered fields become individual paragraphs?
  • Checkbox preservation: Are checkbox symbols left untouched?
  • Overflow rate: What percentage of labels exceed their cell boundaries after processing?

Formula Quality

  • False positive rate: Product codes incorrectly classified as formulas
  • False negative rate: Math expressions handed to the LLM unprotected
  • Inline accuracy: Short codes correctly inlined vs. placeholder-protected

Track these metrics per document profile. Form documents will have different thresholds than academic papers.

Conclusion

Document intelligence transforms OCR from a character recognition problem into a structure understanding problem. The components - layout analysis, table detection, form processing, formula classification, and term extraction - each address a specific class of document complexity that raw OCR cannot handle.

The engineering patterns in this handbook come from production experience processing operator manuals, government forms, academic papers, and legal documents across dozens of languages. The recurring theme: understand document structure before transforming content.

OCR tells you what characters exist. Document intelligence tells you what they mean and how they relate to each other. Build the intelligence layer first - output quality follows from understanding.