High severityNVD Advisory· Published Aug 26, 2025· Updated Aug 26, 2025
jsPDF Parsing of Corrupt PNGs Leads to Potential Denial of Service (DoS)
CVE-2025-57810
Description
jsPDF is a library to generate PDFs in JavaScript. Prior to 3.0.2, user control of the first argument of the addImage method results in CPU utilization and denial of service. If given the possibility to pass unsanitized image data or URLs to the addImage method, a user can provide a harmful PNG file that results in high CPU utilization and denial of service. The vulnerability was fixed in jsPDF 3.0.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
jspdfnpm | < 3.0.2 | 3.0.2 |
Affected products
1Patches
14cf3ab619e56Fix parsing corrupt PNG images in the addImage method (#3880)
28 files changed · +2268 −2246
bower.json+1 −1 modified@@ -67,7 +67,7 @@ "rollup-plugin-license": "^2.1.0", "rollup-plugin-node-resolve": "4.2.3", "rollup-plugin-preprocess": "0.0.4", - "rollup-plugin-terser": "^6.1.0", + "@rollup/plugin-terser": "^0.4.4", "typescript": "^3.9.6" }, "license": "MIT",
package.json+3 −4 modified@@ -24,9 +24,8 @@ }, "dependencies": { "@babel/runtime": "^7.26.9", - "atob": "^2.1.2", - "btoa": "^1.2.1", - "fflate": "^0.8.1" + "fflate": "^0.8.1", + "fast-png": "^6.2.0" }, "optionalDependencies": { "canvg": "^3.0.11", @@ -40,6 +39,7 @@ "@babel/preset-env": "^7.10.4", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-replace": "^2.3.3", + "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^8.0.0", "@types/jasmine": "^3.5.11", "@types/node": "^20.16.5", @@ -82,7 +82,6 @@ "rollup-plugin-license": "^2.1.0", "rollup-plugin-node-resolve": "5.2.0", "rollup-plugin-preprocess": "0.0.4", - "rollup-plugin-terser": "^6.1.0", "typescript": "^5.6.2", "yarpm": "^0.2.1" },
package-lock.json+1785 −1154 modifiedrollup.config.js+19 −6 modified@@ -1,4 +1,4 @@ -import { terser } from "rollup-plugin-terser"; +import terser from "@rollup/plugin-terser"; import { babel } from "@rollup/plugin-babel"; import RollupPluginPreprocess from "rollup-plugin-preprocess"; import resolve from "rollup-plugin-node-resolve"; @@ -37,6 +37,19 @@ const umdExternals = matchSubmodules([ ...Object.keys(pkg.peerDependencies || {}), ...Object.keys(pkg.optionalDependencies || {}) ]); + +const terserOptions = { + ecma: 2023, + module: true, + compress: { + ecma: 2023, + passes: 2 + }, + mangle: { + safari10: true + } +}; + const externals = matchSubmodules([ ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), @@ -57,7 +70,7 @@ const umd = { file: "dist/jspdf.umd.min.js", format: "umd", name: "jspdf", - plugins: [terser({})], + plugins: [terser(terserOptions)], exports: "named", sourcemap: true } @@ -88,7 +101,7 @@ const es = { format: "es", name: "jspdf", sourcemap: true, - plugins: [terser({})] + plugins: [terser(terserOptions)] } ], external: externals, @@ -117,7 +130,7 @@ const node = { name: "jspdf", exports: "named", sourcemap: true, - plugins: [terser({})] + plugins: [terser(terserOptions)] } ], external: externals, @@ -136,7 +149,7 @@ const umdPolyfills = { file: "dist/polyfills.umd.js", format: "umd", name: "jspdf-polyfills", - plugins: [terser({})] + plugins: [terser(terserOptions)] } ], external: [], @@ -159,7 +172,7 @@ const esPolyfills = { file: "dist/polyfills.es.js", format: "es", name: "jspdf-polyfills", - plugins: [terser({})] + plugins: [terser(terserOptions)] } ], external: externals,
src/libs/AtobBtoa.js+2 −14 modified@@ -1,18 +1,6 @@ import { globalObject } from "./globalObject.js"; -var atob, btoa; - -(function() { - // @if MODULE_FORMAT!='cjs' - atob = globalObject.atob.bind(globalObject); - btoa = globalObject.btoa.bind(globalObject); - return; - // @endif - - // @if MODULE_FORMAT='cjs' - atob = require("atob"); - btoa = require("btoa"); - // @endif -})(); +const atob = globalObject.atob.bind(globalObject); +const btoa = globalObject.btoa.bind(globalObject); export { atob, btoa };
src/libs/fast-png.js+1 −0 added@@ -0,0 +1 @@ +export { decode } from "fast-png";
src/libs/png.js+0 −598 removed@@ -1,598 +0,0 @@ -// Generated by CoffeeScript 1.4.0 - -/** - * @license - * PNG.js - * Copyright (c) 2011 Devon Govett - * MIT LICENSE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons - * to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING - * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -import { unzlibSync } from "./fflate.js"; -import { globalObject } from "./globalObject.js"; - -var PNG = (function() { - var APNG_BLEND_OP_OVER, - APNG_BLEND_OP_SOURCE, - APNG_DISPOSE_OP_BACKGROUND, - APNG_DISPOSE_OP_NONE, - APNG_DISPOSE_OP_PREVIOUS, - makeImage, - scratchCanvas, - scratchCtx; - - APNG_DISPOSE_OP_NONE = 0; - - APNG_DISPOSE_OP_BACKGROUND = 1; - - APNG_DISPOSE_OP_PREVIOUS = 2; - - APNG_BLEND_OP_SOURCE = 0; - - APNG_BLEND_OP_OVER = 1; - - function PNG(data) { - var chunkSize, - colors, - palLen, - delayDen, - delayNum, - frame, - i, - index, - key, - section, - palShort, - text, - _i, - _j, - _ref; - this.data = data; - this.pos = 8; - this.palette = []; - this.imgData = []; - this.transparency = {}; - this.animation = null; - this.text = {}; - frame = null; - while (true) { - chunkSize = this.readUInt32(); - section = function() { - var _i, _results; - _results = []; - for (i = _i = 0; _i < 4; i = ++_i) { - _results.push(String.fromCharCode(this.data[this.pos++])); - } - return _results; - } - .call(this) - .join(""); - switch (section) { - case "IHDR": - this.width = this.readUInt32(); - this.height = this.readUInt32(); - this.bits = this.data[this.pos++]; - this.colorType = this.data[this.pos++]; - this.compressionMethod = this.data[this.pos++]; - this.filterMethod = this.data[this.pos++]; - this.interlaceMethod = this.data[this.pos++]; - break; - case "acTL": - this.animation = { - numFrames: this.readUInt32(), - numPlays: this.readUInt32() || Infinity, - frames: [] - }; - break; - case "PLTE": - this.palette = this.read(chunkSize); - break; - case "fcTL": - if (frame) { - this.animation.frames.push(frame); - } - this.pos += 4; - frame = { - width: this.readUInt32(), - height: this.readUInt32(), - xOffset: this.readUInt32(), - yOffset: this.readUInt32() - }; - delayNum = this.readUInt16(); - delayDen = this.readUInt16() || 100; - frame.delay = (1000 * delayNum) / delayDen; - frame.disposeOp = this.data[this.pos++]; - frame.blendOp = this.data[this.pos++]; - frame.data = []; - break; - case "IDAT": - case "fdAT": - if (section === "fdAT") { - this.pos += 4; - chunkSize -= 4; - } - data = (frame != null ? frame.data : void 0) || this.imgData; - for ( - i = _i = 0; - 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; - i = 0 <= chunkSize ? ++_i : --_i - ) { - data.push(this.data[this.pos++]); - } - break; - case "tRNS": - this.transparency = {}; - switch (this.colorType) { - case 3: - palLen = this.palette.length / 3; - this.transparency.indexed = this.read(chunkSize); - if (this.transparency.indexed.length > palLen) - throw new Error("More transparent colors than palette size"); - /* - * According to the PNG spec trns should be increased to the same size as palette if shorter - */ - //palShort = 255 - this.transparency.indexed.length; - palShort = palLen - this.transparency.indexed.length; - if (palShort > 0) { - for ( - i = _j = 0; - 0 <= palShort ? _j < palShort : _j > palShort; - i = 0 <= palShort ? ++_j : --_j - ) { - this.transparency.indexed.push(255); - } - } - break; - case 0: - this.transparency.grayscale = this.read(chunkSize)[0]; - break; - case 2: - this.transparency.rgb = this.read(chunkSize); - } - break; - case "tEXt": - text = this.read(chunkSize); - index = text.indexOf(0); - key = String.fromCharCode.apply(String, text.slice(0, index)); - this.text[key] = String.fromCharCode.apply( - String, - text.slice(index + 1) - ); - break; - case "IEND": - if (frame) { - this.animation.frames.push(frame); - } - this.colors = function() { - switch (this.colorType) { - case 0: - case 3: - case 4: - return 1; - case 2: - case 6: - return 3; - } - }.call(this); - this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6; - colors = this.colors + (this.hasAlphaChannel ? 1 : 0); - this.pixelBitlength = this.bits * colors; - this.colorSpace = function() { - switch (this.colors) { - case 1: - return "DeviceGray"; - case 3: - return "DeviceRGB"; - } - }.call(this); - this.imgData = new Uint8Array(this.imgData); - return; - default: - this.pos += chunkSize; - } - this.pos += 4; - if (this.pos > this.data.length) { - throw new Error("Incomplete or corrupt PNG file"); - } - } - } - - PNG.prototype.read = function(bytes) { - var i, _i, _results; - _results = []; - for ( - i = _i = 0; - 0 <= bytes ? _i < bytes : _i > bytes; - i = 0 <= bytes ? ++_i : --_i - ) { - _results.push(this.data[this.pos++]); - } - return _results; - }; - - PNG.prototype.readUInt32 = function() { - var b1, b2, b3, b4; - b1 = this.data[this.pos++] << 24; - b2 = this.data[this.pos++] << 16; - b3 = this.data[this.pos++] << 8; - b4 = this.data[this.pos++]; - return b1 | b2 | b3 | b4; - }; - - PNG.prototype.readUInt16 = function() { - var b1, b2; - b1 = this.data[this.pos++] << 8; - b2 = this.data[this.pos++]; - return b1 | b2; - }; - - PNG.prototype.decodePixels = function(data) { - var pixelBytes = this.pixelBitlength / 8; - var fullPixels = new Uint8Array(this.width * this.height * pixelBytes); - var pos = 0; - var _this = this; - - if (data == null) { - data = this.imgData; - } - if (data.length === 0) { - return new Uint8Array(0); - } - - data = unzlibSync(data); - function pass(x0, y0, dx, dy) { - var abyte, - c, - col, - i, - left, - length, - p, - pa, - paeth, - pb, - pc, - pixels, - row, - scanlineLength, - upper, - upperLeft, - _i, - _j, - _k, - _l, - _m; - var w = Math.ceil((_this.width - x0) / dx), - h = Math.ceil((_this.height - y0) / dy); - var isFull = _this.width == w && _this.height == h; - scanlineLength = pixelBytes * w; - pixels = isFull ? fullPixels : new Uint8Array(scanlineLength * h); - length = data.length; - row = 0; - c = 0; - while (row < h && pos < length) { - switch (data[pos++]) { - case 0: - for (i = _i = 0; _i < scanlineLength; i = _i += 1) { - pixels[c++] = data[pos++]; - } - break; - case 1: - for (i = _j = 0; _j < scanlineLength; i = _j += 1) { - abyte = data[pos++]; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - pixels[c++] = (abyte + left) % 256; - } - break; - case 2: - for (i = _k = 0; _k < scanlineLength; i = _k += 1) { - abyte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - upper = - row && - pixels[ - (row - 1) * scanlineLength + - col * pixelBytes + - (i % pixelBytes) - ]; - pixels[c++] = (upper + abyte) % 256; - } - break; - case 3: - for (i = _l = 0; _l < scanlineLength; i = _l += 1) { - abyte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - upper = - row && - pixels[ - (row - 1) * scanlineLength + - col * pixelBytes + - (i % pixelBytes) - ]; - pixels[c++] = (abyte + Math.floor((left + upper) / 2)) % 256; - } - break; - case 4: - for (i = _m = 0; _m < scanlineLength; i = _m += 1) { - abyte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - if (row === 0) { - upper = upperLeft = 0; - } else { - upper = - pixels[ - (row - 1) * scanlineLength + - col * pixelBytes + - (i % pixelBytes) - ]; - upperLeft = - col && - pixels[ - (row - 1) * scanlineLength + - (col - 1) * pixelBytes + - (i % pixelBytes) - ]; - } - p = left + upper - upperLeft; - pa = Math.abs(p - left); - pb = Math.abs(p - upper); - pc = Math.abs(p - upperLeft); - if (pa <= pb && pa <= pc) { - paeth = left; - } else if (pb <= pc) { - paeth = upper; - } else { - paeth = upperLeft; - } - pixels[c++] = (abyte + paeth) % 256; - } - break; - default: - throw new Error("Invalid filter algorithm: " + data[pos - 1]); - } - if (!isFull) { - var fullPos = ((y0 + row * dy) * _this.width + x0) * pixelBytes; - var partPos = row * scanlineLength; - for (i = 0; i < w; i += 1) { - for (var j = 0; j < pixelBytes; j += 1) - fullPixels[fullPos++] = pixels[partPos++]; - fullPos += (dx - 1) * pixelBytes; - } - } - row++; - } - } - if (_this.interlaceMethod == 1) { - /* - 1 6 4 6 2 6 4 6 - 7 7 7 7 7 7 7 7 - 5 6 5 6 5 6 5 6 - 7 7 7 7 7 7 7 7 - 3 6 4 6 3 6 4 6 - 7 7 7 7 7 7 7 7 - 5 6 5 6 5 6 5 6 - 7 7 7 7 7 7 7 7 - */ - pass(0, 0, 8, 8); // 1 - /* NOTE these seem to follow the pattern: - * pass(x, 0, 2*x, 2*x); - * pass(0, x, x, 2*x); - * with x being 4, 2, 1. - */ - pass(4, 0, 8, 8); // 2 - pass(0, 4, 4, 8); // 3 - - pass(2, 0, 4, 4); // 4 - pass(0, 2, 2, 4); // 5 - - pass(1, 0, 2, 2); // 6 - pass(0, 1, 1, 2); // 7 - } else { - pass(0, 0, 1, 1); - } - return fullPixels; - }; - - PNG.prototype.decodePalette = function() { - var c, i, length, palette, pos, ret, transparency, _i, _ref, _ref1; - palette = this.palette; - transparency = this.transparency.indexed || []; - ret = new Uint8Array((transparency.length || 0) + palette.length); - pos = 0; - length = palette.length; - c = 0; - for (i = _i = 0, _ref = length; _i < _ref; i = _i += 3) { - ret[pos++] = palette[i]; - ret[pos++] = palette[i + 1]; - ret[pos++] = palette[i + 2]; - ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255; - } - return ret; - }; - - PNG.prototype.copyToImageData = function(imageData, pixels) { - var alpha, colors, data, i, input, j, k, length, palette, v, _ref; - colors = this.colors; - palette = null; - alpha = this.hasAlphaChannel; - if (this.palette.length) { - palette = - (_ref = this._decodedPalette) != null - ? _ref - : (this._decodedPalette = this.decodePalette()); - colors = 4; - alpha = true; - } - data = imageData.data || imageData; - length = data.length; - input = palette || pixels; - i = j = 0; - if (colors === 1) { - while (i < length) { - k = palette ? pixels[i / 4] * 4 : j; - v = input[k++]; - data[i++] = v; - data[i++] = v; - data[i++] = v; - data[i++] = alpha ? input[k++] : 255; - j = k; - } - } else { - while (i < length) { - k = palette ? pixels[i / 4] * 4 : j; - data[i++] = input[k++]; - data[i++] = input[k++]; - data[i++] = input[k++]; - data[i++] = alpha ? input[k++] : 255; - j = k; - } - } - }; - - PNG.prototype.decode = function() { - var ret; - ret = new Uint8Array(this.width * this.height * 4); - this.copyToImageData(ret, this.decodePixels()); - return ret; - }; - - var hasBrowserCanvas = function() { - if (Object.prototype.toString.call(globalObject) === "[object Window]") { - try { - scratchCanvas = globalObject.document.createElement("canvas"); - scratchCtx = scratchCanvas.getContext("2d"); - } catch (e) { - return false; - } - return true; - } - return false; - }; - - hasBrowserCanvas(); - - makeImage = function(imageData) { - if (hasBrowserCanvas() === true) { - var img; - scratchCtx.width = imageData.width; - scratchCtx.height = imageData.height; - scratchCtx.clearRect(0, 0, imageData.width, imageData.height); - scratchCtx.putImageData(imageData, 0, 0); - img = new Image(); - img.src = scratchCanvas.toDataURL(); - return img; - } - throw new Error("This method requires a Browser with Canvas-capability."); - }; - - PNG.prototype.decodeFrames = function(ctx) { - var frame, i, imageData, pixels, _i, _len, _ref, _results; - if (!this.animation) { - return; - } - _ref = this.animation.frames; - _results = []; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - frame = _ref[i]; - imageData = ctx.createImageData(frame.width, frame.height); - pixels = this.decodePixels(new Uint8Array(frame.data)); - this.copyToImageData(imageData, pixels); - frame.imageData = imageData; - _results.push((frame.image = makeImage(imageData))); - } - return _results; - }; - - PNG.prototype.renderFrame = function(ctx, number) { - var frame, frames, prev; - frames = this.animation.frames; - frame = frames[number]; - prev = frames[number - 1]; - if (number === 0) { - ctx.clearRect(0, 0, this.width, this.height); - } - if ( - (prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_BACKGROUND - ) { - ctx.clearRect(prev.xOffset, prev.yOffset, prev.width, prev.height); - } else if ( - (prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_PREVIOUS - ) { - ctx.putImageData(prev.imageData, prev.xOffset, prev.yOffset); - } - if (frame.blendOp === APNG_BLEND_OP_SOURCE) { - ctx.clearRect(frame.xOffset, frame.yOffset, frame.width, frame.height); - } - return ctx.drawImage(frame.image, frame.xOffset, frame.yOffset); - }; - - PNG.prototype.animate = function(ctx) { - var doFrame, - frameNumber, - frames, - numFrames, - numPlays, - _ref, - _this = this; - frameNumber = 0; - (_ref = this.animation), - (numFrames = _ref.numFrames), - (frames = _ref.frames), - (numPlays = _ref.numPlays); - return (doFrame = function() { - var f, frame; - f = frameNumber++ % numFrames; - frame = frames[f]; - _this.renderFrame(ctx, f); - if (numFrames > 1 && frameNumber / numFrames < numPlays) { - return (_this.animation._timeout = setTimeout(doFrame, frame.delay)); - } - })(); - }; - - PNG.prototype.stopAnimation = function() { - var _ref; - return clearTimeout( - (_ref = this.animation) != null ? _ref._timeout : void 0 - ); - }; - - PNG.prototype.render = function(canvas) { - var ctx, data; - if (canvas._png) { - canvas._png.stopAnimation(); - } - canvas._png = this; - canvas.width = this.width; - canvas.height = this.height; - ctx = canvas.getContext("2d"); - if (this.animation) { - this.decodeFrames(ctx); - return this.animate(ctx); - } else { - data = ctx.createImageData(this.width, this.height); - this.copyToImageData(data, this.decodePixels()); - return ctx.putImageData(data, 0, 0); - } - }; - - return PNG; -})(); - -export { PNG };
src/modules/addimage.js+18 −38 modified@@ -33,7 +33,7 @@ */ import { jsPDF } from "../jspdf.js"; -import { atob, btoa } from "../libs/AtobBtoa.js"; +import { atob } from "../libs/AtobBtoa.js"; (function(jsPDFAPI) { "use strict"; @@ -286,10 +286,8 @@ import { atob, btoa } from "../libs/AtobBtoa.js"; // Soft mask if ("sMask" in image && typeof image.sMask !== "undefined") { var decodeParameters = - "/Predictor " + - image.predictor + - " /Colors 1 /BitsPerComponent " + - image.bitsPerComponent + + (image.predictor != null ? "/Predictor " + image.predictor : "") + + " /Colors 1 /BitsPerComponent 8" + " /Columns " + image.width; var sMask = { @@ -671,19 +669,6 @@ import { atob, btoa } from "../libs/AtobBtoa.js"; return dataUrl.substring(commaIndex + 1); }); - /** - * Check to see if ArrayBuffer is supported - * - * @name supportsArrayBuffer - * @function - * @returns {boolean} - */ - var supportsArrayBuffer = (jsPDFAPI.__addimage__.supportsArrayBuffer = function() { - return ( - typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined" - ); - }); - /** * Tests supplied object to determine if ArrayBuffer * @@ -694,7 +679,7 @@ import { atob, btoa } from "../libs/AtobBtoa.js"; * @returns {boolean} */ jsPDFAPI.__addimage__.isArrayBuffer = function(object) { - return supportsArrayBuffer() && object instanceof ArrayBuffer; + return object instanceof ArrayBuffer; }; /** @@ -709,18 +694,15 @@ import { atob, btoa } from "../libs/AtobBtoa.js"; object ) { return ( - supportsArrayBuffer() && - typeof Uint32Array !== "undefined" && - (object instanceof Int8Array || - object instanceof Uint8Array || - (typeof Uint8ClampedArray !== "undefined" && - object instanceof Uint8ClampedArray) || - object instanceof Int16Array || - object instanceof Uint16Array || - object instanceof Int32Array || - object instanceof Uint32Array || - object instanceof Float32Array || - object instanceof Float64Array) + object instanceof Int8Array || + object instanceof Uint8Array || + object instanceof Uint8ClampedArray || + object instanceof Int16Array || + object instanceof Uint16Array || + object instanceof Int32Array || + object instanceof Uint32Array || + object instanceof Float32Array || + object instanceof Float64Array ); }); @@ -910,12 +892,10 @@ import { atob, btoa } from "../libs/AtobBtoa.js"; result = checkImagesForAlias.call(this, alias); if (!result) { - if (supportsArrayBuffer()) { - // no need to convert if imageData is already uint8array - if (!(imageData instanceof Uint8Array) && format !== "RGBA") { - dataAsBinaryString = imageData; - imageData = binaryStringToUint8Array(imageData); - } + // no need to convert if imageData is already uint8array + if (!(imageData instanceof Uint8Array) && format !== "RGBA") { + dataAsBinaryString = imageData; + imageData = binaryStringToUint8Array(imageData); } result = this["process" + format.toUpperCase()]( @@ -1007,7 +987,7 @@ import { atob, btoa } from "../libs/AtobBtoa.js"; ); } - if (supportsArrayBuffer() && !(imageData instanceof Uint8Array)) { + if (!(imageData instanceof Uint8Array)) { imageData = binaryStringToUint8Array(imageData); }
src/modules/png_support.js+427 −419 modified@@ -26,37 +26,125 @@ import { jsPDF } from "../jspdf.js"; import { zlibSync } from "../libs/fflate.js"; -import { PNG } from "../libs/png.js"; +import { decode as decodePng } from "../libs/fast-png.js"; -/** - * jsPDF PNG PlugIn - * @name png_support - * @module - */ -(function(jsPDFAPI) { - "use strict"; +/* + * @see http://www.w3.org/TR/PNG-Chunks.html + * + Color Allowed Interpretation + Type Bit Depths - /* - * @see http://www.w3.org/TR/PNG-Chunks.html - * - Color Allowed Interpretation - Type Bit Depths + 0 1,2,4,8,16 Each pixel is a grayscale sample. - 0 1,2,4,8,16 Each pixel is a grayscale sample. + 2 8,16 Each pixel is an R,G,B triple. - 2 8,16 Each pixel is an R,G,B triple. + 3 1,2,4,8 Each pixel is a palette index; + a PLTE chunk must appear. - 3 1,2,4,8 Each pixel is a palette index; - a PLTE chunk must appear. + 4 8,16 Each pixel is a grayscale sample, + followed by an alpha sample. - 4 8,16 Each pixel is a grayscale sample, - followed by an alpha sample. + 6 8,16 Each pixel is an R,G,B triple, + followed by an alpha sample. +*/ - 6 8,16 Each pixel is an R,G,B triple, - followed by an alpha sample. - */ +/* + * @name processPNG + * Entry point: process a PNG and return image dict and metadata for jsPDF + */ +jsPDF.API.processPNG = function(imageData, index, alias, compression) { + if (this.__addimage__.isArrayBuffer(imageData)) { + imageData = new Uint8Array(imageData); + } + if (!this.__addimage__.isArrayBufferView(imageData)) { + return; + } + + const decodedPng = decodePng(imageData, { checkCrc: true }); + const { + width, + height, + channels, + palette: decodedPalette, + depth: bitsPerComponent + } = decodedPng; + + let result; + if (decodedPalette && channels === 1) { + result = processIndexedPNG(decodedPng); + } else if (channels === 2 || channels === 4) { + result = processAlphaPNG(decodedPng); + } else { + result = processOpaquePNG(decodedPng); + } + + const { + colorSpace, + colorsPerPixel, + colorBytes, + alphaBytes, + needSMask, + palette, + mask + } = result; + + let predictor = null; + + let filter, decodeParameters, sMask; + if (canCompress(compression)) { + predictor = getPredictorFromCompression(compression); + filter = this.decode.FLATE_DECODE; + decodeParameters = `/Predictor ${predictor} `; + imageData = compressBytes( + colorBytes, + width * colorsPerPixel, + colorsPerPixel, + compression + ); + if (needSMask) { + sMask = compressBytes(alphaBytes, width, 1, compression); + } + } else { + filter = undefined; + decodeParameters = ""; + imageData = colorBytes; + if (needSMask) sMask = alphaBytes; + } + + decodeParameters += `/Colors ${colorsPerPixel} /BitsPerComponent ${bitsPerComponent} /Columns ${width}`; + + if ( + this.__addimage__.isArrayBuffer(imageData) || + this.__addimage__.isArrayBufferView(imageData) + ) { + imageData = this.__addimage__.arrayBufferToBinaryString(imageData); + } - /* + if ( + (sMask && this.__addimage__.isArrayBuffer(sMask)) || + this.__addimage__.isArrayBufferView(sMask) + ) { + sMask = this.__addimage__.arrayBufferToBinaryString(sMask); + } + + return { + alias, + data: imageData, + index, + filter, + decodeParameters, + transparency: mask, + palette, + sMask, + predictor, + width, + height, + bitsPerComponent, + colorSpace + }; +}; + +/* * PNG filter method types * * @see http://www.w3.org/TR/PNG-Filters.html @@ -74,419 +162,339 @@ import { PNG } from "../libs/png.js"; 4 Paeth */ - var canCompress = function(value) { - return value !== jsPDFAPI.image_compression.NONE && hasCompressionJS(); - }; - - var hasCompressionJS = function() { - return typeof zlibSync === "function"; - }; - var compressBytes = function(bytes, lineLength, colorsPerPixel, compression) { - var level = 4; - var filter_method = filterUp; - - switch (compression) { - case jsPDFAPI.image_compression.FAST: - level = 1; - filter_method = filterSub; - break; - - case jsPDFAPI.image_compression.MEDIUM: - level = 6; - filter_method = filterAverage; - break; - - case jsPDFAPI.image_compression.SLOW: - level = 9; - filter_method = filterPaeth; - break; - } - - bytes = applyPngFilterMethod( - bytes, - lineLength, - colorsPerPixel, - filter_method - ); - var dat = zlibSync(bytes, { level: level }); - return jsPDFAPI.__addimage__.arrayBufferToBinaryString(dat); - }; - - var applyPngFilterMethod = function( +function canCompress(value) { + return value !== jsPDF.API.image_compression.NONE && hasCompressionJS(); +} + +function hasCompressionJS() { + return typeof zlibSync === "function"; +} +function compressBytes(bytes, lineLength, colorsPerPixel, compression) { + let level = 4; + let filter_method = filterUp; + + switch (compression) { + case jsPDF.API.image_compression.FAST: + level = 1; + filter_method = filterSub; + break; + + case jsPDF.API.image_compression.MEDIUM: + level = 6; + filter_method = filterAverage; + break; + + case jsPDF.API.image_compression.SLOW: + level = 9; + filter_method = filterPaeth; + break; + } + + bytes = applyPngFilterMethod( bytes, lineLength, colorsPerPixel, filter_method - ) { - var lines = bytes.length / lineLength, - result = new Uint8Array(bytes.length + lines), - filter_methods = getFilterMethods(), - line, - prevLine, - offset; - - for (var i = 0; i < lines; i += 1) { - offset = i * lineLength; - line = bytes.subarray(offset, offset + lineLength); - - if (filter_method) { - result.set(filter_method(line, colorsPerPixel, prevLine), offset + i); - } else { - var len = filter_methods.length, - results = []; - - for (var j; j < len; j += 1) { - results[j] = filter_methods[j](line, colorsPerPixel, prevLine); - } - - var ind = getIndexOfSmallestSum(results.concat()); - - result.set(results[ind], offset + i); + ); + const dat = zlibSync(bytes, { level: level }); + return jsPDF.API.__addimage__.arrayBufferToBinaryString(dat); +} + +function applyPngFilterMethod( + bytes, + lineLength, + colorsPerPixel, + filter_method +) { + const lines = bytes.length / lineLength; + const result = new Uint8Array(bytes.length + lines); + const filter_methods = getFilterMethods(); + let prevLine; + + for (let i = 0; i < lines; i += 1) { + const offset = i * lineLength; + const line = bytes.subarray(offset, offset + lineLength); + + if (filter_method) { + result.set(filter_method(line, colorsPerPixel, prevLine), offset + i); + } else { + const len = filter_methods.length; + const results = []; + + for (let j = 0; j < len; j += 1) { + results[j] = filter_methods[j](line, colorsPerPixel, prevLine); } - prevLine = line; - } - - return result; - }; - - var filterNone = function(line) { - /*var result = new Uint8Array(line.length + 1); - result[0] = 0; - result.set(line, 1);*/ - - var result = Array.apply([], line); - result.unshift(0); - - return result; - }; - - var filterSub = function(line, colorsPerPixel) { - var result = [], - len = line.length, - left; - - result[0] = 1; - - for (var i = 0; i < len; i += 1) { - left = line[i - colorsPerPixel] || 0; - result[i + 1] = (line[i] - left + 0x0100) & 0xff; - } - - return result; - }; - - var filterUp = function(line, colorsPerPixel, prevLine) { - var result = [], - len = line.length, - up; - - result[0] = 2; + const ind = getIndexOfSmallestSum(results.concat()); - for (var i = 0; i < len; i += 1) { - up = (prevLine && prevLine[i]) || 0; - result[i + 1] = (line[i] - up + 0x0100) & 0xff; + result.set(results[ind], offset + i); } - return result; - }; + prevLine = line; + } - var filterAverage = function(line, colorsPerPixel, prevLine) { - var result = [], - len = line.length, - left, - up; + return result; +} - result[0] = 3; +function filterNone(line) { + /*const result = new Uint8Array(line.length + 1); + result[0] = 0; + result.set(line, 1);*/ - for (var i = 0; i < len; i += 1) { - left = line[i - colorsPerPixel] || 0; - up = (prevLine && prevLine[i]) || 0; - result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff; + const result = Array.apply([], line); + result.unshift(0); + + return result; +} + +function filterSub(line, colorsPerPixel) { + const len = line.length; + const result = []; + + result[0] = 1; + + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + result[i + 1] = (line[i] - left + 0x0100) & 0xff; + } + + return result; +} + +function filterUp(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; + + result[0] = 2; + + for (let i = 0; i < len; i += 1) { + const up = (prevLine && prevLine[i]) || 0; + result[i + 1] = (line[i] - up + 0x0100) & 0xff; + } + + return result; +} + +function filterAverage(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; + + result[0] = 3; + + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + const up = (prevLine && prevLine[i]) || 0; + result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff; + } + + return result; +} + +function filterPaeth(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; + + result[0] = 4; + + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + const up = (prevLine && prevLine[i]) || 0; + const upLeft = (prevLine && prevLine[i - colorsPerPixel]) || 0; + const paeth = paethPredictor(left, up, upLeft); + result[i + 1] = (line[i] - paeth + 0x0100) & 0xff; + } + + return result; +} + +function paethPredictor(left, up, upLeft) { + if (left === up && up === upLeft) { + return left; + } + const pLeft = Math.abs(up - upLeft), + pUp = Math.abs(left - upLeft), + pUpLeft = Math.abs(left + up - upLeft - upLeft); + return pLeft <= pUp && pLeft <= pUpLeft ? left : pUp <= pUpLeft ? up : upLeft; +} + +function getFilterMethods() { + return [filterNone, filterSub, filterUp, filterAverage, filterPaeth]; +} + +function getIndexOfSmallestSum(arrays) { + const sum = arrays.map(function(value) { + return value.reduce(function(pv, cv) { + return pv + Math.abs(cv); + }, 0); + }); + return sum.indexOf(Math.min.apply(null, sum)); +} + +function getPredictorFromCompression(compression) { + let predictor; + switch (compression) { + case jsPDF.API.image_compression.FAST: + predictor = 11; + break; + + case jsPDF.API.image_compression.MEDIUM: + predictor = 13; + break; + + case jsPDF.API.image_compression.SLOW: + predictor = 14; + break; + + default: + predictor = 12; + break; + } + return predictor; +} + +// Extracted helper for Indexed PNGs (palette-based) +function processIndexedPNG(decodedPng) { + const { width, height, data, palette: decodedPalette, depth } = decodedPng; + let needSMask = false; + let palette = []; + let mask = []; + let alphaBytes = undefined; + let hasSemiTransparency = false; + + const maxMaskLength = 1; + let maskLength = 0; + + for (let i = 0; i < decodedPalette.length; i++) { + const [r, g, b, a] = decodedPalette[i]; + palette.push(r, g, b); + if (a != null) { + if (a === 0) { + maskLength++; + if (mask.length < maxMaskLength) { + mask.push(i); + } + } else if (a < 255) { + hasSemiTransparency = true; + } } - - return result; - }; - - var filterPaeth = function(line, colorsPerPixel, prevLine) { - var result = [], - len = line.length, - left, - up, - upLeft, - paeth; - - result[0] = 4; - - for (var i = 0; i < len; i += 1) { - left = line[i - colorsPerPixel] || 0; - up = (prevLine && prevLine[i]) || 0; - upLeft = (prevLine && prevLine[i - colorsPerPixel]) || 0; - paeth = paethPredictor(left, up, upLeft); - result[i + 1] = (line[i] - paeth + 0x0100) & 0xff; + } + + if (hasSemiTransparency || maskLength > maxMaskLength) { + needSMask = true; + mask = undefined; + + const totalPixels = width * height; + alphaBytes = new Uint8Array(totalPixels); + const dataView = new DataView(data.buffer); + for (let p = 0; p < totalPixels; p++) { + const paletteIndex = readSample(dataView, p, depth); + const [, , , alpha] = decodedPalette[paletteIndex]; + alphaBytes[p] = alpha; } - - return result; + } + + return { + colorSpace: "Indexed", + colorsPerPixel: 1, + colorBytes: data, + alphaBytes, + needSMask, + palette, + mask }; +} - var paethPredictor = function(left, up, upLeft) { - if (left === up && up === upLeft) { - return left; +/* + * Splits color and alpha values into separate buffers + */ +function processAlphaPNG(decodedPng) { + const { data, width, height, channels, depth } = decodedPng; + + const colorSpace = channels === 2 ? "DeviceGray" : "DeviceRGB"; + const colorsPerPixel = channels - 1; + + const totalPixels = width * height; + const colorChannels = colorsPerPixel; // 1 for Gray, 3 for RGB + const alphaChannels = 1; + const totalColorSamples = totalPixels * colorChannels; + const totalAlphaSamples = totalPixels * alphaChannels; + + const colorByteLen = Math.ceil((totalColorSamples * depth) / 8); + const alphaByteLen = Math.ceil((totalAlphaSamples * depth) / 8); + const colorBytes = new Uint8Array(colorByteLen); + const alphaBytes = new Uint8Array(alphaByteLen); + + const dataView = new DataView(data.buffer); + const colorView = new DataView(colorBytes.buffer); + const alphaView = new DataView(alphaBytes.buffer); + + let needSMask = false; + for (let p = 0; p < totalPixels; p++) { + const pixelStartIndex = p * channels; + for (let s = 0; s < colorChannels; s++) { + const sampleIndex = pixelStartIndex + s; + const colorValue = readSample(dataView, sampleIndex, depth); + writeSample(colorView, colorValue, p * colorChannels + s, depth); } - var pLeft = Math.abs(up - upLeft), - pUp = Math.abs(left - upLeft), - pUpLeft = Math.abs(left + up - upLeft - upLeft); - return pLeft <= pUp && pLeft <= pUpLeft - ? left - : pUp <= pUpLeft - ? up - : upLeft; - }; - - var getFilterMethods = function() { - return [filterNone, filterSub, filterUp, filterAverage, filterPaeth]; - }; - - var getIndexOfSmallestSum = function(arrays) { - var sum = arrays.map(function(value) { - return value.reduce(function(pv, cv) { - return pv + Math.abs(cv); - }, 0); - }); - return sum.indexOf(Math.min.apply(null, sum)); - }; - - var getPredictorFromCompression = function(compression) { - var predictor; - switch (compression) { - case jsPDFAPI.image_compression.FAST: - predictor = 11; - break; - - case jsPDFAPI.image_compression.MEDIUM: - predictor = 13; - break; - - case jsPDFAPI.image_compression.SLOW: - predictor = 14; - break; - - default: - predictor = 12; - break; + const sampleIndex = pixelStartIndex + colorChannels; + const alphaValue = readSample(dataView, sampleIndex, depth); + if (alphaValue < (1 << depth) - 1) { + needSMask = true; } - return predictor; - }; - - /** - * @name processPNG - * @function - * @ignore - */ - jsPDFAPI.processPNG = function(imageData, index, alias, compression) { - "use strict"; - - var colorSpace, - filter = this.decode.FLATE_DECODE, - bitsPerComponent, - image, - decodeParameters = "", - trns, - colors, - pal, - smask, - pixels, - len, - alphaData, - imgData, - hasColors, - pixel, - i, - n; - - if (this.__addimage__.isArrayBuffer(imageData)) - imageData = new Uint8Array(imageData); - - if (this.__addimage__.isArrayBufferView(imageData)) { - image = new PNG(imageData); - imageData = image.imgData; - bitsPerComponent = image.bits; - colorSpace = image.colorSpace; - colors = image.colors; - - /* - * colorType 6 - Each pixel is an R,G,B triple, followed by an alpha sample. - * - * colorType 4 - Each pixel is a grayscale sample, followed by an alpha sample. - * - * Extract alpha to create two separate images, using the alpha as a sMask - */ - if ([4, 6].indexOf(image.colorType) !== -1) { - /* - * processes 8 bit RGBA and grayscale + alpha images - */ - if (image.bits === 8) { - pixels = - image.pixelBitlength == 32 - ? new Uint32Array(image.decodePixels().buffer) - : image.pixelBitlength == 16 - ? new Uint16Array(image.decodePixels().buffer) - : new Uint8Array(image.decodePixels().buffer); - len = pixels.length; - imgData = new Uint8Array(len * image.colors); - alphaData = new Uint8Array(len); - var pDiff = image.pixelBitlength - image.bits; - i = 0; - n = 0; - var pbl; - - for (; i < len; i++) { - pixel = pixels[i]; - pbl = 0; - - while (pbl < pDiff) { - imgData[n++] = (pixel >>> pbl) & 0xff; - pbl = pbl + image.bits; - } - - alphaData[i] = (pixel >>> pbl) & 0xff; - } - } - - /* - * processes 16 bit RGBA and grayscale + alpha images - */ - if (image.bits === 16) { - pixels = new Uint32Array(image.decodePixels().buffer); - len = pixels.length; - imgData = new Uint8Array( - len * (32 / image.pixelBitlength) * image.colors - ); - alphaData = new Uint8Array(len * (32 / image.pixelBitlength)); - hasColors = image.colors > 1; - i = 0; - n = 0; - var a = 0; - - while (i < len) { - pixel = pixels[i++]; - - imgData[n++] = (pixel >>> 0) & 0xff; - - if (hasColors) { - imgData[n++] = (pixel >>> 16) & 0xff; - - pixel = pixels[i++]; - imgData[n++] = (pixel >>> 0) & 0xff; - } - - alphaData[a++] = (pixel >>> 16) & 0xff; - } - bitsPerComponent = 8; - } - - if (canCompress(compression)) { - imageData = compressBytes( - imgData, - image.width * image.colors, - image.colors, - compression - ); - smask = compressBytes(alphaData, image.width, 1, compression); - } else { - imageData = imgData; - smask = alphaData; - filter = undefined; - } - } - - /* - * Indexed png. Each pixel is a palette index. - */ - if (image.colorType === 3) { - colorSpace = this.color_spaces.INDEXED; - pal = image.palette; - - if (image.transparency.indexed) { - var trans = image.transparency.indexed; - var total = 0; - i = 0; - len = trans.length; - - for (; i < len; ++i) { - total += trans[i]; - } - - total = total / 255; - - /* - * a single color is specified as 100% transparent (0), - * so we set trns to use a /Mask with that index - */ - if (total === len - 1 && trans.indexOf(0) !== -1) { - trns = [trans.indexOf(0)]; - - /* - * there's more than one colour within the palette that specifies - * a transparency value less than 255, so we unroll the pixels to create an image sMask - */ - } else if (total !== len) { - pixels = image.decodePixels(); - alphaData = new Uint8Array(pixels.length); - i = 0; - len = pixels.length; - - for (; i < len; i++) { - alphaData[i] = trans[pixels[i]]; - } - - smask = compressBytes(alphaData, image.width, 1); - } - } - } - - var predictor = getPredictorFromCompression(compression); - - if (filter === this.decode.FLATE_DECODE) { - decodeParameters = "/Predictor " + predictor + " "; - } - decodeParameters += - "/Colors " + - colors + - " /BitsPerComponent " + - bitsPerComponent + - " /Columns " + - image.width; - - if ( - this.__addimage__.isArrayBuffer(imageData) || - this.__addimage__.isArrayBufferView(imageData) - ) { - imageData = this.__addimage__.arrayBufferToBinaryString(imageData); - } - - if ( - (smask && this.__addimage__.isArrayBuffer(smask)) || - this.__addimage__.isArrayBufferView(smask) - ) { - smask = this.__addimage__.arrayBufferToBinaryString(smask); - } + writeSample(alphaView, alphaValue, p * alphaChannels, depth); + } - return { - alias: alias, - data: imageData, - index: index, - filter: filter, - decodeParameters: decodeParameters, - transparency: trns, - palette: pal, - sMask: smask, - predictor: predictor, - width: image.width, - height: image.height, - bitsPerComponent: bitsPerComponent, - colorSpace: colorSpace - }; - } + return { + colorSpace, + colorsPerPixel, + colorBytes, + alphaBytes, + needSMask }; -})(jsPDF.API); +} + +function processOpaquePNG(decodedPng) { + const { data, channels } = decodedPng; + const colorSpace = channels === 1 ? "DeviceGray" : "DeviceRGB"; + const colorsPerPixel = colorSpace === "DeviceGray" ? 1 : 3; + const colorBytes = + data instanceof Uint8Array ? data : new Uint8Array(data.buffer); + return { colorSpace, colorsPerPixel, colorBytes, needSMask: false }; +} + +function readSample(view, sampleIndex, depth) { + const bitIndex = sampleIndex * depth; + const byteIndex = Math.floor(bitIndex / 8); + const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); + const bitMask = (1 << depth) - 1; + const word = safeGetUint16(view, byteIndex); + return (word >> bitOffset) & bitMask; +} + +function writeSample(view, value, sampleIndex, depth) { + const bitIndex = sampleIndex * depth; + const byteIndex = Math.floor(bitIndex / 8); + const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); + const bitMask = (1 << depth) - 1; + const writeValue = (value & bitMask) << bitOffset; + const word = + safeGetUint16(view, byteIndex) & ~(bitMask << bitOffset) & 0xffff; + safeSetUint16(view, byteIndex, word | writeValue); +} + +function safeGetUint16(view, byteIndex) { + if (byteIndex + 1 < view.byteLength) { + return view.getUint16(byteIndex, false); + } + const b0 = view.getUint8(byteIndex); + return b0 << 8; +} + +function safeSetUint16(view, byteIndex, value) { + if (byteIndex + 1 < view.byteLength) { + view.setUint16(byteIndex, value, false); + return; + } + const byteToWrite = (value >> 8) & 0xff; + view.setUint8(byteIndex, byteToWrite); +}
test/deployment/node/loadGlobals.js+0 −2 modified@@ -1,8 +1,6 @@ /* eslint-disable no-unused-vars */ global.loadGlobals = function() { - global.btoa = require("btoa"); // used in some specs - global.Canvg = require("canvg").Canvg; const jsPDF = require("../../../dist/jspdf.node.js"); global.jsPDF = jsPDF.jsPDF;
test/reference/colortype_1_grayscale_16_bit_png.pdf+0 −0 addedtest/reference/colortype_1_grayscale_8_bit_png.pdf+0 −0 removedtest/reference/colortype_2_rgb_16_bit_png.pdf+0 −0 modifiedtest/reference/colortype_2_rgb_8_bit_png.pdf+0 −0 modifiedtest/reference/colortype_3_indexed_multi_colour_alpha_4_bit_png.pdf+0 −0 modifiedtest/reference/colortype_3_indexed_multi_colour_alpha_8_bit_png.pdf+0 −0 modifiedtest/reference/colortype_3_indexed_single_colour_alpha_4_bit_png.pdf+0 −0 modifiedtest/reference/colortype_3_indexed_single_colour_alpha_8_bit_png.pdf+0 −0 modifiedtest/reference/colortype_4_grayscale_alpha_16_bit_png.pdf+0 −0 modifiedtest/reference/colortype_4_grayscale_alpha_8_bit_png.pdf+0 −0 modifiedtest/reference/colortype_6_rgba_16_bit_png.pdf+0 −0 modifiedtest/reference/colortype_6_rgba_8_bit_png_NONE.pdf+0 −0 modifiedtest/reference/colortype_6_rgba_8_bit_png.pdf+0 −0 modifiedtest/reference/encrypted_withImage.pdf+0 −0 modifiedtest/reference/html-margin-page-break-image.pdf+0 −0 modifiedtest/specs/png.spec.js+9 −5 modified@@ -297,7 +297,7 @@ describe("Module: PNGSupport", () => { ); }); - xit("colortype_3_indexed_multi_colour_alpha_4_bit_png", () => { + it("colortype_3_indexed_multi_colour_alpha_4_bit_png", () => { var colortype_3_indexed_multi_colour_alpha_4_bit_png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAAD///8AAAD///8AAAD///8AAAD///8AAAD///8AAAD///8AAAD///8AAAD///809TMSAAAAEHRSTlP//8DAoKCAgGBgQEAgIAAATXQTkgAAAJtJREFUCB0BkABv/wAAAAAAAAAAAAAAERERERERAAAjIzMzMzMyMgAjMjMzMzMjMgBFVUVVVVRVVABFVVRVVUVVVABnd3dndnd3dgBnd3d2Z3d3dgCJmZmYiZmZmACJmZmJmJmZmACru7q7u6u7ugCru6u7u7q7ugDN3N3d3d3N3ADNzd3d3d3c3ADu////////7gDu7u7u7u7u7gXuQRWvTQmUAAAAAElFTkSuQmCC"; const doc = new jsPDF({ @@ -371,8 +371,8 @@ describe("Module: PNGSupport", () => { comparePdf(doc.output(), "colortype_2_rgb_16_bit_png.pdf", "addimage"); }); - it("colortype_1_grayscale_8_bit_png", () => { - var colortype_1_grayscale_8_bit_png = + it("colortype_1_grayscale_16_bit_png", () => { + var colortype_1_grayscale_16_bit_png = "data:image/png;base64,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"; const doc = new jsPDF({ orientation: "p", @@ -381,7 +381,7 @@ describe("Module: PNGSupport", () => { floatPrecision: 2 }); doc.addImage( - colortype_1_grayscale_8_bit_png, + colortype_1_grayscale_16_bit_png, "PNG", 100, 200, @@ -391,6 +391,10 @@ describe("Module: PNGSupport", () => { undefined ); - comparePdf(doc.output(), "colortype_1_grayscale_8_bit_png.pdf", "addimage"); + comparePdf( + doc.output(), + "colortype_1_grayscale_16_bit_png.pdf", + "addimage" + ); }); });
test/specs/Uint8Array.spec.js+0 −3 modified@@ -5,9 +5,6 @@ describe("Module: addimage Uint8Array", () => { beforeAll(loadGlobals); - if (typeof global !== "undefined" && global.isNode == true) { - eval("var atob = require('atob')"); - } function convertDataURIToArrayBuffer(dataURI) { var BASE64_MARKER = ";base64,"; var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
test/unit/karma.conf.js+3 −2 modified@@ -37,7 +37,8 @@ module.exports = config => { } ], preprocessors: { - "src/libs/fflate.js": ["rollup"] + "src/libs/fflate.js": ["rollup"], + "src/libs/fast-png.js": ["rollup"] }, rollupPreprocessor: { @@ -48,7 +49,7 @@ module.exports = config => { } }, - browsers: ["Chrome", "Firefox"], + browsers: ["ChromeHeadless"], // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-8mvj-3j78-4qmwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-57810ghsaADVISORY
- github.com/parallax/jsPDF/commit/4cf3ab619e565d9b88b4b130bff901b91d8688e9ghsax_refsource_MISCWEB
- github.com/parallax/jsPDF/pull/3880ghsax_refsource_MISCWEB
- github.com/parallax/jsPDF/releases/tag/v3.0.2ghsax_refsource_MISCWEB
- github.com/parallax/jsPDF/security/advisories/GHSA-8mvj-3j78-4qmwghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.