Repository navigation
Conversation
Some producers fit long text into a table cell by clipping it to the cell's box (`q <cell> re W n ... Q`). The page shows the part that fits; the text layer keeps the whole string, whose hidden end runs under the next column. The clip was tracked for the renderer only, so the cells carried the full glyph boxes, the hidden tail got the box of the column it ran under, and the word contraction glued the tail to the first word of that column: `AB-1001-XYZ` + `Replacement` came out as one word. Every char, word and line cell now carries the bounding box of the clip it was painted under (`has_clip_bbox`, `clip_x0..clip_y1`, page coordinates; the intersection of the clip groups' boxes), computed once per glyph from the clip state the render instruction gets. It rotates with the page, is exposed through pybind, and reaches docling-core as `PdfTextCell.clip_bbox` when the installed model has the field. A word or line keeps the box when all of its glyphs share it. Contraction: two consecutive glyphs under different clip boxes, of which at least one is cut by its own clip, never join into one word or one line. A glyph counts as cut when the clip hides more than a fifth of its extent along the writing axis, or of its em size if that is larger; a clip box shorter than one em along that axis never cuts, since a cell holds at least one glyph while a clip drawn tight around the ink of a period, or a sliver clip under which a producer repaints a glyph, is narrower than the glyph. When only one of the two glyphs has a clip box, the other still belongs to it while it lies inside that box: some producers clip only the glyph that overflows the cell. Glyphs whole under their clips always may join, since producers also wrap every text run, or every glyph, in a clip that does not cut it. Each of these producer patterns is in the regression corpus. Clip paths are tracked even when shapes are skipped: path construction and W/W* run whenever shapes or clips are kept, painting operators register shapes only when shapes are kept. `DecodeConfig.keep_clips` (default true) turns the tracking off. docling computes shapes only when it renders pages, so without this the box would be missing from a parse with `render_pages=False`. Over the regression page selection (453 documents, 950 pages, dataset revision df881cc) the only pages whose words or lines change are tables with clipped cells. Refs: docling-project#404, docling-project/docling#4762 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
The sanitizer moves cells to the origin of the crop or media box but left
clip_{x0,y0,x1,y1} in raw PDF coordinates. On a page whose box does not
start at (0, 0) the clip box sat beside its glyphs, so the contraction
rule saw cuts that the page does not show (eac6df0b2a6f9cf0_0002.pdf,
11268045627910569931_002.pdf) and the cells reported a wrong clip_bbox.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
Contributor
|
✅ DCO Check Passed Thanks @wittjeff, all your commits are properly signed off. 🎉 |
Contributor
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
PeterStaar-IBM
self-requested a review
November 3, 2026 15:53
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Standalone topic per #428: a clip box on text cells, and a contraction rule that uses it. Two commits, rebased on
main(f4b12cf), no merge commits. This is the prototype proposed on #404 (2026-10-16, A/B updated 2026-10-29), now on currentmain.Fixes #404. Refs docling-project/docling#4762 (the docling table matcher can place a clipped word by its clip box once it has one).
has_clip_bbox,clip_x0..clip_y1, page coordinates; pybind +PdfTextCell.clip_bboxwhen the installed docling-core has the field). Two consecutive glyphs under different clip boxes, of which at least one is cut by its own clip, never join into one word or one line. Clip paths are tracked even when shapes are skipped (DecodeConfig.keep_clips, default true), since docling computes shapes only when it renders pages.Why the words merged
contract()starts a new word when the normalized forward gap exceeds the word gap, and clamps the gap at zero, so a glyph that overlaps its predecessor can never start a word. In the issue'sclipped-table.pdf, row 1'sZends at x=130.5 andRstarts at x=125.0: gap −5.5 pt, clamped to 0, one wordAB-1001-XYZReplacement. The clip that hides the tail was tracked only inpdf_state<SHAPE>for the render instruction; the cell got nothing. And every path and clip operator inshape.hreturned early withoutkeep_shapes, which docling sets only when it renders pages, so a parse withrender_pages=Falsehad no clip at all.What "cut" means
The corpus has producers that wrap every text run, or every glyph, in a clip that does not cut it; that clip only the one glyph which overflows a cell and draw the rest of the word unclipped; that draw ink-tight clips around punctuation; and that repaint glyphs under sliver clips a fraction of a point wide. So the rule is narrower than "different clip means different word": a glyph counts as cut when its clip hides more than a fifth of its extent along the writing axis, or of its em if that is larger; a clip box shorter than one em along that axis never cuts, since it cannot be a cell; and when only one of the two glyphs has a clip box, the other still belongs to it while it lies inside that box. Each of those producer patterns was a false positive of a simpler rule before its guard went in, and each has a unit test in
tests/test_unit_clipped_cell_text.py(9 tests, synthetic PDFs built withtests/pdf_builder).Changes made while rebasing on #437
crossed_explicit_spaceflag from the gap-profile loop (a semantic space now resetsprevious); the clip check moved onto the new condition ininfer_unspaced_gap_profile().infer_spaced_word_gap(); it skips clip-boundary pairs the same way, so a cut tail under the next cell does not feed the median of a line's letter gaps.render_instructions.hincludes merged with feat: read the tagged-PDF structure tree and link text cells to marked content #337 and fix(render): stroke clipping, tiling patterns and soft masks, consolidated per #428 #439 textually; the renderer still receives the same clip state, now captured once per glyph and shared with the cell.docling-core
The field is
PdfTextCell.clip_bbox: Optional[BoundingBox], docling-project/docling-core#964 (the origin conversions move it withrect). docling-parse fills it only when the installed model has the field, so this PR does not raise the docling-core floor; the positional JSON record is unchanged.Evidence and ground truth changes
I built
main(f4b12cf) and this branch and dumped the word and line cells of every page of the regression page selection (tests/regression_page_selection.py: 453 documents, 950 pages, revisiondf881cc), with the parser configuration oftest_reference_documents_from_filenames.Clips are common: on 350 of the 950 pages at least one word cell carries a clip box (114,123 word cells in all), and only the 22 pages below regroup, which is why the rule asks for a cut glyph and not merely for different clips.
Every regrouped page is the pattern of this issue, found in the corpus without looking for it: a table whose producer clipped cell text to the cell. Ground truth changes on the 22 pages below because the rule separates what a producer put into different boxes; no page changes for any other reason, and no text changes.
11024633002361155703-1.pdf, p1: results table, name column clipped at x=160.7:Christophen°1059splits intoChristophe+n°1059, and the line splits at the cell borderGround truth changes because the clipped tail of a cell no longer joins the first word of the next cell.
words on p1:
Christophen°1059Christophe/n°105911268045627910569931_002.pdf, pp. 1–3: region column clipped at x=216.7 (Yorke Peninsul,Outback - Nortis what the page shows):W12-<15andMa12-<15split at the cell border, and those rows' lines split into the region cell and the months cellGround truth changes because the clipped tail of a cell no longer joins the first word of the next cell.
words on p1:
W12-<15/Ma12-<15W/12-<15/Ma/12-<153 pages; word cells 1389 → 1395, line cells 985 → 991.
1135949864700736255-1.pdf, p1: Japanese menu table: a dish name and its ingredient list, clipped to neighbouring cells, become two linesGround truth changes because a line no longer runs from a clipped cell into the next cell.
lines on p1:
ちくわの磯辺揚げ(じゃがいも、まいたけ) ちくわ、あおのり、小麦粉、じゃがいも、まいたけ、油ちくわの磯辺揚げ(じゃがいも、まいたけ)/ちくわ、あおのり、小麦粉、じゃがいも、まいたけ、油16470238655385139760-1.pdf, p1: Japanese table: the label2-2separates from the clipped neighbour cellGround truth changes because the clipped tail of a cell no longer joins the first word of the next cell.
words on p1:
建築物の環境効率(BEEランク&チャート)2-2建築物の環境効率(BEEランク&チャート)/2-29590626299437503566-1.pdf, p1: Japanese table: the unitkgseparates from the clipped item nameGround truth changes because the clipped tail of a cell no longer joins the first word of the next cell.
words on p1:
混合廃棄物(石膏ボード系混入物)kg混合廃棄物(石膏ボード系混入物)/kgddf7d91a5a010970_0020.pdf, p1: headerCréditsseparates from the clipped header of the next column; words unchangedGround truth changes because a line no longer runs from a clipped cell into the next cell.
lines on p1:
Crédits OBSERVATIONS/Crédits OBSERVATIONSCrédits/OBSERVATIONS/Crédits/OBSERVATIONS3763025099401294496-2.pdf, pp. 2–15: roster whose name column clips long names: no word changes, lines such asMUNOZ, BRANDON SDsplit into the name cell and the school cellGround truth changes because a line no longer runs from a clipped cell into the next cell.
lines on p2:
MUNOZ, BRANDON SD/0.0 MUNOZ, BRANDON SD/MUNOZ, BRANDON SD/0.0 ARREOLA, NATHAN MON/ARREOLA, NATHAN MONMUNOZ, BRANDON/SD/0.0 MUNOZ, BRANDON/SD/MUNOZ, BRANDON14 pages; word cells 12765 → 12765, line cells 8633 → 8826.
All pages
11024633002361155703-1.pdfp1,11268045627910569931_002.pdfp1,11268045627910569931_002.pdfp2,11268045627910569931_002.pdfp3,1135949864700736255-1.pdfp1,16470238655385139760-1.pdfp1,9590626299437503566-1.pdfp1,ddf7d91a5a010970_0020.pdfp1,3763025099401294496-2.pdfp2,3763025099401294496-2.pdfp3,3763025099401294496-2.pdfp4,3763025099401294496-2.pdfp5,3763025099401294496-2.pdfp6,3763025099401294496-2.pdfp7,3763025099401294496-2.pdfp8,3763025099401294496-2.pdfp9,3763025099401294496-2.pdfp10,3763025099401294496-2.pdfp11,3763025099401294496-2.pdfp12,3763025099401294496-2.pdfp13,3763025099401294496-2.pdfp14,3763025099401294496-2.pdfp15Before/after images
Every page of the A/B whose word or line cells change. Page render: pypdfium2. Left: cells that
mainemits and this branch does not (red). Right: the cells that replace them (green and blue alternate, so adjacent cells stay apart). Images and script:pr-evidence/404-pr.11024633002361155703-1.pdf, p111268045627910569931_002.pdf, pp. 1–31135949864700736255-1.pdf, p116470238655385139760-1.pdf, p19590626299437503566-1.pdf, p1ddf7d91a5a010970_0020.pdf, p13763025099401294496-2.pdf, pp. 2–15Known limits
clipped-table.pdf,AB-1002 Gutter cleaningis one line as before); the words still carry their cell boxes.CLIP_HIDDEN_FRACTION,CLIP_BBOX_EPSincells.h); the A/B above is the evidence for them.Checklist (#428)
main, no merge commits🤖 Generated with Claude Code