All writing

September 2, 2026 · 8 min read

How to extract text from Thai PDFs and scans

Why copying text out of a Thai PDF returns broken marks, junk letters, and unspaced phrases, and how Thai OCR turns the page into text, headings, and tables.

A Thai PDF gives up its text in one of two ways. A file with a text layer can be copied directly. A scanned file has to be read by OCR, which looks at the page as an image and writes the text back.

The catch is that the text layer of a Thai PDF often carries broken text, even when the page prints perfectly. Tone marks land in the wrong order, older fonts extract as Latin junk, phrases arrive as one unbroken token, and Thai and Arabic digits sit on the same line. This guide shows each failure with the string it produces, when OCR is the right tool, and what document OCR returns for a real Thai invoice.

The two kinds of Thai PDF#

A PDF exported from a word processor, an accounting system, or a browser carries a text layer. The glyphs on the page are drawn from fonts, and the characters behind them are stored in the file. Select all and copy, and something comes out.

A PDF from a scanner, a phone camera, or a fax gateway is a container for images. The page is pixels. Select all finds nothing, and search finds nothing, until a reader has turned the pixels into characters.

Two quick checks tell them apart. Search the file for a word you can see on the page, and if the search fails on a word that is clearly there, the page is an image. Drag to select a line, and if the selection highlights the whole page as one rectangle, or nothing at all, the page is an image. Many real Thai documents are mixed, with a typed form as a text layer and a stamped, signed, or annotated section as an image on top of it.

Why the text layer of a Thai PDF breaks#

English extraction has one common failure, the missing space between columns. Thai extraction has four, and each one produces text that looks plausible until a Thai reader or a database sees it.

Tone and vowel marks come out of order#

A Thai syllable stacks marks above and below its consonant. The word กุ้ง (shrimp) is four code points in Unicode order, and a correct text layer stores them in that order. A PDF producer that lays out glyphs one at a time can store the tone mark before the vowel, because the mark was drawn first. Some producers do not store the marks as characters at all and paint them as positioned artwork.

one word, four code points, three storage orders
กุ้ง   as written

U+0E01 ก   base consonant
U+0E38  ุ  vowel below
U+0E49  ้  tone mark above
U+0E07 ง   final consonant

ก ้ ุ ง     the same four, as one PDF stores them
ก ง         the same word, marks placed as art

Both cases extract as a string that renders almost right and matches nothing. A search for กุ้ง fails, a database that stores the string fails to join on it, and a downstream translation of it reads a different word. The page itself looks correct throughout.

Legacy fonts extract as Latin junk#

Thai documents from before Unicode was universal were set in TIS-620 and in fonts with their own private glyph maps. Those files still circulate, and government forms, contracts, and title deeds are reprinted from them. A PDF made from one has a text layer, and the bytes in it are Thai only to a reader that knows the code page. A generic extractor reads them as Latin-1 and produces the characters below.

a legacy font on the page and in the file
กรุงเทพ        on the page
A1 C3 D8 A7 E0 B7 BE   in the file (TIS-620)
¡Ãا෾         after extraction

The junk is deterministic, and a table can map it back for the TIS-620 case. Font-private maps have no table. The visible page is the only reliable copy of the text, and that is the definition of a scan.

There are no spaces between words#

Thai writes words together and spends the space character on phrase and clause breaks. A correct text layer of an invoice returns ใบแจ้งหนี้เลขที่ as one token. Any tool that splits on spaces, matches a word boundary with \b, or wraps at a fixed width is working on phrases it cannot see inside. The text is right and the tooling around it is wrong.

Two numeral systems share a line#

Thai documents use Thai digits (๑ ๒ ๓) for dates, quantities, and addresses and Arabic digits for money, in the same table, sometimes in the same cell. A date reads ๑๕ กรกฎาคม ๒๕๖๙, in the Buddhist Era. A quantity of sits beside an amount of 3,000.00. Latin runs are mixed in as well, from invoice numbers like INV-2569-0107 to product names. Extraction that normalizes digits, or that assumes one script per field, silently changes values.

When OCR is the right tool for a Thai PDF#

The fileWhat to do
Text layer, extracted text reads correctlyUse the text layer and keep the structure the producer stored
Text layer, marks out of order or missingTreat the page as an image and OCR it
Text layer, Latin junk from a legacy fontTreat the page as an image and OCR it
Scanned or photographed pageOCR
Typed form with a stamped or handwritten layerOCR the whole page, then compare with the text layer

