parallax/jsPDF

RangeError: offset is out of bounds: Some PNG filters are not processable

Open

#3,004 opened on Nov 16, 2020

View on GitHub
 (9 comments) (9 reactions) (0 assignees)JavaScript (4,596 forks)batch import
Bugdifficulty:mediumhacktoberfest

Repository metrics

Stars
 (28,280 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

Adding PNGs via addImage with certain filters throws an error:

offset is out of bounds RangeError: offset is out of bounds
    at Uint8Array.set (<anonymous>)
    at applyPngFilterMethod (http://raw.githack.com/MrRio/jsPDF/master/dist/jspdf.umd.js:21341:18)
    at compressBytes (http://raw.githack.com/MrRio/jsPDF/master/dist/jspdf.umd.js:21280:15)
    at Object.jsPDFAPI.processPNG (http://raw.githack.com/MrRio/jsPDF/master/dist/jspdf.umd.js:21650:23)
    at Object.processImageData (http://raw.githack.com/MrRio/jsPDF/master/dist/jspdf.umd.js:10309:56)
    at Object.jsPDFAPI.addImage (http://raw.githack.com/MrRio/jsPDF/master/dist/jspdf.umd.js:10245:36)
    at eval (eval at <anonymous> (http://raw.githack.com/MrRio/jsPDF/master/examples/js/editor.js:177:11), <anonymous>:2:5)
    at http://raw.githack.com/MrRio/jsPDF/master/examples/js/editor.js:177:11 RangeError: offset is out of bounds
    at Uint8Array.set (<anonymous>)
    at applyPngFilterMethod (png_support.js:167)
    at compressBytes (png_support.js:106)
    at Object.jsPDFAPI.processPNG (png_support.js:477)
    at Object.processImageData (addimage.js:883)
    at Object.jsPDFAPI.addImage (addimage.js:819)
    at eval (eval at <anonymous> (editor.js:177), <anonymous>:2:5)
    at editor.js:177

Please bear with me, I am certainly no expert on images, but I'd like to relay my findings on this error.
Some PNG images cannot be processed if they are directly added with addImage via URL (previously I have included them by creating a data URL from a canvas). The issue is this code that seems to apply PNG filters: https://github.com/MrRio/jsPDF/blob/82d3b7d72861e06c4c167ec562715c6734c3f709/src/modules/png_support.js#L167

Previous method

public static imageToDataUrl(
    image: HTMLImageElement,
    width: number = image.naturalWidth,
    height: number = image.naturalHeight,
    mode: 'contain' | 'scale' = 'contain'
): string {
    const canvas = document.createElement('canvas');

    canvas.height = height;
    canvas.width = width;

    const context = canvas.getContext('2d');

    context.clearRect(0, 0, width, height);

    const imgAr = +Big(image.naturalWidth).div(image.naturalHeight);
    const destinationAr = +Big(width).div(height);

    if (mode === 'scale' || imgAr === destinationAr) {
        context.drawImage(image, 0, 0, width, height);
    } else if (mode === 'contain') {
        if (imgAr < destinationAr) {
            const scaledWidth = +Big(height).times(imgAr);
            context.drawImage(image, +Big(width - scaledWidth).div(2), 0, scaledWidth, height);
        } else {
            const scaledHeight = +Big(width).div(imgAr);
            context.drawImage(image, 0, +Big(height - scaledHeight).div(2), width, scaledHeight);
        }
    }

    return canvas.toDataURL('image/png');
}

[...]

const image = await PDFCreator.loadImage(imageURL);
pdf.addImage(PDFCreator.imageToDataUrl(image), [...]);

New method

pdf.addImage(imageURL, [...]);

This was changed because now this generation happens in a offloaded worker, which no longer can access canvases BTW.

This can be worked around by changing the image source to a JPEG, that still works.
This does not occur on all PNG images, but only on certain ones, what exactly determines it will fail I don't know, but it seems it has nothing to do with the image mode (indexed/RGB/CMYK all seem to behave the same), I have tested a series of images for products where you can see the material the products are made of, all with the same material pattern fail, some other material patterns all work, which means of images by the same source - that probably have different filters because of the different materials - some fail and some don't

I have uploaded one failing image to imgur, you can replicate the error by opening the live demo and adding this code:

var doc = new jsPDF();
doc.addImage("https://i.imgur.com/LcEVqn4.png", 15, 40, 180, 180);

Contributor guide