Skip to content

Measure annotation rotation against the page, not a crop of it - #1436

Open
Krushil-Dhankecha wants to merge 1 commit into
jsvine:developfrom
Krushil-Dhankecha:fix/annots-on-a-rotated-crop
Open

Krushil-Dhankecha wants to merge 1 commit into
jsvine:developfrom
Krushil-Dhankecha:fix/annots-on-a-rotated-crop

Conversation

@Krushil-Dhankecha

Copy link
Copy Markdown

Cropping a rotated page moves its annotations, and then the crop keeps the wrong ones.

Page.annots turns each annotation's corners through the page, then flips them into pdfplumber's top-left origin. The flip reads self.root_page.height, but the turns read self.width and self.height — and a CroppedPage redefines those as the size of the crop. So on a page with /Rotate, every annotation moves by however much was cropped away. Because _crop_fn then filters on the moved coordinates, crop() and within_bbox() also keep the wrong annotations. This is the part of #1171 that the rotation case missed; the coordinates on an uncropped page have always been right.

Which dimension goes wrong follows the number of turns: a 90-degree page shifts when the crop changes the height, a 270-degree page when it changes the width, and a 180-degree page on either.

To reproduce

Using tests/pdfs/annotations-rotated-180.pdf:

import pdfplumber

with pdfplumber.open("tests/pdfs/annotations-rotated-180.pdf") as pdf:
    page = pdf.pages[0]           # rotation 180, bbox (0, 0, 595, 842)
    annot = page.annots[2]
    print(annot["x0"], annot["x1"])

    cropped = page.crop((0, 0, 585, 842))   # 10pt off the right edge
    print(cropped.annots[2]["x0"], cropped.annots[2]["x1"])
412.99 540.0
402.99 530.0

The characters do not move, so the annotation no longer sits over its text. Cropping further makes the filtering wrong as well — with crop((0, 0, 420, 842)) all five annotations are returned, shifted 175pt left, including one that really sits at x 594-595 and is nowhere near the crop:

annotations kept: 5 of 5
  'Author Name'        x0=239.79 x1=363.20
  'Author Name'        x0=215.45 x1=286.30
  'Jeremy Singer-Vine' x0=237.99 x1=365.00
  'Jeremy Singer-Vine' x0=146.69 x1=172.69
  None                 x0=419.00 x1=420.00

With the fix, four are returned, clipped to the crop, and the one at x 594-595 is dropped.

The change

rotate_point reads the original page's dimensions, which is what the flip beside it already does. Nothing else changes: an uncropped page, an unrotated page, and the objects on any page are all byte-identical.

Tests

tests/test_basics.py::Test::test_annots_rotated_and_cropped covers the gap between the two tests that already exist — test_annots_cropped crops an unrotated page, test_annots_rotated reads a rotated page whole. For each of the four rotations it takes a strip off both the width and the height, so every rotation meets the dimension its turns are measured against, and asserts that the annotations inside the crop keep the coordinates they have on the whole page. It then checks the filtering on the 180-degree page: four of the five annotations reach left of x=420.

The test fails on the parent commit and passes with the fix. The other three annotation tests pass either way.

Full suite: 168 passed. The 6 failures in tests/test_repair.py are Ghostscript not being installed here, and they fail the same way on the parent commit. black, isort, flake8 and mypy --strict are all clean.

Page.annots turns each annotation's corners through the page before
flipping them into pdfplumber's top-left origin. The flip already reads
the root page's height, but the rotation read self.width and
self.height, which a CroppedPage redefines as the size of the crop. On a
rotated page every annotation therefore moved by however much was
cropped away, and because the crop filter then runs on the moved
coordinates, crop() and within_bbox() kept the wrong annotations too.

Which dimension went wrong followed the number of turns: a 90-degree
page shifted when the crop changed the height, a 270-degree page when it
changed the width, and a 180-degree page on either.
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