Skip to content

bug: sideways tables (rotated text matrix, rotation_degrees=90) extract word-reversed, glued, and structurally scrambled #1358

Description

@tobocop2

What happens

Tables drawn sideways on a portrait page (rotated text matrix, glyph rotation_degrees = 90) extract as scrambled text: word order reversed inside runs, adjacent words glued, and the table's 2D structure destroyed. The words all survive, so nothing errors and nothing looks empty — the page is just unreadable.

From the capacities table of a car manual (page 264), xberg 1.0.8:

CAPACITIES AND  part FordequivalentMotorcraftPerformanceVehicle MotorFluid...
SPECIFICATIONS  Capacityon  18.6(17.6L) quarts
BetweenMAX 6.0

The same page through kreuzberg 4.x (and pypdf):

Engine coolant 18.6 quarts (17.6L) Motorcraft Premium Gold Engine Coolant ...
Engine oil 6.0 quarts (5.7L)

A footnote paragraph on page 266 comes out fully word-reversed: the meet only need oil Engine for "Engine oil need only meet the …".

Reproduce

cv-manual.pdf from the gist used in #1220 (https://gist.github.com/tobocop2/0d30905e2375e09b153ce50296316f91), pages 264–266:

import asyncio
from xberg import ExtractInput, ExtractInputKind, ExtractionConfig, PageConfig, extract

async def main():
    cfg = ExtractionConfig(pages=PageConfig(extract_pages=True))
    inp = ExtractInput(kind=ExtractInputKind.BYTES, bytes=open("cv-manual.pdf", "rb").read(),
                       mime_type="application/pdf", filename="cv-manual.pdf")
    doc = (await extract(inp, cfg)).results[0]
    print(doc.pages[263].content)   # scrambled; "Engine oil" nowhere near "6.0 quarts"

asyncio.run(main())

Reproduces identically on 1.0.4, 1.0.8 (current PyPI), and a CLI built from main (7539de03e4). Layout config makes no difference: AUTO and ALWAYS strategies, CPU and CoreML providers all return byte-identical page text for these pages, so the reading-order machinery never rescues them.

Root cause

These pages are ~90% rotated text: pdf_oxide.extract_page_text reports 795 of 886 chars on page 264 at rotation_degrees = 90 (the remainder is the upright running footer). The glyph geometry is correct; the assembly of rotated runs is what garbles. This is exactly yfedoseev/pdf_oxide#806 ("reversed reading, glued words, destroyed table/chart structure"), still open there. The #1345 two-column fix (v1.0.5) does not cover rotated runs.

Why it matters

This is the dominant layout for wide spec tables in manuals and datasheets — capacities, fluids, torque specs — which are exactly the pages people point a RAG pipeline at. The failure is silent: retrieval indexes the shredded fragments, surfaces adjacent prose instead, and the answer looks considered rather than uninformed. kreuzberg 4.x extracted these pages cleanly, so for this document class 1.0 is a regression.

Possible xberg-side handling

Until pdf_oxide#806 lands, xberg can see the condition coming: a text-layer page whose chars are predominantly rotation_degrees = ±90. Candidates, in rough order of ambition: reassemble those runs in the rotated frame (the per-char geometry is already correct); route such pages through the existing OCR orientation-classifier path the way scanned rotated pages are handled; or at minimum flag the page so callers can quarantine it instead of indexing scrambled text.

Workaround

Use OCR extraction for these types of files.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions