Description of the bug
In pymupdf/table.py, the internal helper function chars_in_rect(CHARS, rect) compares a top-down bounding box rect against character coordinates in CHARS.
However, while rect uses top-down Y coordinates (rect[1] is top, rect[3] is bottom) and the horizontal check uses top-down x0/x1, the vertical check in chars_in_rect compares rect[1] and rect[3] against c["y0"] and c["y1"] instead of c["top"] and c["bottom"].
def chars_in_rect(CHARS, rect):
return any(
1
and rect[0] <= c["x0"]
and c["x1"] <= rect[2]
and rect[1] <= c["y0"] # ❌ rect[1] is top-down Y, but c["y0"] is CTM / bottom-up
and rect[3] >= c["y1"] # ❌ rect[3] is top-down Y, but c["y1"] is CTM / bottom-up
for c in CHARS
)
Impact on Table Detection (find_tables()):
In table.py::clean_graphics(), candidate table boundary rectangles (prect0) are filtered using chars_in_rect(CHARS, prect0) to determine if a candidate box contains text.
- On standard full-page tables,
c["y0"] and c["top"] happen to numerically overlap by coincidence, masking the issue.
- On short tables near the bottom of a page, continuation tables across page breaks, or pages with non-identity CTMs,
c["y0"]/c["y1"] do not overlap with top-down rect[1]/rect[3].
- As a result,
chars_in_rect returns False, clean_graphics() discards the candidate boundary box, line synthesis fails, and entire table rows (or short tables) disappear from find_tables().
Note that elsewhere in table.py (e.g. in has_text() around line 1554), top-down coordinates are correctly paired with c["top"] and c["bottom"]:
text_bboxes = [
(c["x0"], c["top"], c["x1"], c["bottom"]) # ✅ Correct top-down pair used in has_text()
for c in CHARS
if c["text"] not in white_spaces
]
How to reproduce the bug
Minimal Code Example 1 (Direct Unit Test):
import pymupdf.table
# A character positioned at top=100.0, bottom=110.0 on an 800px page.
# Top-down coordinates: top=100, bottom=110
# CTM / bottom-up coordinates: y0=690, y1=700
char = {
"x0": 10.0, "x1": 20.0,
"top": 100.0, "bottom": 110.0,
"y0": 690.0, "y1": 700.0
}
# Top-down bounding box clearly surrounding the character (top=90, bottom=120)
rect = (0.0, 90.0, 30.0, 120.0)
# Expected: True (character is inside the top-down bounding box)
# Actual: False (because chars_in_rect compares top-down rect bounds with y0/y1)
result = pymupdf.table.chars_in_rect([char], rect)
print("chars_in_rect result:", result) # Outputs: False
Proposed Fix:
In src/table.py, update chars_in_rect to use c["top"] and c["bottom"]:
def chars_in_rect(CHARS, rect):
"""Check whether any of the chars in CHAR are inside rectangle 'rect'."""
return any(
1
and rect[0] <= c["x0"]
and c["x1"] <= rect[2]
- and rect[1] <= c["y0"]
- and rect[3] >= c["y1"]
+ and rect[1] <= c["top"]
+ and rect[3] >= c["bottom"]
for c in CHARS
)
PyMuPDF version
1.27.2.3
Operating system
MacOS
Python version
3.12
Description of the bug
In
pymupdf/table.py, the internal helper functionchars_in_rect(CHARS, rect)compares a top-down bounding boxrectagainst character coordinates inCHARS.However, while
rectuses top-down Y coordinates (rect[1]is top,rect[3]is bottom) and the horizontal check uses top-downx0/x1, the vertical check inchars_in_rectcomparesrect[1]andrect[3]againstc["y0"]andc["y1"]instead ofc["top"]andc["bottom"].Impact on Table Detection (
find_tables()):In
table.py::clean_graphics(), candidate table boundary rectangles (prect0) are filtered usingchars_in_rect(CHARS, prect0)to determine if a candidate box contains text.c["y0"]andc["top"]happen to numerically overlap by coincidence, masking the issue.c["y0"]/c["y1"]do not overlap with top-downrect[1]/rect[3].chars_in_rectreturnsFalse,clean_graphics()discards the candidate boundary box, line synthesis fails, and entire table rows (or short tables) disappear fromfind_tables().Note that elsewhere in
table.py(e.g. inhas_text()around line 1554), top-down coordinates are correctly paired withc["top"]andc["bottom"]:How to reproduce the bug
Minimal Code Example 1 (Direct Unit Test):
Proposed Fix:
In
src/table.py, updatechars_in_rectto usec["top"]andc["bottom"]:def chars_in_rect(CHARS, rect): """Check whether any of the chars in CHAR are inside rectangle 'rect'.""" return any( 1 and rect[0] <= c["x0"] and c["x1"] <= rect[2] - and rect[1] <= c["y0"] - and rect[3] >= c["y1"] + and rect[1] <= c["top"] + and rect[3] >= c["bottom"] for c in CHARS )PyMuPDF version
1.27.2.3
Operating system
MacOS
Python version
3.12