Environments
Version: 0.4.0
Node version: v24.11.1
Description
embedPage() returns a PDFEmbeddedPage whose .width/.height are rotation-swapped to match the source page's /Rotate (e.g. a raw 1000×200 page rotated 90° reports embedded.width=200, embedded.height=1000). However, the underlying Form XObject's content stream and /BBox stay raw/un-rotated, and drawPage() never applies a rotation transform unless the caller explicitly passes options.rotate.
When drawPage() is called with width/height (not scale), it computes scaleX = width / embedded.width and scaleY = height / embedded.height. Since embedded.width/height are already swapped but the content being scaled is not, the actual drawn footprint comes out transposed relative to what was requested.
Reproduction
import { PDF } from '@libpdf/core';
const srcDoc = PDF.create();
srcDoc.addPage({ width: 1000, height: 200 }).setRotation(90); // raw 1000x200, visually 200x1000
const newDoc = PDF.create();
const embedded = await newDoc.embedPage(await PDF.load(await srcDoc.save()), 0);
const page = newDoc.addPage({ width: 2000, height: 2000 });
// Ask to draw the embedded (rotated) page at a 400x2000 visual size.
page.drawPage(embedded, { x: 0, y: 0, width: 400, height: 2000 });
Expected behavior
The drawn content occupies a 400×2000 footprint (or the page itself reflects the rotation visually).
Actual behavior
the content is drawn at a 2000×400 footprint — transposed, because scaleX = 400/embedded.width(200) = 2, scaleY = 2000/embedded.height(1000) = 2, applied uniformly to the raw (un-rotated) 1000×200 content, yielding 2000×400 instead of 400×2000.
Root cause
embedPage()'s reported width/height (rotation-aware) is inconsistent with drawPage()'s scaling math, which implicitly assumes the embedded content is already oriented to match those reported dimensions. Either embedded.width/height shouldn't be rotation-swapped, or drawPage() should auto-apply the source page's rotation when drawing via width/height.
Workaround used downstream
Pass scale (a uniform number) instead of width/height, computed against the raw (un-swapped) source dimensions on both sides, avoiding embedded.width/height entirely.
Environments
Version: 0.4.0
Node version: v24.11.1
Description
embedPage() returns a PDFEmbeddedPage whose .width/.height are rotation-swapped to match the source page's /Rotate (e.g. a raw 1000×200 page rotated 90° reports embedded.width=200, embedded.height=1000). However, the underlying Form XObject's content stream and /BBox stay raw/un-rotated, and drawPage() never applies a rotation transform unless the caller explicitly passes options.rotate.
When drawPage() is called with width/height (not scale), it computes scaleX = width / embedded.width and scaleY = height / embedded.height. Since embedded.width/height are already swapped but the content being scaled is not, the actual drawn footprint comes out transposed relative to what was requested.
Reproduction
Expected behavior
The drawn content occupies a 400×2000 footprint (or the page itself reflects the rotation visually).
Actual behavior
the content is drawn at a 2000×400 footprint — transposed, because scaleX = 400/embedded.width(200) = 2, scaleY = 2000/embedded.height(1000) = 2, applied uniformly to the raw (un-rotated) 1000×200 content, yielding 2000×400 instead of 400×2000.
Root cause
embedPage()'s reported width/height (rotation-aware) is inconsistent with drawPage()'s scaling math, which implicitly assumes the embedded content is already oriented to match those reported dimensions. Either embedded.width/height shouldn't be rotation-swapped, or drawPage() should auto-apply the source page's rotation when drawing via width/height.
Workaround used downstream
Pass scale (a uniform number) instead of width/height, computed against the raw (un-swapped) source dimensions on both sides, avoiding embedded.width/height entirely.