Skip to content

Fix coordinate inversion when MediaBox origin is not (0, 0) - #1381

Open
apoorvdarshan wants to merge 1 commit into
jsvine:developfrom
apoorvdarshan:fix/issue-1332-mediabox-origin-inversion
Open

Fix coordinate inversion when MediaBox origin is not (0, 0)#1381
apoorvdarshan wants to merge 1 commit into
jsvine:developfrom
apoorvdarshan:fix/issue-1332-mediabox-origin-inversion

Conversation

@apoorvdarshan

Copy link
Copy Markdown

Fixes #1332.

Root cause

_invert_box (in pdfplumber/page.py) flips PDF coordinates vertically so
the origin ends up in the top-left. It did so about the MediaBox height:

def _invert_box(box_raw, mb_height):
    x0, y0, x1, y1 = box_raw
    return (x0, mb_height - y1, x1, mb_height - y0)

with mb_height = mb_raw[3] - mb_raw[1]. Because it subtracts from the
height rather than from the raw top edge (mb_raw[3]), the MediaBox's
raw y-origin (mb_raw[1]) is never subtracted. For a MediaBox whose
lower-left is (0, 0) this is fine (mb_height == mb_raw[3]), but when
the MediaBox does not begin at y == 0 every top/bottom is shifted by
that origin, and page.mediabox / page.bbox start at a non-zero y.
(pdfminer.six already normalizes object coordinates to a (0, 0)-origin
page, so the flip must not re-introduce the origin.)

Reproduction

A PDF with a centered-origin MediaBox [-100 -200 100 200] containing a
single rectangle drawn at raw coordinates 10 20 30 40 re (raw span
x: 10..40, y: 20..60):

import pdfplumber
with pdfplumber.open("centered.pdf") as pdf:
    p = pdf.pages[0]
    print("mediabox:", p.mediabox)
    r = p.rects[0]
    print("rect top/bottom:", r["top"], r["bottom"])

Actual (before):

mediabox: (-100, 200, 100, 600)
rect top/bottom: 340.0 380.0

The page's top edge sits at top == 200 and mediabox[3] == 600 even
though the page is only 400 units tall — the whole page is shifted down
by the origin (200).

Corrected (after):

mediabox: (-100, 0, 100, 400)
rect top/bottom: 140.0 180.0

top now ranges over [0, height], as expected.

This also matches the report in #1332: raw MediaBox (-100, -200, 100, 200)
previously yielded (-100, 200, 100, 600).

The regression is also visible on the existing tests/pdfs/issue-1181.pdf
fixture, whose first page has MediaBox [0, 200, 420.9449, 585.2756]:

  • Before: page.mediabox == (0, -200.0, 420.9449, 185.2756), and object
    top values were shifted -200 into negative territory
    (min(top) == -41.3).
  • After: page.mediabox == (0, 0.0, 420.9449, 385.2756), and min(top)
    is 158.7 (all non-negative).

Fix

Flip about the raw upper-y edge (mb_raw[3]) instead of the height.
This subtracts the origin correctly and is a no-op for the common case
(where the MediaBox lower-left is (0, 0), so the top edge equals the
height). The change is confined to the two MediaBox/CropBox call sites in
Page.__init__; the x-offset behavior introduced for #1181 is preserved
(object x-coordinates and page.mediabox[0] still keep the raw
MediaBox x0), and downstream consumers (process_object,
point2coord) read the corrected origin from self.mediabox
automatically, so no other code needed to change.

Tests

Added tests/test_issues.py::test_issue_1332, which uses the existing
issue-1181.pdf fixture (its first page has a non-zero MediaBox
y-origin) and asserts that the inverted MediaBox begins at y == 0, that
no object has a negative top, and that a sample character's coordinates
are corrected. The test fails on the current code
(assert -200.0 == 0) and passes with the fix. The existing suite
continues to pass (python -m pytest, excluding the test_repair.py
cases that require a local Ghostscript install, which is unrelated to this
change). black, isort, and flake8 are clean on the changed files.

Disclosure: prepared with AI assistance; reviewed and verified locally.

`_invert_box` flipped coordinates about the MediaBox *height* rather than
its top edge, so the raw MediaBox y-origin (`mb_raw[1]`) was never
subtracted. For pages whose MediaBox does not begin at `y == 0`, this
shifted every object's `top`/`bottom` by that origin (often producing
negative `top` values) and left `page.mediabox`/`page.bbox` starting at a
non-zero `y`.

Flip about the raw upper-`y` edge (`mb_raw[3]`) instead. This subtracts
the origin correctly and is a no-op for the common case, where the
MediaBox lower-left is `(0, 0)` and the top edge equals the height.

Fixes jsvine#1332.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant