You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
2.0: Table.extract() moves the decimal point out of numbers ("0.74" comes back as "0 74", newline, ".") — regression from 1.28.2 #5198
Since 2.0, Table.extract() returns cell text with decimal numbers broken apart: the
digits are split by a space and the decimal point is moved onto a line of its own after
them. 0.74 comes back as '0 74\n.' and 12.5 mg as '12 5 mg\n.'. This happens in
the native table finder too (with pymupdf._get_layout = None). With the layout hook
that 2.0 installs on import, the same table also comes back with an extra empty column. page.get_text("words") on the same page is correct, and 1.28.2 extracts the same table
correctly.
In a real document the same regression also drops the spaces between words. On the
landscape (/Rotate 90) table pages 11-14 of the PubMed Central open-access article
PMC12000001, find_tables() returns the same grid (same bbox) on both versions, with the
native finder (pymupdf._get_layout = None), but extract() gives:
Version
header row
data row
1.28.0
[None, None, 'β (95%CI)', 'FDR', 'P for interaction', ...]
I traced this to the quad-corrections toggle rather than to anything in Table.extract(), and opened #5213 with a fix for the line-based finder.
Cause.find_tables() sets TOOLS.set_small_glyph_heights(True), but small glyph heights only apply while quad corrections are on (JM_char_quad() returns ch->quad unchanged when g_skip_quad_corrections is set). pymupdf4llm's helpers call pymupdf.TOOLS.unset_quad_corrections(True) at import time, and 2.0 imports pymupdf4llm at the end of import pymupdf, so in 2.0 every process has corrections off. Each character then keeps its exact glyph box, the "." of "0.74" sits about 7pt below the digits, and extract_text() puts it on a line of its own. The same exact boxes drop the spaces on the rotated pages.
One line confirms it on 2.0:
importpymupdfprint(pymupdf.TOOLS.unset_quad_corrections()) # True right after import (0 on 1.28)pymupdf.TOOLS.unset_quad_corrections(False)
# the reproducer above now gives [['P for interaction', 'Cooking fuel type'], ['0.74', '12.5 mg']]
After that reset, the rotated pages of PMC12000001 extract as '0.47' and 'P for interaction' again.
After #5205. Deferring the pymupdf4llm import fixes a plain import pymupdf, but not a process that imports pymupdf4llm. Also, find_tables() now turns corrections off before get_layout(), so the line-based finder runs without them whenever layout provides no boxes. The PR turns corrections on before make_chars() in that case. On 66 PubMed Central articles, the result is identical to 1.28.0 for every table and cell.
The layout path is a separate question, so it is not in the PR. It turns corrections off on purpose, and has split decimals since layout support was added (reproduced on 1.28.2 with pymupdf.layout imported). The layout expectations in tests/test_tables.py now record that output: '2755 9\n.', '269\n-' for -269, '13144%\n.' for 131.44%. Turning corrections on there fixes the text but merges rows in test_4017 and splits a header in test_paths_param, so row segmentation seems to rely on the exact glyph boxes. A fix would probably build cell text from separately corrected boxes, or group a cell's characters by baseline rather than by top. I'm happy to work on that if you agree on the direction.
The extra empty column with the layout hook, from the original report, is unrelated to this, and the PR does not change it.
Thanks for the analysis, it matches what I found. I approved #5213 for the line-based finder. For the layout path I'd like to open a separate PR myself and will leave a note here once it is up.
The layout-path fix is up as #5243. It groups cell text by the font-metric line top instead of the glyph top, so it no longer depends on the quad-corrections toggle, and it covers rotated pages too.
Thanks for appreciating that we have developed a fix, @thomas-villani !
It is however our policy to keep issues open beyond fix development, until the corresponding version has been published on PyPI.
So, I am going to reopen it.
Activity
build: bump fastapi to 0.161.1 and skip PyMuPDF 2.0 (#648)
fix(documents): read PDFs with pypdfium2 and pdfplumber (#651)
src/table.py: find_tables(): keep quad corrections on for the line-ba…
thomas-villani commentedon Oct 25, 2026
I traced this to the quad-corrections toggle rather than to anything in
Table.extract(), and opened #5213 with a fix for the line-based finder.Cause.
find_tables()setsTOOLS.set_small_glyph_heights(True), but small glyph heights only apply while quad corrections are on (JM_char_quad()returnsch->quadunchanged wheng_skip_quad_correctionsis set). pymupdf4llm's helpers callpymupdf.TOOLS.unset_quad_corrections(True)at import time, and 2.0 imports pymupdf4llm at the end ofimport pymupdf, so in 2.0 every process has corrections off. Each character then keeps its exact glyph box, the "." of "0.74" sits about 7pt below the digits, andextract_text()puts it on a line of its own. The same exact boxes drop the spaces on the rotated pages.One line confirms it on 2.0:
After that reset, the rotated pages of PMC12000001 extract as
'0.47'and'P for interaction'again.After #5205. Deferring the pymupdf4llm import fixes a plain
import pymupdf, but not a process that imports pymupdf4llm. Also,find_tables()now turns corrections off beforeget_layout(), so the line-based finder runs without them whenever layout provides no boxes. The PR turns corrections on beforemake_chars()in that case. On 66 PubMed Central articles, the result is identical to 1.28.0 for every table and cell.The layout path is a separate question, so it is not in the PR. It turns corrections off on purpose, and has split decimals since layout support was added (reproduced on 1.28.2 with
pymupdf.layoutimported). The layout expectations intests/test_tables.pynow record that output:'2755 9\n.','269\n-'for -269,'13144%\n.'for 131.44%. Turning corrections on there fixes the text but merges rows intest_4017and splits a header intest_paths_param, so row segmentation seems to rely on the exact glyph boxes. A fix would probably build cell text from separately corrected boxes, or group a cell's characters by baseline rather than by top. I'm happy to work on that if you agree on the direction.The extra empty column with the layout hook, from the original report, is unrelated to this, and the PR does not change it.
src/table.py: find_tables(): keep quad corrections on for the line-ba…
src/table.py: find_tables(): keep quad corrections on for the line-ba…
table: group cell text into lines by the font-metric line top (pymupd…
veget-able commentedon Nov 2, 2026
Thanks for the analysis, it matches what I found. I approved #5213 for the line-based finder. For the layout path I'd like to open a separate PR myself and will leave a note here once it is up.
table: group cell text into lines by the font-metric line top (pymupd…
table: group cell text into lines by the font-metric line top (pymupd…
veget-able commentedon Nov 2, 2026
The layout-path fix is up as #5243. It groups cell text by the font-metric line top instead of the glyph top, so it no longer depends on the quad-corrections toggle, and it covers rotated pages too.
table: group cell text into lines by the font-metric line top (#5198)
JorjMcKie commentedon Nov 11, 2026
Thanks for appreciating that we have developed a fix, @thomas-villani !
It is however our policy to keep issues open beyond fix development, until the corresponding version has been published on PyPI.
So, I am going to reopen it.