Skip to content

Fix putImage mutating the global filters array#3987

Open
douglasmatheus wants to merge 1 commit into
parallax:masterfrom
douglasmatheus:fix/putimage-mutates-global-filters
Open

Fix putImage mutating the global filters array#3987
douglasmatheus wants to merge 1 commit into
parallax:masterfrom
douglasmatheus:fix/putimage-mutates-global-filters

Conversation

@douglasmatheus
Copy link
Copy Markdown

Problem

putImage calls splice() on the array returned by getFilters() to remove "FlateEncode" before writing image data. But getFilters() returns the live reference to the document's internal filters array, not a copy:

https://github.com/parallax/jsPDF/blob/master/src/jspdf.js#L1748-L1750

var getFilters = (API.__private__.getFilters = function() {
  return filters;
});

So the splice in putImage permanently empties the document's filters array. After the first image is rendered, all subsequent page content streams are emitted without compression.

Symptoms

Calling doc.output() twice in a row on a document containing any image produces PDFs of dramatically different sizes:

const doc = new jsPDF({ compress: true });
doc.addImage(jpg, "JPEG", 10, 10, 100, 75);

const a = doc.output();
const b = doc.output();

console.log(a.length, b.length);
// → 5440, 110xxx   (page content stream goes from FlateEncoded ~5KB to raw ~100KB)

The first output is correct; every output after that has uncompressed page content streams. The PDF still renders fine in viewers, just much larger than expected.

Cause

src/modules/addimage.js line 206-208:

var filter = getFilters();
while (filter.indexOf("FlateEncode") !== -1) {
  filter.splice(filter.indexOf("FlateEncode"), 1);
}

Since filter and the document's internal filters are the same array reference, the splice strips "FlateEncode" from the document-wide configuration.

Fix

One-line change: copy the array before mutating.

var filter = getFilters().slice();

putImage still gets its local "filters minus FlateEncode" list, and the document's filters array is unaffected.

Test

Added a regression test in test/specs/addimage.spec.js that asserts repeated doc.output() calls return identical buffers. All 541 unit tests pass locally.

Notes

  • I did not regenerate dist/ per the CONTRIBUTING guideline.
  • Discovered while debugging cumulative PDF growth in a library that wraps jsPDF for canvas-editor-pdf. Happy to add more context if useful.

The internal getFilters() returns the live reference to the document's
filters array. putImage was calling splice() on that reference to strip
"FlateEncode" before writing image data — but this side-effect persists
between save()/output() calls.

After the first putImage during a save, the document's filters array
becomes empty, so all subsequent page content streams are emitted
uncompressed. Result: the 1st PDF output is normal size, but every
subsequent output() balloons (e.g. 5 KB compressed page stream becomes
95 KB raw, only on documents that contain images).

Fix: copy the array via .slice() before mutating, so putImage's local
view is independent of the document state.

Repro:
  const doc = new jsPDF({ compress: true });
  doc.addImage(jpg, "JPEG", 10, 10, 100, 75);
  const a = doc.output();
  const b = doc.output();
  console.assert(a.length === b.length); // failed before this fix

Regression covered by new test in test/specs/addimage.spec.js.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant