Skip to content

drawPage({ width, height }) draws a rotated embedded page transposed (scales against rotation-swapped embedded.width/height, but content stays un-rotated) #83

Description

@alfikiafan

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions