Published 2026-09-21. Updated 2026-09-21. · By Blake Crosley · Who makes it

PDF or Word: what a parser receives from each

What a resume parser receives from a PDF and from a Word file, from its code and tests: columns, tables, text boxes, headers, image pages, and what each costs.

The question is usually asked as "which format do applicant tracking systems prefer", and that question has no honest general answer: each vendor's parser is its own program, and we hold none of their documentation. This page answers a narrower question with evidence: what Jini's own parser receives from a PDF and from a Word file, where the formats differ, and what each hazard costs in the structure readout. The parser is open in the repository, and the tests named below run in its gate.

What a PDF gives

A PDF carries a text layer, and the parser reads that layer word by word with each word's position on the page, then groups words into lines by their vertical position and reads the lines top to bottom.[1] A run of words separated from the next by a gap much wider than the words around it is treated as a separate block, which is how two columns come out as two blocks and are flagged as a multi-column layout.[1:1] The parser also sees how many images each page holds and how many characters it read from it: a page with images and under twenty characters of text is an image-only page, and a file whose pages are all image-only, or that carries under two hundred characters in total with no page above one hundred, is refused as having no text.[1:2] A password-protected file is refused as encrypted, and a file over thirty pages is refused before extraction.[1:3]

What a Word file gives

A Word file has no pages until it is printed, so the parser reads its structure instead of its layout: the body's paragraphs and tables in the order they appear in the document, then the text boxes, then the headers and footers.[2] A table is read row by row with the cells joined in order, so a value can sit far from its label in the extracted text; every text box is read once, after the body, wherever the box sits, even inside a table cell or a header; each header and footer part a section shows (default, first page, even pages) is read once.[2:1] A field's cached text is read like any other text, but the field itself is not interpreted; a field, a drawing, or SmartArt in the body marks the document as carrying unsupported content, which the readout says, while the same inside a header or a footer is read as header text and not marked.[2:2] A file with under two hundred characters of text is refused as having no text.[2:3]

What each hazard costs

The structure readout starts at 100 and subtracts a fixed amount per hazard found, floored at zero, by formula version 1: a multi-column layout 25, an image-only page 30, a table 20, text in a header or footer 15, a text box 15, an unusual reading order 15, tiny type 10, more than two pages 10, and a section heading the parser could not place 5.[3] The methodology page lists the same constants and computes a worked example from them on every visit.[3:1]

So which format

For this parser, the format itself decides little; what the file does with it decides the hazards. A one-column PDF with a text layer comes out one block per line, in order.[1:4] A plain Word file with no tables, boxes, or header text comes out as its paragraphs, in order, with no hazard from those three; a hazard that remains is one any file can carry, such as tiny type, and a Word file never carries the many-pages hazard, because it has no page count until it is printed.[2:4] The layout hazards are things a template adds: columns, tables used as layout, a sidebar in a text box, contact details in the header, a scanned page. Check shows the extracted text beside your file, so you can see which of these your own file has before you choose.

What this page does not claim

What any vendor's parser does with either format. Whether a recruiter prefers either format. Whether the parsers used by employers apply deductions at all; the numbers above are this parser's and are published on the methodology page.


  1. jini/services/check/parser_child.py, the PDF path (pdfplumber words in text-flow order, lines by vertical position, runs split at wide gaps into blocks; the constants MAX_PAGES 30, IMAGE_ONLY_CHARS 20; a file is no_text when every page is image-only or its text totals under 200 characters with no page above 100; PDFPasswordIncorrect refused as encrypted). Tested 2026-09-21 in tests/check/test_parser_child.py (a one-column PDF stays one block per line; two columns are separate blocks and a hazard) and tests/parsing/test_layout.py (pages with images and no text are image-only pages; a PDF over two pages is many pages and a Word file never is). Unknown: PDFs with tagged structure, which the parser does not consult. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. jini/services/check/parser_child.py, the Word path: the body's children are walked in document order, a paragraph added as it comes and a table as its rows come (the tables limitation recorded); the outer text boxes after the body; the header and footer parts of each section once each; the text walker reads every run, so a field's cached result is read, and the presence of a w:fldSimple, w:fldChar, or w:drawing element in the body records unsupported_content (the scan walks the body only, so a field in a header is read as header text and not marked); page_count is none, so the many-pages hazard never applies to a Word file; the no-text rule is 200 characters. Tested 2026-09-21 in tests/check/test_parser_child.py (blocks carry provenance and a font size; text keeps its breaks and reads every header and each text box once; a text box inside a table cell is read once; a plain document has no layout hazards from tables, boxes, or headers) and tests/parsing/test_layout.py (a body set below nine point is tiny type; one small line in a long document is not; a PDF over two pages is many pages and a Word file never is). ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. jini/services/check/formula.py, formula version 1: DEDUCTIONS and the structure score floored at zero; jini/services/check/hazards.json for what each hazard means; /methodology, read 2026-09-21, renders the constants and the worked example from them on every visit. ↩︎ ↩︎

Guides