Skip to content

A table is the stretch of page that every row of it leaves empty - #18

Merged
tannevaled merged 1 commit into
mainfrom
a-table-is-the-gap-every-row-shares
Oct 11, 2026
Merged

tannevaled merged 1 commit into
mainfrom
a-table-is-the-gap-every-row-shares

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Tables reports the grids a page's text lines up into — the capability behind
BentoPDF's extract-tables and pdf-to-csv, which nothing in the fleet's 2033
repositories had.

A page holds no table. It holds text put at places, and a table is what a
reader sees when those places line up, so this reports an inference and
there is nothing in the file to check it against.

The one decision

A column boundary is a stretch of the page that every row of the table leaves
empty
. The first line of a block proposes its own gaps, each line after it
takes away whatever it draws in them, and a line that leaves nothing closes the
block and begins the next.

⛔ Subtraction, not the intersection of the lines' gaps — which is where
this started. A cell whose text wraps leaves a line with one piece of text on
it and therefore no gaps of its own; intersecting with nothing ends the table,
and a four-row table with one wrapped cell came back as two tables of two
rows
. Subtracting asks the question that was meant all along — is anything
drawn there — and a wrapped line sitting inside a column draws nothing in the
boundary beside it.

Three things that are not columns

Each was found by running this over real documents, not over a fixture.

a bullet a bulleted list is a two-column grid. What tells them apart is that one column says the same thing wherever it says anything — and a column like that distinguishes nothing. One or two characters, repeated, is a mark; CNRS repeated down a column is data and is kept.
a column nothing was drawn in that is a stretch of page nothing was drawn in, which is what a boundary already is. A run of nothing but spaces splits a boundary and leaves an empty column between the halves.
a line far below the row above a page number sits in the left-hand column and draws nothing in any boundary, which is exactly what a row of a table does. What says it is not one is the distance.

Measured on one 213-page document as each went in:

588 tables, 1841 rows   gaps the rows share
213 tables, 1631 rows   + boundaries the rows leave empty (wrapped cells)
 83 tables,  268 rows    + a repeated mark is a bullet
 40 tables,  120 rows     + an empty column is not a column, and a page
                            number is not a row

The forty are that document's Code / Intitulé pairs, one per job
description, second cells wrapped. A 3-page prose report in the same run
reports none
, which is the control: a detector that cannot say no to a
paragraph says yes to every page there is.

⛔ What it does not do, said in the README

Ruled lines are not read. This is the text strategy — the one a bordered
table does not need — so a table whose columns are held apart by rules rather
than by whitespace is not found. And a page laid out in two columns of prose is
a grid too; what saves the ordinary case is that prose does not stay out of its
gutter for long, since one full-width line closes the block.

How it is checked

100% of statements with zero uncovered blocks in the profile, and 31
mutations run against it — every one caught except two that are equivalent
rather than uncovered, and the code says which and why. The new entry point
went into the fuzz target, since it compares coordinates the document chose and
a comparison is the one operation that will not complain about an infinity:
7.2M executions, no crash.

Two instrument failures on the way here, both worth naming: a uniform result
across heterogeneous mutations is a broken instrument, and I got the opposite
uniform answer twice — once because the binary path was a directory, once
because zsh does not word-split an unquoted variable, so mutate received one
unexecutable argument. The run that counts is calibrated against a mutation
that must be caught and one that must survive.

🤖 Generated with Claude Code

Tables reports the grids a page's text lines up into: the capability behind
BentoPDF's extract-tables and pdf-to-csv, which nothing in the fleet had.

A column boundary is a stretch every row leaves empty. The first line of a
block proposes its own gaps, each line after it subtracts whatever it draws in
them, and a line that leaves nothing closes the block.

Subtraction and not the intersection of the lines' gaps, which is where this
started: a cell whose text wraps leaves a line with one piece of text on it and
so with no gaps of its own, so intersecting ended the table there. A four-row
table with one wrapped cell came back as two tables of two rows.

Three things are not columns, each found by running this over real documents
rather than over a fixture, and measured on one 213-page document as each went
in: 588 tables, then 213, then 83, then 40.

  a bullet                 a bulleted list IS a two-column grid; what tells
                           them apart is a column that says the same thing
                           wherever it says anything
  a column nothing was     that is a stretch of page nothing was drawn in,
  drawn in                 which is what a boundary already is
  a line far below         a page number draws nothing in any boundary, which
                           is exactly what a row of a table does

The forty are that document's Code/Intitule pairs. A 3-page prose report in
the same run reports none, which is the control.

100% of statements with no uncovered blocks, 31 mutations caught, and the new
entry point added to the fuzz target: 7.2M executions, no crash. Said in the
README: ruled lines are not read, so this is the text strategy only, and a page
in two columns of prose is a grid too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit e0227bd into main Oct 11, 2026
1 check passed
@tannevaled
tannevaled deleted the a-table-is-the-gap-every-row-shares branch October 11, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant