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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions