Documents: OCR, Layout, Tables and Forms

By the Syncfusion DevTutorials team · Last updated

Invoices break extraction in four ways: merged cells shift columns, long tables get cut short, dates are read the wrong way round, and money stays as text. Fix each one, and extract ten invoices to a schema.

What Document Extraction Is

Document extraction = turning a page (an invoice, a form, a bank letter) into typed data: dates, amounts, rows of a table.

In this lesson, you will extract ten supplier invoices into a schema, and fix the four ways that invoices break extraction.

A French supplier sends an invoice. On page 1, one description cell is two rows tall. The model reads the table as text, row by row. After the tall cell, a price lands in the quantity column.

Every value is real. One is in the wrong place. Nothing looks broken.

Press Play, or use Next to go one step at a time.

  • Frame 1: the table as printed, with the merged cell.
  • Frame 2: what the LLM returned: the second row has shifted.
  • Frame 3: the fix: OCR's table cells sent with the page as hints.

Simple lesson: a table is a layout, not a line of text.

A Real-World Example

Think about copying a timetable from a poster into a spreadsheet.

  • You do not read it like a sentence. You read across each row and down each column.
  • Where one cell covers two rows ("Weekends"), you know it applies to both.
  • When the timetable continues on the next page, you keep adding rows in the same columns, and you skip the repeated header.

A model reading a page as text does none of this unless you help it:

  • Reading across and down = layout hints: where each cell is.
  • The next page = splitting the document into pages and joining the tables.
  • "Weekends" means Saturday and Sunday = per-field rules: how to read each field.

Simple lesson: give the model the structure a person sees without thinking.

What You Will Learn

By the end of this lesson, you will be able to:

  • explain why tables and multi-page documents break extraction
  • use layout hints from OCR to keep table columns in place
  • split a long document into pages and join its tables
  • use per-field rules for dates and money
  • write a Structure stage that turns text into typed values

This lesson covers three stages: Ingest (splitting pages), Perceive (the model and its hints) and Structure (the schema).

Four Ways Invoices Break

FailureWhat you seeFixStage
Column shiftA price in the quantity columnLayout hintsPerceive
Cut-short tableRows missing; the total is a page subtotalSplit pages, join tablesIngest
Wrong date order03/04 read as 4 MarchPer-field rulesPerceive
Text, not valuesThe total stored as "€2.418,60"SchemaStructure

You saw the column shift above. Here are the other three.

Long Documents Get Cut Short

Step through all three frames.

  • Frame 1: a 4-page invoice; the total is on page 4.
  • Frame 2: sent as one file, the model stops after page 2.
  • Frame 3: split into pages, read one by one, then joined.

A missing page is a silent error. The output looks complete: a list of rows and a total. Only a check against the page count shows that half of it is missing.

Dates Read the Wrong Way Round

Step through both frames.

  • Frame 1: 03/04/2026 from a UK supplier, read the US way.
  • Frame 2: a per-field rule fixes every date.

Text Is Not a Number

Step through both frames.

  • Frame 1: a euro total uses a point for thousands and a comma for cents.
  • Frame 2: without a schema, the text is stored; with one, it becomes a number.

Simple lesson: each failure has its own fix, in its own stage.

The Schema Is the Contract

A schema says what each field must look like:

  • date: a string like 2026-04-03 (YYYY-MM-DD)
  • total: a number
  • each line: a description, a number quantity and a number price

Structure turns the model's text into values that fit the schema. Anything that does not fit is an error, not data. It goes to a person (Lesson 5) instead of into your database.

Fix the failures one at a time and watch the score climb.

Step through all five frames.

  • The row of fixes: green = on.
  • The ring: key fields right out of 40.
  • The last frame: every fix on, every field right.

Click the button and look at the Pipeline tab.

  • The invoice list: ✓ or ✗ per invoice for the current settings.
  • The page: click Page 1, 2, … for multi-page invoices.
  • The table on the right: each typed field; open "What the model returned" to see the raw text.

Easy way to remember: layout for columns, pages for length, rules for formats, schema for types.

Lab Exercise: Extract Ten Invoices to the Schema

Your task: change the Ingest, Perceive and Structure settings until the invoices pass. Then press Check.

Click the button and open the Pipeline tab.

  • Ingest: split pages and join the tables.
  • Perceive: pick the model, layout hints and per-field rules.
  • Structure: validate against the schema.
  • To pass: at least 95% of key fields right (invoice number, date, total, line items), and every multi-page table complete.

There is more than one way to pass. Compare the cost of the passing settings in the line under the table.

Simple lesson: most document accuracy is won in the stages around the model.

Build It in TypeScript

The OCR + layout model returns every value as printed text. Your Structure stage turns that text into the schema's types.

function money(text: string, cur: "£" | "€"): number {
  const digits = text.replace(/[£€$\s]/g, "");
  // £1,234.50: comma for thousands.  €1.234,50: point for thousands, comma for cents.
  return Number(cur === "€" ? digits.replace(/\./g, "").replace(",", ".") : digits.replace(/,/g, ""));
}

export function toInvoice(raw: RawInvoice): Invoice {
  const [d, m, y] = raw.date.split("/"); // printed as DD/MM/YYYY
  return {
    number: raw.number,
    date: `${y}-${m}-${d}`,
    total: money(raw.total, raw.cur),
    lines: raw.lines.map(([desc, qty, unit]) => ({ desc, qty: Number(qty), unit: money(unit, raw.cur) })),
  };
}

Check the result too. In a real app, run the typed value through a schema library such as zod, so a bad value raises an error instead of quietly becoming NaN.

Click the button and open the Code tab.

  • The starter: copies the text as it is. Rewrite it.
  • Run on the media bank: your function runs on all 10 invoices' raw text.
  • To pass: every invoice fully right, including the euro ones.

Simple lesson: Structure is your code, and it is where text becomes data.

TL;DR

  1. Document extraction = a page into typed data.
  2. Column shift → send OCR's table cells as layout hints.
  3. Cut-short tables → split into pages, read each, join the tables, skip repeated headers.
  4. Wrong date order → a per-field rule: "dates are DD/MM/YYYY".
  5. Text, not values → a schema, and a Structure stage that parses each field.
  6. A schema error is not data. It goes to review.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Reading tables as text"The model sees the table."Send layout hints, or use a layout model.Values in the wrong columns.
Sending a long PDF in one call"It has the whole document."Split, read each page, join.Missing rows and a wrong total.
Trusting date order"Dates are dates."State the format for each field.Every date up to the 12th can flip.
Storing text as money"We can parse it later."Parse in Structure, against a schema.Sums break, or €2.418,60 becomes 2.4.
Counting the repeated header"It is just another row."Drop "Item (continued)" rows when joining.Extra line items on every page.

Check Your Understanding

Exercises

What to Remember

  • A table is a layout, not a line of text.
  • Layout hints keep columns in place.
  • Split long documents into pages, then join the tables.
  • Tell the model the format of each field.
  • Structure parses text into typed values, against a schema.
  • A value that does not fit the schema goes to review, not to your database.

Further Reading

Next: Screenshots, UI and Charts, where you turn error screenshots into complete bug reports and read charts honestly.