Skip to content

Table: text clipped at a cell border is assigned to the neighbouring cells #4762

Open

Description

@bartkummel

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.

Image

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

  1. PranavRKris commented on Oct 11, 2026

    @PranavRKris

    Hi @bartkummel

    I would like to take a look at this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions