Skip to content

fix(text): word and cell segmentation fixes, consolidated per #428 - #437

Merged
PeterStaar-IBM merged 6 commits into
docling-project:mainfrom
wittjeff:fix/word-cell-segmentation
Nov 3, 2026
Merged

PeterStaar-IBM merged 6 commits into
docling-project:mainfrom
wittjeff:fix/word-cell-segmentation

Conversation

@wittjeff

@wittjeff wittjeff commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Word and cell segmentation topic from #428. This PR replaces #409, #356 and #358, which are closed and link here. It has one commit per fix, rebased on main (2fb6525), with no merge commits.

Fixes #383. Fixes docling-project/docling#4018. Refs #355, #362 (case 3), #377.

commit replaces fix
1, 2 #409 (fixes #383) A run without a space glyph (TeX output, positioned headings) judges its gaps in em above the run's own letter spacing, not in neighbouring glyph advances under a 0.35 cap. The cap lost a word gap next to a wide glyph (2 RelatedWork) and kept a kern next to narrow glyphs. Commit 2 lets a tightly justified line follow its own word spacing.
3 #356 (fixes docling#4018) Right-to-left text written in logical order under a left-to-right matrix (Quartz) is measured from the next glyph's advance end back to the previous origin, and appended instead of prepended, so Hebrew no longer comes out one letter per word.
4 #358 (follow-up to #355) On a run that carries space glyphs, the threshold for the unmarked gaps rises with the line's letter spacing, so tracked text with suppressed spaces (Tinta) stays whole.

Changes made while consolidating

Evidence and ground truth changes

I built the branch commit by commit and dumped the word and line cells of the sampled ground-truth pages after each step, so every changed page is attributed to the commit that changes it: the 942 pages of dataset revision a89cdc6 (449 documents), and the 8 pages of the 4 documents that #436 added with revision 6774fc0, none of which changes at any step. 138 of the 950 pages change between main and the branch tip; 10 of them are changed by more than one commit. Rendering does not read word cells, so the render ground truth does not change, but test_render_reference_documents_from_filenames also checks the parsed text against the parser ground truth and stays red until that is updated, as for #431. The maintainers update the ground truth when merging; the regenerated page dumps are available if useful.

step ground-truth pages changed seams (split / join) arXiv seam list of #383 (22 papers, 378 lost on main)
commits 1, 2 (#409) 104 in 69 documents +556 / +1804 201 repaired, 177 still lost
commit 3 (#356) 25 in 25 documents +3 / 0, plus reordered letters in 2877 lines no change
commit 4 (#358) 19 in 14 documents 0 / +3953 no change

The 177 seams that remain lost are the inline-mathematics cursor rewinds and the gaps on lines with space glyphs that #409's measurement listed; the latter are #385's scope.

Each section gives the reason, the first changed words of the cited pages (before → after), and the full page list.

#409: 104 pages in 69 documents

(commit 1 and 2 together; the same 104 pages, 556 splits and 1804 joins as measured on #409, since cells.h did not change on main in between)

Ground truth changes because runs without a space glyph judge their gaps in em above the run's letter spacing instead of in neighbouring advances under a 0.35 cap; word gaps next to wide glyphs come back, kerns and per-glyph corrections next to narrow glyphs stop splitting, and justified CJK text is no longer split at every character.

2206.01062.pdf p2:

before after
RELATEDWORK RELATED WORK

newspaper-00.pdf p1:

before after
unserenWerbekunden, unseren Werbekunden,
ChefredaktorinDésiréePomper. Chefredaktorin Désirée Pomper.
Ichwar skeptisch.Wer Ich war skeptisch. Wer
miertenGesellschaft mierten Gesellschaft
insGymi ins Gymi
All pages

024805ca-0473-491f-8e49-6444ecac3d4b-5.pdf p1, 07f5395c8b3e7d1c_0001.pdf p1, 082b97f3d239a9c5_0006.pdf p1, 10003567098752154021-14.pdf p6, 10003567098752154021-14.pdf p15, 1020000062573-15.pdf p13, 1020000062573-15.pdf p18, 1020017012728-14.pdf p18, 1020017012728-14.pdf p118, 10446922681186632184_002.pdf p8, 10446922681186632184_002.pdf p16, 10588776208842536523_006.pdf p5, 10749817875312063576_009.pdf p5, 11816493302388412838-4.pdf p1, 11816493302388412838-4.pdf p2, 11816493302388412838-4.pdf p6, 11816493302388412838-4.pdf p7, 11816493302388412838-4.pdf p8, 13785621751355796362-1.pdf p1, 14267954770681214922-1.pdf p1, 15424333971388669153-2.pdf p1, 15424333971388669153-2.pdf p3, 15424333971388669153-2.pdf p4, 15523818099946337472-5.pdf p1, 15523818099946337472-5.pdf p2, 15523818099946337472-5.pdf p3, 15523818099946337472-5.pdf p4, 15523818099946337472-5.pdf p5, 16470238655385139760-1.pdf p1, 17791c05056ff856_0022.pdf p1, 18964da9aca4905a_0004.pdf p1, 19246029784032167-4.pdf p2, 2203.01017v2.pdf p12, 2206.01062.pdf p2, 2206.01062.pdf p3, 2206.01062.pdf p4, 222271419343144742-5.pdf p5, 222271419343144742-5.pdf p9, 222271419343144742-5.pdf p10, 222271419343144742-5.pdf p12, 222271419343144742-5.pdf p14, 2305.03393v1.pdf p10, 2508.13113v2.pdf p2, 2603.16056.pdf p1, 298064347821956345-83.pdf p68, 3040977140767198439-1.pdf p1, 335fea0ba454ba15_0004.pdf p1, 4402863604551657979-1.pdf p1, 4561257296328379730-2.pdf p2, 4561257296328379730-2.pdf p4, 4561257296328379730-2.pdf p5, 4561257296328379730-2.pdf p7, 4561257296328379730-2.pdf p8, 4796728975040539044-1.pdf p1, 4865216256588543301.pdf p1, 490193be45e28959_0004.pdf p1, 5081873815222802242-2.pdf p1, 5081873815222802242-2.pdf p2, 5462902444445925132-4.pdf p1, 5462902444445925132-4.pdf p2, 5462902444445925132-4.pdf p3, 5462902444445925132-4.pdf p4, 5a2e3eec-f6a1-487a-9929-35f9f1661679-1.pdf p1, 60d7bccfcfdf7ae6_0004.pdf p1, 6442bc6d-74bb-4310-981e-66dc381ccc2e-3.pdf p1, 71fcd26c-8105-4690-8958-cf10be9b9787-2.pdf p1, 762975394748001135-1.pdf p1, 7816024906253388048-2.pdf p2, 7d17f06dc82377db_0004.pdf p1, 8624879949050867564-1.pdf p1, 8793130488931799579-2.pdf p1, 8793130488931799579-2.pdf p2, 8793130488931799579-2.pdf p3, 8e4f27ceb074831c_0023.pdf p1, 9180310055394461959-1.pdf p1, 9402bf128c06d7b1_0001.pdf p1, 9acb62b4-4449-48d0-8127-f2be26349a6a-6.pdf p1, 9df200b18be072f7_0003.pdf p1, CN102017000717986CN00001077030360BPDFZH20200630CN00Q-1.pdf p1, CN112019000093292CN00001135187520BPDFZH20230728CN00H-1.pdf p1, a5a2e4e8-bf96-43c5-a518-324b99a05d68-186.pdf p1, ba55e6a2-1a86-460d-bf2c-e01a6771657b-2.pdf p1, c8702e75db7ff148_0001.pdf p1, c9f1771acf9f7e29_0001.pdf p1, complex_invisible_fonts_05.pdf p1, dln_7145620b626e28e17deb09b5b4a38cb8434cb232ca0dee48449fba35065d7637.pdf p1, e7a3a534-9609-4dc9-9446-2d3a13c0b2ff-5.pdf p1, eac6df0b2a6f9cf0_0002.pdf p1, elsevier-00.pdf p1, elsevier-00.pdf p9, ff608f7bb175bb97_0010.pdf p1, font_07.pdf p1, font_09.pdf p1, form_fields.pdf p4, ligatures_01.pdf p1, ligatures_01.pdf p2, ligatures_01.pdf p3, ligatures_01.pdf p4, math_latex_formulas.pdf p2, math_latex_formulas.pdf p4, newspaper-00.pdf p1, right_to_left_02.pdf p1, stream_parameter_misinterpretation_01.pdf p1, table_of_contents_01.pdf p4

#356: 25 pages in 25 documents

Ground truth changes because right-to-left text written in logical order is measured from the next glyph's advance end back to the previous origin and appended in stream order, so backward-running runs are no longer cut at every glyph pair and no longer reversed.

b766d7d18edf92b1_0002.pdf p1:

before after
ﺮ ي ﺮي
ﻴ ﺮ ي ﻴﺮي
ﺳ ﻴ ﻢ ﺳﻴﻢ
ﻲ ، ﻲ،
ﻴ ﺪ ﻴﺪ

069af5ebf89bc7b3_0030.pdf p1:

before after
ی چ ی یچی
د ی دی
ی ج یج
ی د ید
ی ر یر

Fewer right-to-left single-letter words and fewer line cells on the cited pages (commit 2 → commit 3):

page one-letter RTL words line cells
b766d7d18edf92b1_0002.pdf p1 613 → 266 1492 → 834
069af5ebf89bc7b3_0030.pdf p1 127 → 66 241 → 144
ead2c552b489aac1_0004.pdf p1 12 → 7 235 → 216
ec62f8dc0265e965_0018.pdf p1 23 → 23 216 → 204
994ed640a9c152e5_0001.pdf p1 13 → 13 1524 → 1257

The reporter's sample from docling-project/docling#4018, heb_test_doc-prog.pdf (Quartz, Hebrew in logical order under a left-to-right matrix), goes from 361 one-letter words to 91 words, אלוהים מרחם על ילדי הגן as the first line. The line merges on the Arabic and Persian pages are where I would welcome a second pair of eyes, as in #356.

All pages

069af5ebf89bc7b3_0030.pdf p1, 11816493302388412838-4.pdf p7, 153a16136ed8f5ac_0011.pdf p1, 335fea0ba454ba15_0004.pdf p1, 4796728975040539044-1.pdf p1, 490193be45e28959_0004.pdf p1, 65e32285b042b013_0027.pdf p1, 6789603da459eee1_0017.pdf p1, 9528c2089295e005_0001.pdf p1, 994ed640a9c152e5_0001.pdf p1, 99b7c55253747f9b_0002.pdf p1, b3f8917b0485daed_0007.pdf p1, b6855cce7fbfd31b_0004.pdf p1, b766d7d18edf92b1_0002.pdf p1, b9a8492e20c62472_0001.pdf p1, bc28af92f730f7a4_0001.pdf p1, d01effade60c7832_0001.pdf p1, da04f7ddb6f59004_0025.pdf p1, dd2cc16445584ea7_0017.pdf p1, ead2c552b489aac1_0004.pdf p1, ec62f8dc0265e965_0018.pdf p1, f41b4838d5d45f01_0004.pdf p1, right_to_left_02.pdf p1, right_to_left_03.pdf p1, right_to_left_04.pdf p1

#358: 19 pages in 14 documents

Ground truth changes because on a line with space glyphs the unmarked-gap threshold rises with the line's letter spacing, so tracked text stays whole. 3953 joins and no split; 3300 of the joins are in 14289803404128846560-14.pdf, a letter-spaced Portuguese regulation whose body text and contents leaders carry suppressed space glyphs. pdfium's own extraction splits tracked letters, so its labels are not usable for this step; the before/after lines are.

14289803404128846560-14.pdf p14 (not in the ground-truth sample):

before after
L ICENÇAS LICENÇAS
Ga b i ne t e Gabinete
Ap o i o à s F r e g ue s i a s P á g i na 1 4 Apoio às Freguesias Página 14
Fórmu l a s Fórmulas
cá l cu l o cálculo
All pages

082b97f3d239a9c5_0006.pdf p1, 10572911635253446040-13.pdf p8, 10588776208842536523_006.pdf p8, 11794545469969901016-2.pdf p2, 14289803404128846560-14.pdf p2, 14289803404128846560-14.pdf p3, 14289803404128846560-14.pdf p5, 14289803404128846560-14.pdf p10, 14289803404128846560-14.pdf p12, 16134591854657313890-2.pdf p2, 19246029784032167-4.pdf p2, 5081873815222802242-2.pdf p2, 7816024906253388048-2.pdf p1, 8313873609013042575-4.pdf p1, 8313873609013042575-4.pdf p5, b766d7d18edf92b1_0002.pdf p1, c879c7ccf5b9de33_0007.pdf p1, dln_096631dd18d20bbff1c89dddcbf7f2fd3d11f3aa2800aa3a5c695215dc19989d.pdf p1, ff608f7bb175bb97_0010.pdf p1

Known limits

  • Tracking wider than 0.2 em on a run without space glyphs is not removed (a r t i c l e i n f o in elsevier-00.pdf p1). Accepting it joins positioned mathematics in 19246029784032167-4.pdf.
  • TeX thin spaces in mathematics (0.167 em) split, as they do in pdfium.
  • right_to_left_04.pdf: the tatweel-stretched heading "المقدمة" is still split; that is a separate defect of the contractor, not of the writing order.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @wittjeff, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@PeterStaar-IBM
PeterStaar-IBM self-requested a review November 2, 2026 15:29
wittjeff and others added 4 commits November 2, 2026 11:00
…h advances

Since 7.22.1 a run without a space glyph (TeX output, positioned headings)
loses a word gap whenever a wide glyph sits on either side of it. The gap
is divided by the mean advance of the two glyphs around it, so `d|W` in
`Related Work` measures 0.26 of the pair and falls under the 0.35 cap,
while a kern between narrow glyphs (`n|f` in `Confidential`) measures 0.24
and a per-glyph correction (`f|l`) 0.42. No threshold in that unit
separates word gaps from kerns. In font-size units they separate: the lost
word gaps measure 0.21 to 0.31 em, the kern and the correction 0.10 and
0.11 em.

For such runs, divide the gap by the mean nominal text height of the two
cells, remove the run's uniform letter spacing (the 20th-percentile gap
when it lies in (0.03, 0.2] em and few gaps are zero; for CJK-majority
runs the median, at most 0.5 em), and start a word above 0.13 em, with no
cap. Runs that carry explicit space glyphs are unchanged. The per-line
k-means `infer_word_gap()` is removed; it only served these runs.

Fixes docling-project#383

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
Justified lines in condensed faces have word gaps of 0.14 to 0.16 em, and
pair kerning between the space and a capital `V`, `W`, `A` or a `w` takes
up to 0.05 em off some of them: `undVerteilung`, `tut:Wir` and `grosseAn-`
in newspaper-00.pdf measure 0.11 to 0.13 em and fell under the 0.13 em
threshold. The other word gaps of such a line show where its word spacing
sits. When a run without tracking has at least two gaps above the
threshold, the threshold becomes 70 % of their median, clamped to
[0.09, 0.13] em.

On the 940 regression pages this changes 14 seams: 12 word gaps on page 1
of newspaper-00.pdf (3 of them also lost on 7.22.3) and 2 seams between
unmapped glyphs of a font without a usable encoding.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
Some generators (macOS Quartz output, e.g. docling#4018) write Hebrew and
Arabic in logical order under a left-to-right text matrix: each glyph is
placed immediately left of the previous one by a large negative Tc plus
per-glyph TJ offsets. The painted glyphs abut, but the PDF cursor runs
backwards.

The geometry-driven contractor from docling-project#351 measures the gap from the
previous cursor advance end to the next origin. For these streams that is
about minus two glyph widths, so continues_line() rejects every pair and
each character becomes its own word and line (regression from 7.20.0).
append_cell() also always prepends right-to-left text, which assumes
visual-order streams and would reverse these words once merged.

- forward_gap(): when both cells are right-to-left and the next origin
  lies behind the previous one, measure from the next cell's advance end
  back to the previous origin.
- append_cell(): append right-to-left text only when the next cell lies
  clearly before the aggregate along the bbox axis; overlapping cells
  (combining marks) keep the prepend default.

The reporter's page now yields the same word cells as 7.20.0. Including
Tc/Tw in the text advance was considered and rejected: it changes
tracked left-to-right text globally and still splits words on the TJ
offsets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
… spacing

Since 7.22.1 (docling-project#361) an unmarked gap above 0.35 neighbouring advances
starts a word on every line. Letter spacing opens every gap of a tracked
line, often past that, so tracked text that carries space glyphs split
into letters: `Tinta` -> `T i nt a` and `Gabinete de Apoio às
Freguesias` -> `Ga b i ne t e de Ap o i o à s F r e g ue s i a s` in
14289803404128846560-14.pdf.

On a run with space glyphs the threshold is now 1.5 times the median
unmarked gap below one advance plus 0.20, at least 0.35 and at most 1.0,
when three or more such gaps exist between glyphs that are not leader
dots. Runs without space glyphs are not touched: the em rule of the
previous commits removes their letter spacing up to 0.2 em, and wider
tracking (`a r t i c l e i n f o` in elsevier-00.pdf, 0.44 em) stays a
known limit, as docling-project#358 and docling-project#409 both documented.

Replaces docling-project#358 (follow-up to docling-project#355).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
@wittjeff
wittjeff force-pushed the fix/word-cell-segmentation branch from 9e430d3 to c5821ea Compare November 2, 2026 16:00
@PeterStaar-IBM
PeterStaar-IBM merged commit 01cd139 into docling-project:main Nov 3, 2026
43 checks passed
@wittjeff
wittjeff deleted the fix/word-cell-segmentation branch November 3, 2026 18:30
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.

Still drops word spaces next to wide glyphs in 7.22.1 [Bee] docling parse generates text as each character separated

2 participants