OCR reads the page the way a person does, from the pixels, and is indifferent to how the file stored its characters. That makes it the right tool for every row above except the first. It is also the only tool that returns the stamped, signed, and handwritten parts of a mixed page.

OCR is the wrong tool for a clean text layer, where it costs a vision pass to recover text the file already holds. Run the two checks first.

What document OCR returns for a Thai page#

Character recognition returns a string. Document OCR returns the page. Paxa OCR Lite reads the whole page in reading order and returns it as Markdown, with headings and tables kept, or as typed blocks (heading, paragraph, list, table, figure) for code to consume. The lines below are the first lines of the committed reading of a sample Thai invoice, unedited.

the reading of a Thai invoice, first lines
# บริษัท ศตวรรษการค้า จำกัด (สำนักงานใหญ่)
เลขประจำตัวผู้เสียภาษีอากร 0 1055 64078 12 3

# ใบแจ้งหนี้
เลขที่ INV-2569-0107
วันที่ ๑๕ กรกฎาคม ๒๕๖๙

| ลำดับ | รายละเอียด | จำนวน | หน่วยละ | จำนวนเงิน |
| ๑ | ค่าบริการดูแลระบบรายเดือน กรกฎาคม ๒๕๖๙ | ๑ | 18,000.00 | 18,000.00 |
| ๒ | ค่าพื้นที่จัดเก็บข้อมูลส่วนเกิน | ๔ | 750.00 | 3,000.00 |

( สามหมื่นหกพันสามร้อยแปดสิบบาทถ้วน )

Three things in that excerpt are the ones that break text-layer extraction. The line items carry Thai digits for the running number and the quantity beside Arabic digits for the money, and both return as the digits printed. The date is in the Buddhist Era, and it returns as printed for the application to convert. The total in words, สามหมื่นหกพันสามร้อยแปดสิบบาทถ้วน, is one unspaced Thai string, and it returns as one line.

With output set to structured, the same page returns as typed blocks. The company name is a level one heading block, each label line is a paragraph block, and the line items are one table block whose rows are arrays of cell strings. Code reads the invoice number as the text after its label and the line items as the rows of that block, and no template per supplier is needed.

The table returns as a table. A reader that walks the page in pixel strips would flatten those five columns into one line per row and lose which number was the unit price. The full reading, and the sample page it was made from, are on the invoice page.

Checking a Thai OCR result#

A reading is verbatim, and a document can be wrong on its own terms. These checks catch most problems in a Thai business document before the values reach a system.

  • A Thai tax identification number is 13 digits. Strip the spaces and count. 0 1055 64078 12 3 passes.
  • Line amounts equal quantity times unit price, and the subtotal, VAT at 7%, and grand total add up. A reading that disagrees with itself has misread a digit.
  • A year above 2500 is Buddhist Era. Subtract 543 before storing it as a date. ๒๕๖๙ is 2026.
  • The number of table rows matches the count of line items on the page. A merged row is the most common table error on a dense invoice.
  • Thai digits and Arabic digits both parse as numbers. Convert them with a table. Hand conversion introduces errors.
  • The amount in words matches the grand total. It is the one field a person wrote to catch exactly this class of error.

Note

A tax invoice is a legal document with fields the Revenue Code prescribes, and a plain invoice is a commercial one. The tax invoice page lists the prescribed fields, which are the ones an audit will read back.

What Thai OCR costs and what it accepts#

The charge is per page. A page costs 6.5 credits, which is $7 per thousand pages, and the amount of text on the page does not change it. A new account starts with 100 free credits, which reads 15 pages.

One request takes a PDF, PNG, JPEG, or WebP of up to 10 MB and up to 50 pages. An image is one page. The page count is read from the file before the charge, and a PDF that wants a password is refused before any credit moves.

Accounts-payable teams that run every supplier invoice through this flow are described on the invoice processing page.

How to extract text from a Thai PDF today#

  • Run the two checks. If the text layer reads correctly, keep it.
  • Drop a broken or scanned page into the playground and read the Markdown back. No code is needed for the first page.
  • Choose markdown when a person or a language model reads the result next, and structured when code does.
  • Read the OCR guide for the request shape and the API reference for every field and error.
  • Run the checklist above on the first hundred documents, and keep the checks that fired.
  • The Thai OCR page holds the witnessed reading of a full document and the twelve document types with sample readings.