Skip to content

Document the y0/y1 vs top/bottom/doctop coordinate systems in README - #1387

Open
soodoku wants to merge 2 commits into
jsvine:developfrom
gojiplus:docs/389-coordinate-systems
Open

Document the y0/y1 vs top/bottom/doctop coordinate systems in README#1387
soodoku wants to merge 2 commits into
jsvine:developfrom
gojiplus:docs/389-coordinate-systems

Conversation

@soodoku

@soodoku soodoku commented Jul 27, 2026

Copy link
Copy Markdown

Closes #389.

Adds a #### Coordinates section to the README, directly above the per-object property tables, explaining the two vertical coordinate systems and how they relate.

The README currently defines y0/y1/top/bottom/doctop five separate times — once per object type — but never explains why there are two systems, which one bounding boxes use, or how to convert between them. That gap keeps generating issues: #198, #845, #1332, and as recently as this April, #1369, where a user reported as a bug that "y-coordinates are top-to-bottom for words but bottom-to-top for line points."

The section covers:

  • which properties are measured from the page bottom (y0/y1) versus the page top (top/bottom) and the document top (doctop);
  • that bounding boxes are (x0, top, x1, bottom), not (x0, y0, x1, y1), and that this is what .crop(...) / .within_bbox(...) expect;
  • why the top-left origin was chosen, summarized from your answer in bbox inversion #198;
  • the conversion top == page.height - y1, with the caveat that a non-zero MediaBox origin shifts it by page.mediabox[1];
  • that objects parsed from the PDF carry both varieties, while .extract_words(...) and .search(...) results carry only the top-down ones, and that pts are (x, top) tuples.

Every factual claim was checked against the library rather than against the existing prose:

  • top == page.height - y1 and bottom == page.height - y0 hold exactly on a standard page, and fail by exactly page.mediabox[1] on tests/pdfs/issue-1181.pdf (MediaBox origin at y = -200), which is what the caveat records.
  • The property inventory was confirmed by inspecting real objects: char/line/rect/curve/image/annot/hyperlink carry all seven; .extract_words(...) returns top/bottom/doctop and no y0/y1; .search(...) returns top/bottom.
  • Using the reproduction attached to y-coordinates are "top to bottom" for words but "bottom to top" for line points. #1369, the documented rule accounts for the reported numbers exactly: read consistently in the top-down system, the two words land within 4 points of the line's endpoints, which is the agreement the reporter expected.

Docs-only, so no tests are added. make lint and make tests both pass locally (172 passed).

The README defines y0/y1/top/bottom/doctop once per object type, in five
separate property tables, but never explains why there are two vertical
coordinate systems, which one bounding boxes use, or how to convert between
them. That gap has repeatedly been reported as a bug: jsvine#198, jsvine#845, jsvine#1332,
and most recently jsvine#1369.

Add a "Coordinates" section above the per-object tables covering which
properties are measured from the page bottom versus the page and document
top, that bounding boxes are (x0, top, x1, bottom), why the top-left origin
was chosen, the conversion between the systems and its MediaBox caveat, and
which objects carry which properties.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread README.md Outdated

Not every object carries both varieties. Objects parsed directly from the PDF — `char`, `line`, `rect`, `curve`, `image`, `annot`, and `hyperlink` — have all of the properties above. Objects that `pdfplumber` computes for you use only the top-down properties: the dicts returned by `.extract_words(...)` provide `top`, `bottom`, and `doctop` but no `y0`/`y1`, and those returned by `.search(...)` provide `top` and `bottom`. Relatedly, a `line`'s or `curve`'s `pts` are `(x, top)` tuples.

Because object bounding boxes are normalized, `top` is never greater than `bottom`, and `height` (equal to `bottom - top`) is never negative.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can probably cut this line. Or does it clarify something that is a common source of confusion? (Perhaps I'm just overlooking that.)

Comment thread README.md

The top-left origin was chosen because ([#198](https://github.com/jsvine/pdfplumber/issues/198)) people generally read PDFs from the top-left toward the bottom-right; because measuring from the top is what makes `doctop` — the distance from the top of the *whole document* — able to distinguish objects at similar heights on different pages; and because most adjacent layout systems (SVG, Canvas, PIL/Pillow, which `pdfplumber` itself uses for visual debugging) also put the origin in the top-left.

For a page whose `MediaBox` begins at the origin — the typical case — the two systems convert simply:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the y-flavored variables are the "original" ones, and top/bottom the conversions, I might suggest framing the code block below in those terms, instead of vice versa.

Comment thread README.md Outdated

The `y0`/`y1` pair follows the PDF specification's own coordinate system, and is retained for compatibility with `pdfminer.six`. Everything else in `pdfplumber` places the origin in the __top-left__ corner. In particular, bounding boxes are expressed as `(x0, top, x1, bottom)` — *not* `(x0, y0, x1, y1)` — which is what `.crop(...)`, `.within_bbox(...)`, and the visual-debugging methods expect.

The top-left origin was chosen because ([#198](https://github.com/jsvine/pdfplumber/issues/198)) people generally read PDFs from the top-left toward the bottom-right; because measuring from the top is what makes `doctop` — the distance from the top of the *whole document* — able to distinguish objects at similar heights on different pages; and because most adjacent layout systems (SVG, Canvas, PIL/Pillow, which `pdfplumber` itself uses for visual debugging) also put the origin in the top-left.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doctop part of the explanation, although true, feels like a bit of a distraction, and slightly difficult to follow. Maybe just keep this paragraph to the first and third parts?

Comment thread README.md
|`top`, `bottom`| Top of the page | Downward |
|`doctop`| Top of the *document* | Downward |

The `y0`/`y1` pair follows the PDF specification's own coordinate system, and is retained for compatibility with `pdfminer.six`. Everything else in `pdfplumber` places the origin in the __top-left__ corner. In particular, bounding boxes are expressed as `(x0, top, x1, bottom)` — *not* `(x0, y0, x1, y1)` — which is what `.crop(...)`, `.within_bbox(...)`, and the visual-debugging methods expect.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd cut the , and is retained for ... part of the first sentence. I'd also cut the part inside the em-dashes.

Comment thread README.md

#### Coordinates

Horizontal coordinates are unambiguous: `x0` and `x1` are both measured from the left edge of the page. Vertical coordinates come in two varieties, and mixing them is a common source of confusion:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might reframe this slightly. It's less that the coordinates "come in two varieties", but rather that pdfplumber uses top/bottom but retains reference to the original y0/y1 coords.

@jsvine

jsvine commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Thanks, @soodoku! I do think it's a good idea to clarify the coordinate system. I've made some specific suggestions in the line-comments above. More broadly: The location in the README feels a bit off, since it breaks up the discussion of object types. This might go better at the end of that section, right before Visual Debugging. What do you think?

@soodoku

soodoku commented Aug 3, 2026

Copy link
Copy Markdown
Author

Thanks for the helpful suggestions — I’ve addressed all three in 619c3c5: removed the normalization sentence, reframed the conversions from y0/y1 to top/bottom, and simplified the origin explanation by removing the doctop aside. make lint and all 172 tests pass locally. Happy to adjust further if needed.

@jsvine

jsvine commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Wow, quick work! I'm curious: Any chance this is an LLM responding / committing?

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.

2 participants