truecopy1.0.0

TypeScript · MIT · runs in the browser

Turn a document into rows,
and refuse the readings you cannot trust.

A table that looks right is never checked. So this one tells you when it is not — the document has to confirm the reading before the reading counts.

A PDF, a table pasted out of one, a CSV. One engine reads all three — only the ruler changes, and the reading says which one it used.

npx truecopy a-statement.pdf

The demo runs the library in your browser. Nothing you drop leaves the page.

explainDocument(document)
truecopy - statement.pdf
pdf, 1 page(s), 6 row(s)

page 1 - cut at 100, 290

3 column(s), 6 row(s)

col  kind        filled  role
  0  date           83%  when
  1  text          100%  -
  2  amount        100%  money

   1   02/05/2026 | AMAZON       | 12,40
   2   03/05/2026 | LOYER MAI    | 750,00
   3   05/05/2026 | SNCF         | 68,00
   4   09/05/2026 | EDF          | 91,32
   5   12/05/2026 | BOULANGER    | 7,90
   6 !            | TOTAL DEBITS | 929,62   <- column 0 (date) is empty

The total row is marked because it breaks what every other row does — no list of forbidden words, no header to read.

The second field

Every extractor returns rows. This one returns what it could not vouch for.

An empty warnings is not a promise that the reading is right. It says nothing looked wrong from the shape of the page — a much smaller claim, and an honest one. Every warning is computed without knowing anything about your document.

Read the quickstart →

import { readTable } from 'truecopy';

const { rows, warnings } = await readTable(file);

// rows
// [['02/05/2026', 'CARTE AMAZON', '12,40'], …]

// warnings
// ['column 3 of page 2 is filled on only 8%
//   of its rows - the cut may have invented it']

No kindOf, no thresholds, no roles. Two lines.

One engine, three rulers

The cut does not care what measured the page.

It votes on which left edges come back row after row — and that question is the same whichever ruler measured them. So a document only has to say where its fields start, and the same two lines read all of these:

What was droppedWhere a column startsWhat the reading prints
a PDFthe item's x, in pointscut at 100, 290
a table pasted with spacesthe character the field starts atcut at characters 7, 22
a file written with a delimiterthe field's index — a CSV lines nothing upcut on the delimiter
prose, or a file that quotes its fieldsnowhereevery row came back whole

That last row is the rule and not the exception. A quoted CSV field may hold the delimiter itself, so splitting on it anyway would shift every column after it — the rows come back uncut instead, because half-parsing is the failure this library exists to prevent.

Three things it does that others do not

Built for the readings somebody acts on.

The document checks itself

A statement carries opening + Σ = closing. A career record announces its own total. Set the reading against what the document declares and you have the only check that depends on no layout — and the one that catches a misread before anybody acts on it.

The five laws →

Every value knows where it came from

A value you cannot point at is a value nobody can check. placesOf(page) is laid out exactly like cellsOf(page): same indices, one gives the text, the other gives the page and the coordinates.

Provenance →

No configuration per issuer

A column full of dates is the date column, whatever its header says. A row that breaks what every other row does is a total, a balance, a footer. It reads no label, so it survives an issuer you have never seen.

The learned signature →

Honest about the limit

It does not promise to read an unknown layout correctly.

It promises to tell you when it did not. Measured on a real statement whose header labels were hidden: the page-wide cut proposed twelve columns where the table had five, and the reading came back wrong. The only reason that was visible is that the rows did not add up to the balances the document declares.

How that compares to the alternatives →

When truecopy is the wrong tool

  • You need a picture of the table, not its meaning. pdfplumber and camelot have years of tuning you will not beat.
  • Your documents are scans. This reads a text layer. Run OCR first, then hand the lines over.
  • Nobody acts on the result. If a wrong row costs nothing, all this machinery costs you something for nothing.

Find out in thirty seconds whether it reads yours.

npx truecopy a-statement.pdf