Skip to content

Bug: API.internal.pages not updated when restoring a RenderTarget #3898

@Opineppes

Description

@Opineppes

Environment

  • jsPDF version: 3.0.3
  • nodejs: 20.19.0
  • OS: Linux ubuntu 24

Description

When restoring a RenderTarget (e.g., after creating a FormObject), the internal reference API.internal.pages is not updated to reflect the restored pages array.

This results in a desynchronization between the internal rendering context and the public API (doc.internal.pages), which causes incorrect behavior in plugins or custom rendering logic that relies on the internal API.

Minimal code example

const doc = new jsPDF();

const pages1 = doc.internal.pages;

doc.beginFormObject( 0, 0, doc.internal.pageSize.width, context.internal.pageSize.height, context.Matrix( 1, 0, 0, 1, 0, 0 ) );
doc.endFormObject("myFormObject");

doc.addPage( "a4", "landscape" );

const pages2 = doc.internal.pages );

console.log( pages1, pages2, pages1 === pages2 );

Expected behavior

After restoring the previous render target,
API.internal.pages should be updated to reference the restored pages array,
so that the public and internal contexts stay in sync.

Actual behavior

API.internal.pages continues pointing to the old pages array created before beginFormObject().
This leads to inconsistencies when interacting with doc.internal.pages or plugins that use it.

Suspected cause

In RenderTarget.prototype.restore() (see snippet below),
the pages variable is reassigned,
but the internal API references (API.internal.pages) is not updated:

jsPDF/src/jspdf.js

Lines 5722 to 5733 in 463b199

RenderTarget.prototype.restore = function() {
page = this.page;
currentPage = this.currentPage;
pagesContext = this.pagesContext;
pages = this.pages;
pageX = this.x;
pageY = this.y;
pageMatrix = this.matrix;
setPageWidthWithoutScaling(currentPage, this.width);
setPageHeightWithoutScaling(currentPage, this.height);
outputDestination = this.outputDestination;
};

Suggested fix

Rebind API.internal.pages after restoring the render target:

RenderTarget.prototype.restore = function() {
  page = this.page;
  currentPage = this.currentPage;
  pagesContext = this.pagesContext;
  API.internal.pages = pages = this.pages;
  pageX = this.x;
  pageY = this.y;
  pageMatrix = this.matrix;
  setPageWidthWithoutScaling(currentPage, this.width);
  setPageHeightWithoutScaling(currentPage, this.height);
  outputDestination = this.outputDestination;
};

Why this matters

  • Keeps the internal and public state consistent.
  • Prevents plugins and internal tools that use API.internal.pages from breaking after a render target restore.
  • Maintains predictable document behavior when working with FormObjects or custom render targets.

PS: Generated by chatgpt and edited by a Human

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions