From 9025c3fc56625becfe895943f957f91a4fda47e5 Mon Sep 17 00:00:00 2001 From: Jeremy Singer-Vine Date: Mon, 5 Aug 2024 12:28:42 -0400 Subject: [PATCH] Fix object coordinates when mediabox != (0,0,...) See issue #1181 for details. --- CHANGELOG.md | 1 + README.md | 1 + pdfplumber/page.py | 13 +++++++++++-- tests/pdfs/issue-1181.pdf | Bin 0 -> 13437 bytes tests/test_issues.py | 22 ++++++++++++++++++++++ 5 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/pdfs/issue-1181.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md index ea93cff..4ae80c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. The format ### Fixed +- Fix handling of object coordinates when `mediabox` does not begin at `(0,0)` (h/t @wodny). ([#1181](https://github.com/jsvine/pdfplumber/issues/1181)) - Fix error on getting `.annots`/`.hyperlinks` from `CroppedPage` (due to missing `.rotation` and `.initial_doctop` attributes) (h/t @Safrone). ([#1171](https://github.com/jsvine/pdfplumber/issues/1171) + [e5737d2](https://github.com/jsvine/pdfplumber/commit/e5737d2)) - Fix problem where `Page.crop(...)` was not cropping `.annots/.hyperlinks` (h/t @Safrone). [#1171](https://github.com/jsvine/pdfplumber/issues/1171) - Fix calculation of coordinates for `.annots` on `CroppedPage`s. ([0bbb340](https://github.com/jsvine/pdfplumber/commit/0bbb340)) diff --git a/README.md b/README.md index edc2174..8753d48 100644 --- a/README.md +++ b/README.md @@ -568,6 +568,7 @@ Many thanks to the following users who've contributed ideas, features, and fixes - [Aron Weiler](https://github.com/aronweiler) - [Quentin André](https://github.com/QuentinAndre11) - [Léo Roux](https://github.com/leorouxx) +- [@wodny](https://github.com/wodny) ## Contributing diff --git a/pdfplumber/page.py b/pdfplumber/page.py index d872128..f992c3e 100644 --- a/pdfplumber/page.py +++ b/pdfplumber/page.py @@ -415,11 +415,20 @@ class Page(Container): attr["dash"] = obj.dashing_style + # As noted in #1181, `pdfminer.six` adjusts objects' + # coordinates relative to the MediaBox: + # https://github.com/pdfminer/pdfminer.six/blob/1a8bd2f730295b31d6165e4d95fcb5a03793c978/pdfminer/converter.py#L79-L84 + mb_x0, mb_top = self.mediabox[:2] + if "y0" in attr: - attr["top"] = self.height - attr["y1"] - attr["bottom"] = self.height - attr["y0"] + attr["top"] = (self.height - attr["y1"]) + mb_top + attr["bottom"] = (self.height - attr["y0"]) + mb_top attr["doctop"] = self.initial_doctop + attr["top"] + if "x0" in attr and mb_x0 != 0: + attr["x0"] = attr["x0"] + mb_x0 + attr["x1"] = attr["x1"] + mb_x0 + return attr def iter_layout_objects( diff --git a/tests/pdfs/issue-1181.pdf b/tests/pdfs/issue-1181.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4c11d8a93a756323acec812f622414496aa396b0 GIT binary patch literal 13437 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 literal 0 HcmV?d00001 diff --git a/tests/test_issues.py b/tests/test_issues.py index 3b83d8c..3a7819b 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -310,3 +310,25 @@ class Test(unittest.TestCase): page = pdf.pages[0] # Should not error: assert page.extract_text() + + def test_issue_1181(self): + """ + Correctly re-calculate coordinates when MediaBox does not start at (0,0) + """ + path = os.path.join(HERE, "pdfs/issue-1181.pdf") + with pdfplumber.open(path) as pdf: + p0, p1 = pdf.pages + assert p0.crop(p0.bbox).extract_table() == [ + ["FooCol1", "FooCol2", "FooCol3"], + ["Foo4", "Foo5", "Foo6"], + ["Foo7", "Foo8", "Foo9"], + ["Foo10", "Foo11", "Foo12"], + ["", "", ""], + ] + assert p1.crop(p1.bbox).extract_table() == [ + ["BarCol1", "BarCol2", "BarCol3"], + ["Bar4", "Bar5", "Bar6"], + ["Bar7", "Bar8", "Bar9"], + ["Bar10", "Bar11", "Bar12"], + ["", "", ""], + ]