Repository navigation
Table: text clipped at a cell border is assigned to the neighbouring cells #4762
Description
Bug
Some PDF producers fit long text into a table cell by clipping it to the cell's box: the page shows only the part that fits, while the text layer keeps the whole string, running underneath the next column. Docling moves the hidden part into the next columns, next to the values that really are there. And when the clipped text runs into the first word of the next cell, the two are glued together, which can take the clipped text out of its own column altogether.
The attached clipped-table.pdf is a small example: a ruled table with the columns Code, Description, Unit and Amount, in which two codes and two descriptions are longer than their columns and are clipped at the cell border.
In row 1 the page shows Replacement of the ventilation u; Docling puts unit, roof under Unit and level under Amount, and glues the code AB-1001-XYZ to Replacement.
The clipped text is content and should stay in the output, in full: a producer clips cell text to make the table fit, not to hide it. It should end up in the cell whose clip it is drawn in.
Steps to reproduce
clipped-table.pdf is attached: clipped-table.pdf. Every cell's text is drawn inside q <cell> re W n … Q; rows 1 and 3 overflow their cells. The script that generates it (standard library only) is in docling-project/docling-parse#404.
The table in text, with ▌ added here to mark where a cell clips its text (the PDF has no marker; the text simply stops at the cell border, as in the image above):
| Code | Description | Unit | Amount |
| AB-1001-XY▌ | Replacement of the ventilation u▌ | pcs | 1 |
| AB-1002 | Gutter cleaning | m | 120 |
| AB-1003-LO▌ | Repainting of window frames an▌ | m2 | 85 |
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode
from docling.document_converter import DocumentConverter, PdfFormatOption
for mode in (TableFormerMode.FAST, TableFormerMode.ACCURATE):
opts = PdfPipelineOptions()
opts.do_ocr = False
opts.do_table_structure = True
opts.table_structure_options.mode = mode
opts.table_structure_options.do_cell_matching = True
doc = DocumentConverter(
format_options={InputFormat.PDF: PdfFormatOption(pipeline_options=opts)}
).convert("clipped-table.pdf").document
for table in doc.tables:
for row in table.data.grid:
print([c.text for c in row])TableFormerMode.FAST:
r0: 'Code' | 'Description' | 'Unit' | 'Amount'
r1: 'AB-1001-XYZReplacement' | 'of the ventilation' | 'unit, roof pcs' | 'level 1'
r2: 'AB-1002' | 'Gutter cleaning' | 'm' | '120'
r3: 'AB-1003-LONG' | 'Repainting of window frames and' | 'fascia m2' | 'boards 85'
TableFormerMode.ACCURATE:
r0: 'Code' | 'Description' | 'Unit' | 'Amount'
r1: '' | 'AB-1001-XYZReplacement of the ventilation' | 'unit, roof pcs' | 'level 1'
r2: 'AB-1002' | 'Gutter cleaning' | 'm' | '120'
r3: 'AB-1003-LONG' | 'Repainting of window frames and' | 'fascia m2' | 'boards 85'
The structure is right in both modes: four columns, four rows. Only the text is in the wrong cells. Row 2, which nothing overflows, is read correctly.
Expected
r0: 'Code' | 'Description' | 'Unit' | 'Amount'
r1: 'AB-1001-XYZ' | 'Replacement of the ventilation unit, roof level' | 'pcs' | '1'
r2: 'AB-1002' | 'Gutter cleaning' | 'm' | '120'
r3: 'AB-1003-LONG' | 'Repainting of window frames and fascia boards' | 'm2' | '85'
Cause
The table structure stage hands TableFormer the word cells inside the table (table_structure_model.py#L239), each with its glyph box (L260). The cell matcher gives each word to the predicted cell it overlaps most (tf_cell_matcher.py, _intersection_over_pdf_match). The glyphs of a clipped text's hidden part lie under the next column, so that is where they go.
The information that would put it right, the clip the text was drawn under, does not reach Docling: docling-parse tracks it per glyph for rendering, but its text cells don't carry it, and it merges words across it (AB-1001-XYZReplacement). That part is docling-project/docling-parse#404.
Proposal
This is a suggestion, not a design; the right solution is yours to choose. One way to get there, once text cells carry their clip box: when a word was drawn under a clip, match it on the clip box rather than its glyph box. In a table the clip is the cell the producer drew, so every word of Replacement of the ventilation unit, roof level would match Description, the full text would land there, and nothing would be cut.
If that is the route, the token bbox in table_structure_model.py looks like the narrowest place, since the matcher would not have to change.
In the wild
The Jasper County, Missouri, 2024 Proposed Budget, page 11, the "Budget Comparison Report" table: its Description column is clipped at the border of the "2021 Total Activity" column, and Docling puts the hidden ends into that column (194,339.87 Enforcement). The same happens on other pages of the document.
https://www.jaspercountymo.gov/_files/ugd/1b6863_5a7be3d5495b4da391f152ad73f1ad00.pdf
We also see this in PDFs that one of our customers' applications writes with the wPDF library (Producer wPDF3). It clips only the cells that overflow, each with a rectangle the size of the cell. We can't share those; the attached example has the same structure.
Impact
We would like to use the tables Docling produces from these documents, and currently we can't. Some of them are large, and a single clipped value is enough to put text in the wrong cells, so the whole table becomes unusable.
Docling version
- docling 2.137.0,
- docling-core 2.101.1,
- docling-ibm-models 4.0.3,
- docling-parse 7.22.2 (PyPI, macOS arm64).
Python version
3.12
Activity
PranavRKris commentedon Oct 11, 2026
Hi @bartkummel
I would like to take a look at this issue