watermark: source page /Rotate is ignored when sizing and positioning a PDF watermark
What happens
When the watermark source page has a /Rotate entry, pdfcpu computes the
watermark's scale and position from the unrotated MediaBox, and applies the
rotation afterwards to the placement matrix.
So a page stored landscape with /Rotate 270 is placed in the right orientation,
but at the wrong size and in the wrong place.
This came up with a scanned PDF used as the watermark. Scanners commonly store a
portrait A4 page landscape and set /Rotate 270, so it is stored 842x595 but
displays as 595x842. Overlaying it on a 595x842 portrait document should be a 1:1
fit, but pdfcpu measures the unrotated 842 wide box, shrinks the watermark by 29%
and centres it on the wrong box. Any scanned page saved this way is affected.
Reproduce
Build a rotated watermark page and a matching destination page from any single
page PDF (in.pdf):
pdfcpu resize -- "dim:842 595, enforce:on" in.pdf land.pdf
pdfcpu rotate -- land.pdf 270 wm.pdf # MediaBox 842x595, /Rotate 270, displays 595x842
pdfcpu resize -- "dim:595 842, enforce:on" in.pdf dest.pdfwm.pdf displays at exactly the size of dest.pdf, so applying it at
scale:1 rel should cover the page 1:1:
pdfcpu watermark add -m pdf -- "wm.pdf:1" "scale:1 rel, position:c, rotation:0" dest.pdf out.pdfInstead the watermark is scaled by 0.70665 (= 595/842, the unrotated aspect ratio) and shifted about 508pt / 123pt off position. Expected is scale 1.0 at offset 0,0.
scale:1 abs fixes the size but not the position, because the anchor is still
computed on the unrotated box.
Also affects multi page watermarks
The source rotation is carried by subtracting it from the watermark rotation, and
that happens once per page, so it accumulates across a multi page watermark:
-270 - 270 = 180. Every page then comes out upside down.
Cause
In pkg/pdfcpu/stamp.go, createPDFRes takes the watermark's visible region from
viewPort(inhPAttrs) without accounting for /Rotate. That region is what the
bounding box is scaled against and what the anchor is positioned against, so both
are computed in the wrong orientation.
normalizePageWatermark in the same file already does the matching box swap for
the destination page — it is just never applied to the watermark source page.
Environment
Reproduced on v0.12.1, v0.13.0 and master (105c0c28), so not a regression. go1.25.0, linux/amd64.
Source: pdfcpu/pdfcpu