Skip to content
Technical, but readable

How PDF2Sheet Extracts Tables — And Why Nothing Is Uploaded

No black box. This page explains exactly what the converter does with your file, what it can and cannot recover, and how to verify for yourself that no data leaves your browser.

Step 1 — read the text, and its position

The first thing that happens is that PDF.js, the open-source PDF engine behind Firefox's built-in viewer, parses the file. Importing it from this site's own /vendor folder is the only third-party code that runs.

PDF.js returns a list of text runs. Each run carries a string and a transform matrix — in practice, an x and y coordinate and a font size. That is the raw material:

str: "POS PURCHASE - WHOLE FOODS MKT" x: 130.0 y: 214.5 width: 132.4 height: 8.0

Note what is not in that list: rows, columns, borders, or any idea that these runs belong together. A PDF is a page description, not a data format. Everything after this point is reconstruction.

Step 2 — group runs into rows

Runs whose baselines sit within about a third of the font size of each other are treated as one line. The tolerance scales with the text: a 20 pt headline and an 8 pt caption still count as the same line if their baselines are two points apart, but two lines of 8 pt body text twelve points apart do not.

Inside a line, runs separated by less than roughly four-tenths of an em are joined — that is just a space in the same cell. Wider gaps stay as separate cells.

Step 3 — find the columns

This is where most converters go wrong, so it is worth being specific about what this one does. It clusters the left edge of every cell into column anchors, and assigns each cell to its nearest anchor.

Three consequences follow, and they are the reason this approach was chosen:

Anchors need a minimum amount of support to exist, so a single stray label floating in whitespace does not create a column of its own.

Step 4 — decide what is actually the table

A page has furniture: bank name, address block, page numbers, legal text. The converter keeps the densest run of numeric rows — real table bodies contain amounts and quantities, letterheads do not — and pulls in the header line directly above it. Everything else is dropped when Only export the table is on.

Step 5 — clean up the values

Finally each cell is examined. Amounts become numbers: brackets mean negative, a trailing minus means negative, both 1,234.56 and 1.234,56 are understood, and ISO currency codes in front of a value are stripped. Identifiers that merely contain digits — account numbers, invoice numbers, ZIP codes with a leading zero — are deliberately kept as text so their leading zeros survive.

Why not OCR? Optical character recognition guesses characters from pixels. On a crisp digital PDF it is strictly worse than reading the text that is already there: slower, less accurate, and it needs a rendering engine. On a genuine scan it is the only option — but the results need a human to check every line, so this tool does not pretend to offer it. When it detects a scan, it says so.

How to verify that nothing is uploaded

You do not need to trust this page. Two minutes with your browser's developer tools settles it.

  1. Open the Network panel

    Press F12 (or Cmd+Opt+I on a Mac), choose the Network tab, then reload the page so you start from a clean list.

  2. Clear the list, then convert a file

    Click the clear button in the panel, then drop a PDF into the converter above. Watch the list as it runs.

  3. Read what appears

    You will see this site's own files: the HTML, the stylesheet, app.js, engine.js, the PDF.js worker, the spreadsheet writer. No request carries your document. There is no POST, no multipart/form-data, no WebSocket.

The same check works offline: load the page, then disable your network adapter, and convert a file. It still works, because the conversion never needed a network in the first place.

Known limits, stated plainly

What the code is

The extraction engine is a single dependency-free JavaScript file of roughly 600 lines: row clustering, column anchoring, table detection, number parsing. PDF.js and SheetJS are used unmodified. There is no analytics script on tool pages, no tracking pixel, no cookie set by the converter.

FAQ

Does the PDF.js worker make a network request?

Once, to load the worker script from this site — the same kind of request as loading the page's CSS. It carries no document data; it is the code that does the parsing. After that the file is processed entirely in memory.

Could a future version start uploading files?

It could, which is exactly why the verification steps above are on this page. Any change that phoned home would be visible in your Network panel immediately. That is a stronger guarantee than a privacy policy sentence.

Why does a large file take a few seconds?

Parsing is CPU work on your machine rather than a server's, so a 200-page statement takes a moment. Nothing is being uploaded, which is why the progress bar moves at the speed of your processor rather than your connection.

Is the code open source?

The engine is readable in your browser's Sources panel — it is served unminified at /assets/js/engine.js. Read it, or run it yourself in Node; it is a UMD module and works outside the browser.

Try it on a statement