Skip to content

Tags: anshu-gautam/pdf-kit

Tags

first

Toggle first's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chunk: normalized table grid (cells, columns, colspan, per-cell bbox,…

… HTML/CSV) (#21)

Upgrades ElementKind::Table from tab-joined text to a real cell grid — the
field's most-cited extraction failure mode.

- pdfkit-core: a new `Cell { text, x0, x1 }` and `Line.cells`, built in lockstep
  with the line text in bucket_region. Purely additive — `text` and `gap_xs` are
  byte-identical, so reflow/Document::text and the #5 table detection are
  unchanged.
- pdfkit-chunk: a promoted table block is turned into a `Table { columns,
  header_rows, rows }` of `GridCell { text, bbox, col, colspan, rowspan }`.
  Columns are inferred by clustering cell *left edges* across rows (stable even
  when cell widths vary — unlike gap centers, which is why this is a distinct
  pass from gap-center detection). Each cell is placed in the leftmost slot it
  covers by >= half the slot width (colspan = slots covered); missing slots are
  filled with empty cells so the grid is rectangular. header_rows = 1 (first-row
  heuristic); rowspan is always 1 (true row spans need ruled-line parsing —
  TODO(design)). Chunk gains `table: Option<Table>` (serde, omitted when None).
- Serializers: grid-aware `to_markdown`, `Table::to_html` (`<thead>`, colspan
  emitted when > 1, escapes & < > " '), `Table::to_csv` (RFC 4180). Tables are
  now kept as distinct chunks so a chunk's grid always matches its text.

Found by the adversarial review and fixed here: to_html no longer drops a
non-empty cell that a preceding colspan covers (collision case); two stacked
tables no longer merge into one chunk with a stale grid.

Tests: table_doc -> 3x3 grid with correct texts/colspan/bbox; HTML/CSV/Markdown
shape; covered-non-empty HTML cell; two stacked tables stay separate; place_cell
colspan, CSV quoting and HTML escaping units. Existing m6/serialize/layout tests
unchanged.

cargo fmt/clippy clean; cargo test --workspace and the minimal
(--no-default-features --features render-native) build both pass.