Skip to content

0033: The seal honours the page's rotation ​

Status: implemented.

Context ​

/Rotate turns a page clockwise for display and leaves its coordinate system alone (ISO 32000-1 §7.7.3.3). SealPlacement is documented in terms of where the seal appears, and the caller giving those coordinates is looking at the document in a reader, where it is already turned.

The two were never reconciled. The placement went straight into /Rect, and grep -rn "Rotate" src/ returned nothing at all: the key was read nowhere.

Measured on a page carrying /Rotate 90, with a placement of (60, 400):

Rect[60 400 180 460]

The caller's numbers, untouched. On screen that is somewhere else entirely, the seal reads sideways, and on a page rotated 90 or 270 it can fall outside the visible area, since the displayed width and height have swapped.

/Rotate 90 is how most scanners and many generators express landscape. This was not an exotic input.

Decision ​

The placement is where the seal appears, and the file records where that is.

Two things follow, and only doing the first would be worse than doing neither: the seal would land correctly and read sideways.

  • /Rect is mapped from displayed coordinates into user space. For a quarter turn clockwise the user-space origin, the bottom left, is displayed at the top left, so a displayed point (x, y) sits at user (width − y, x).
  • The appearance carries a /Matrix turning it the other way, because a form XObject is drawn in user space and the display rotation applies to it too. No translation is needed: the reader maps the transformed bounding box onto /Rect (§12.5.5).

/Rotate and /MediaBox are read with inheritance, through /Parent. Both are inheritable (§7.7.3.4, Table 30), and one declaration on /Pages is the ordinary way to say "this document is landscape". Reading the page object alone would have missed the common case.

Geometry is per page, not per document. A file can carry a landscape scan beside a portrait cover, and SealPlacement can put a stamp on both.

Verification ​

The rectangle is asserted at every rotation, including 360 and −90, which normalise to no turn and to 270.

The claim that matters is about what a reader shows, so it was checked with one. Both documents rendered with poppler's pdftoppm at 40 dpi, and the ink located:

Asked for, as displayedRendered
No rotationx 10-30%, y 45-52%x 10-30%, y 45-51%
/Rotate 90x 7-21%, y 23-33%x 7-21%, y 24-33%

The rotated page's ink also spans 120 pt across and 60 pt down, so the seal is neither distorted nor turned on screen.

Consequences ​

  • A document with no rotation produces the bytes it did before. The mapping returns its input and no /Matrix is written, so every committed sample and every conformance verdict measured against them is unaffected.
  • A page with no /MediaBox anywhere above it behaves as unrotated. The mapping needs the page's size and inventing one would put the seal somewhere arbitrary; landing where it used to is at least predictable.
  • Signing\Incremental\PageGeometry is where the mapping lives, so the arithmetic is testable without signing anything.

Alternatives rejected ​

Why not
Treat the placement as user space and let the caller compensateThe caller is looking at a reader. The package is the only party that knows the page is turned
/MK <</R 90>> on the widgetReaders vary in whether they honour it. A matrix on the appearance is what the rendering algorithm is specified to apply
Read /Rotate from the page onlyMisses the document declared landscape once on /Pages, which is the common shape
Fix the rectangle and leave the appearanceThe seal lands correctly and reads sideways, which looks like a different bug

Released under the MIT Licence.