Datagrunt 4.6.8: Correct Table Cell Reading Order on Rotated PDFs

October 30, 2026by Martin Graham

Datagrunt 4.6.8 resolves a key table extraction bug on rotated PDF pages (#387 / #391), ensuring table cell content and grid structures match their authored reading order regardless of page /Rotate settings.


What Was Wrong

In prior releases (including 4.6.7), table bounding box placement was accurately aligned to the displayed page frame, but table cell text was read in display order after page rotation was applied.

When a document’s page had a /Rotate attribute (e.g. 90°, 180°, or 270° applied by a scanner or PDF viewing tool) while the underlying text was authored upright on the page canvas:

  • 90° and 270° Rotations: Transposed the table grid layout (for instance, a 4-row by 3-column table was extracted as 3 rows by 4 columns).
  • 180° and 270° Rotations: Reversed the character sequence inside individual cells (e.g., "Part" was extracted as "traP").
  • Multi-line / Multi-word Cells: Cell text lines were fragmented and re-joined in incorrect display order.

What Changed in v4.6.8

Datagrunt now checks, from each glyph’s own placement data, whether every glyph in a table was authored upright on the page before /Rotate turned it.

When all glyphs in a table pass this test:

  1. Cell bounding boxes and glyph coordinates are unturned back to the authored page orientation.
  2. Grid layout reconstruction and cell text extraction run against the unturned coordinates.
  3. Cells and text are returned in exact authored reading order.

Content authored explicitly to display upright on landscape/rotated pages (such as LaTeX pdflscape output), tables with mixed glyph orientations, or empty tables with no text glyphs continue to use display-order extraction seamlessly.

from datagrunt import PDFReader

# Parse a rotated PDF containing tabular data
reader = PDFReader("rotated_invoice.pdf")
page = reader.to_dicts()["document"]["pages"][0]

# Inspect extracted table elements
for element in page["elements"]:
    if element["type"] == "table":
        print(f"Table dimensions: {element['metadata']['rows']}x{element['metadata']['columns']}")
        for row in element["content"]:
            print(row)

Key Guarantees Retained

  • No Duplicated Text: Table cell text remains inside the table element and is never duplicated as body text.
  • Table positions stay in the same coordinate frame as the engine’s text (with pdfium, where the table renders on the displayed page).
  • Both engines: pdfium and pymupdf return the same cell content; on rotated pages pymupdf positions remain in its unrotated page frame.

Known Limitations

Tracked in #390: on rotated pages table detection can still differ from the unrotated page (some tables are found with different cells), a table that mixes vertical and horizontal text keeps the display-order reading, several tables on one rotated page are numbered in display order, and tables with no text keep their displayed shape.


Upgrading

Upgrade to the latest version via uv:

uv pip install --upgrade datagrunt