$StatementProof

Guides

What's Actually Inside a Bank Statement PDF

By Tarun Vashishth · Published

A bank statement PDF downloaded directly from your bank's website is, almost always, not a picture of a document — it's a page of positioned text. Understanding that distinction explains both how a converter like this one works and exactly where it can't help.

Text layer vs. image

A native PDF — one generated directly by the bank's own statement software — embeds each character as a glyph with an exact position on the page: this letter at this x/y coordinate, in this font. A scanned statement is different: it's a photograph or scan of a paper document, stored as a raster image with no embedded text at all — visually a statement, but to software it's just pixels. The two look identical to a human eye and are fundamentally different to a parser. A tool that reads the text layer, like this one, can do nothing with the second kind — there's no text to read, only an image.

How text becomes rows

Reading a PDF's text layer (via pdf.js) returns every glyph run on the page along with its exact bounding box, but not organized into rows or columns — that structure is visual, not logical, in the file format. To reconstruct rows, glyphs are clustered by vertical position: tokens whose y-coordinates fall within a tolerance of each other (derived from the median glyph height on the page, so it adapts across statements at different font sizes) are treated as one visual row.

How rows become columns

Once rows exist, columns are found one of two ways. If the statement has a recognizable header row — "Date," "Description," "Amount," "Balance" or close variants — the header's own x-positions define the column boundaries for everything below it. If there's no clean header (common on statements that print section labels instead), the parser falls back to a geometric heuristic: dates tend to cluster in a narrow band on the left, amounts tend to be right-aligned in one or two bands on the right, and description text fills the space between. This is why the tool isn't limited to a fixed list of banks — the column detection works from layout, not a template matched to a specific bank's PDF.

What makes a row a transaction

Not every row of text is a transaction — statements are full of headers, section labels, page-number footers, and summary lines ("Total deposits: $4,200.00"). A row only becomes a transaction candidate if it has both a date token inside the date column and an amount token inside an amount/debit/credit/ balance column. A summary line matching patterns like "beginning balance" or "total withdrawals" is classified separately and excluded from the transaction list — read instead by the reconciliation step, which is what makes the opening/closing-balance check possible in the first place.

Bank-specific hints, and what happens without them

For a handful of profiled banks, the fingerprint text on the page (a phrase like "JPMorgan Chase Bank" or a domain like "wellsfargo.com") is matched to enable bank-specific section headings — recognizing "Deposits and Additions" as the start of the credit section, for example. Any statement that doesn't match a known fingerprint still runs through the same geometric row/column detection; it just doesn't get those specific hints. See supported banks for exactly which banks have profiles and what they add.

Where this approach hits a wall

Anything requiring optical character recognition — reading pixels and inferring what characters they represent — is out of scope for a text-layer parser by definition. That includes scanned statements, statements exported as flattened images, and photographs of a printed statement. There's no partial support here: either the PDF has a text layer to read, or it doesn't. If you're not sure which you have, try selecting text on the PDF in any normal PDF viewer — if you can highlight and copy the transaction text, it has a text layer and this tool can read it.

Related: the full parsing and reconciliation pipeline,why OCR-based converters make certain errors this approach doesn't.