From fbc089f33e42e308d3a4a48abc249b6132a12ec3 Mon Sep 17 00:00:00 2001 From: Manoj Date: Sun, 25 Oct 2026 12:27:33 +0530 Subject: [PATCH 1/2] Fix zero-width character and ligature drop in table cell extraction When quad corrections are unset (e.g. in the table layout path or when importing pymupdf4llm), expanded ligatures and combining marks receive zero-width bounding boxes. In extract_cells, the 50% bbox area overlap check evaluates to 0 > 0 (False), silently dropping the second character of ligatures (e.g. 'fine' -> 'fne'). This commit: - Pre-allocates cell_rect once per extract_cells call. - Falls back to testing character origin inside cell_rect when bbox is empty. - Adds a regression test in tests/test_5185.py covering zero-width ligature retention and out-of-cell exclusion. Fixes #5185 --- src/table.py | 14 ++++++++++++-- tests/test_5185.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/test_5185.py diff --git a/src/table.py b/src/table.py index eb3aaf412..5e6e873d9 100644 --- a/src/table.py +++ b/src/table.py @@ -348,6 +348,7 @@ def extract_cells(textpage, cell, markdown=False): A string with the text extracted from the cell. """ text = "" + cell_rect = pymupdf.Rect(cell) for block in textpage.extractRAWDICT()["blocks"]: if block["type"] != 0: continue @@ -388,12 +389,21 @@ def extract_cells(textpage, cell, markdown=False): ): continue # skip spans outside cell - # only include chars with more than 50% bbox overlap + # only include chars with more than 50% bbox overlap. + # A char with an empty bbox (e.g. second character of an expanded + # ligature with quad corrections unset, or a zero-width combining mark) + # has no area; fall back to testing if its origin is inside the cell: + # issue #5185. span_text = "" for char in span["chars"]: this_char = char["c"] bbox = pymupdf.Rect(char["bbox"]) - if abs(bbox & cell) > 0.5 * abs(bbox): + if bbox.is_empty: + in_cell = pymupdf.Point(char["origin"]) in cell_rect + else: + in_cell = abs(bbox & cell_rect) > 0.5 * abs(bbox) + + if in_cell: span_text += this_char elif this_char in white_spaces: span_text += " " diff --git a/tests/test_5185.py b/tests/test_5185.py new file mode 100644 index 000000000..4ef775e31 --- /dev/null +++ b/tests/test_5185.py @@ -0,0 +1,34 @@ +""" +Regression test for issue #5185: table cell extraction dropped characters with +an empty bbox, e.g. the second character of an expanded ligature when quad +corrections are unset ("fine" became "fne"). +""" + +from pymupdf.table import extract_cells + + +class _TextPage: + """Minimal stand-in for a TextPage, providing extractRAWDICT() only.""" + + def __init__(self, chars): + bbox = (0, 0, 500, 30) + span = {"bbox": bbox, "flags": 0, "char_flags": 0, "chars": chars} + line = {"bbox": bbox, "dir": (1, 0), "spans": [span]} + self.rawdict = {"blocks": [{"type": 0, "bbox": bbox, "lines": [line]}]} + + def extractRAWDICT(self): + return self.rawdict + + +def test_5185(): + cell = (0, 0, 100, 30) + chars = [ + {"c": "f", "bbox": (10, 10, 15, 20), "origin": (10, 18)}, + # zero-width bbox of the expanded "fi" ligature, inside the cell + {"c": "i", "bbox": (15, 10, 15, 20), "origin": (15, 18)}, + {"c": "n", "bbox": (15, 10, 20, 20), "origin": (15, 18)}, + {"c": "e", "bbox": (20, 10, 25, 20), "origin": (20, 18)}, + # zero-width char outside the cell must still be excluded + {"c": "x", "bbox": (150, 10, 150, 20), "origin": (150, 18)}, + ] + assert extract_cells(_TextPage(chars), cell) == "fine" From a4ad7d805469050b478381536152bb6771b85195 Mon Sep 17 00:00:00 2001 From: Manoj Date: Sun, 25 Oct 2026 12:46:14 +0530 Subject: [PATCH 2/2] Trigger CLA check