diff --git a/MODIFICATIONS.md b/MODIFICATIONS.md index 61b397aa8..638c37f76 100644 --- a/MODIFICATIONS.md +++ b/MODIFICATIONS.md @@ -1,11 +1,18 @@ # Modifications -## [Unreleased] +## [1.17.1-mod.2026.1] + +### Build System Changes - Native ESM everywhere: all src/tests/apps use `.js`-suffixed local imports; Jest replaced with vitest; CJS build removed. - Build outputs relocated to `dist/`: `dist/es` (native ESM + typings) and `dist/umd` (bundled). - Package now uses conditional exports; consumers must import via the published entry points (`import`/`exports` map) rather than deep-linking files. +### PNG Handling Changes + +- Switched PNG decoding from `@pdf-lib/upng` to `fast-png`, preventing freezes on truncated PNG inputs. +- Animated PNGs are now accepted and decoded as a single image. + ## [1.17.1-mod.2025.8] ### Feature Removals diff --git a/package.json b/package.json index 40a5d15f1..bc1afc8eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@denkiyagi/pdf-lib", - "version": "1.17.1-mod.2025.8", + "version": "1.17.1-mod.2026.1", "description": "A modified version of pdf-lib. Create and modify PDF files with JavaScript", "author": "DenkiYagi Inc. (modified version author), Andrew Dillon (orignal version author)", "contributors": [ @@ -89,8 +89,8 @@ "dependencies": { "@denkiyagi/fontkit": "2.0.4-mod.2025.5", "@pdf-lib/standard-fonts": "^1.0.0", - "@pdf-lib/upng": "^1.0.1", "crypto-js": "^4.2.0", + "fast-png": "^8.0.0", "pako": "^2.1.0", "tslib": "^2.8.1" }, @@ -135,4 +135,4 @@ "javascript", "library" ] -} \ No newline at end of file +} diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 09a84136f..1aaac080b 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -49,15 +49,6 @@ export class InvalidPngError extends PDFLibUtilsError { } } -export class AnimatedPngNotSupportedError extends PDFLibUtilsError { - constructor() { - super( - PDFLibErrorTypes.UNSUPPORTED_EXTERNAL_BINARY_DATA, - 'Animated PNGs are not supported', - ); - } -} - export class InvalidOptionPassedError extends PDFLibUtilsError { constructor(valueName: string, allowedValues: Primitive[], actual: any) { const allowed = allowedValues.map(formatValue).join(' or '); diff --git a/src/utils/fast-png-helper.ts b/src/utils/fast-png-helper.ts new file mode 100644 index 000000000..3e90a809e --- /dev/null +++ b/src/utils/fast-png-helper.ts @@ -0,0 +1,190 @@ +import { convertIndexedToRgb, type DecodedPng } from 'fast-png'; +import { InvalidPngError } from 'src/utils/errors.js'; + +const toByte = (value: number, depth: number) => { + if (depth === 8) return value; + if (depth === 16) return Math.round(value / 257); + const maxValue = (1 << depth) - 1; + return Math.round((value * 255) / maxValue); +}; + +const unpackSamples = (image: DecodedPng): Uint8Array | Uint16Array => { + const { data, depth, width, height, channels } = image; + if (depth === 16) { + if (data instanceof Uint16Array) return data; + return new Uint16Array(data.buffer, data.byteOffset, data.byteLength / 2); + } + + if (depth === 8) { + if (data instanceof Uint8Array) return data; + return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + } + + const raw = + data instanceof Uint8Array + ? data + : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + const pixelCount = width * height; + const samplesPerPixel = channels; + const samplesPerRow = width * samplesPerPixel; + const samples = new Uint8Array(pixelCount * samplesPerPixel); + const mask = (1 << depth) - 1; + + let dataIndex = 0; + let sampleIndex = 0; + + for (let row = 0; row < height; row++) { + let bitsRemaining = 0; + let currentByte = 0; + + for (let sample = 0; sample < samplesPerRow; sample++) { + if (bitsRemaining < depth) { + currentByte = raw[dataIndex++]; + bitsRemaining = 8; + } + + bitsRemaining -= depth; + samples[sampleIndex++] = (currentByte >> bitsRemaining) & mask; + } + + bitsRemaining = 0; + } + + return samples; +}; + +const expandIndexedToRgba = (image: DecodedPng) => { + if (!image.palette || image.palette.length === 0) { + throw new InvalidPngError('Indexed PNG is missing a color palette'); + } + + const paletteChannels = image.palette[0].length; + if (paletteChannels !== 3 && paletteChannels !== 4) { + throw new InvalidPngError( + `Unsupported palette channel count: ${paletteChannels}`, + ); + } + + const paletteData = convertIndexedToRgb(image); + const pixelCount = image.width * image.height; + const rgba = new Uint8Array(pixelCount * 4); + + for (let i = 0; i < pixelCount; i++) { + const paletteOffset = i * paletteChannels; + const rgbaOffset = i * 4; + + rgba[rgbaOffset] = paletteData[paletteOffset]; + rgba[rgbaOffset + 1] = paletteData[paletteOffset + 1]; + rgba[rgbaOffset + 2] = paletteData[paletteOffset + 2]; + rgba[rgbaOffset + 3] = + paletteChannels === 4 ? paletteData[paletteOffset + 3] : 255; + } + + return rgba; +}; + +export const toRgba8 = (image: DecodedPng): Uint8Array => { + if (image.palette) return expandIndexedToRgba(image); + + const samples = unpackSamples(image); + const { channels, depth, width, height, transparency } = image; + const pixelCount = width * height; + + const expectedSamples = pixelCount * channels; + if (samples.length < expectedSamples) { + throw new InvalidPngError( + `PNG data is truncated (expected at least ${expectedSamples} samples, got ${samples.length})`, + ); + } + + const rgba = new Uint8Array(pixelCount * 4); + + switch (channels) { + case 4: { + for (let i = 0; i < pixelCount; i++) { + const sampleOffset = i * 4; + const rgbaOffset = i * 4; + + rgba[rgbaOffset] = toByte(samples[sampleOffset], depth); + rgba[rgbaOffset + 1] = toByte(samples[sampleOffset + 1], depth); + rgba[rgbaOffset + 2] = toByte(samples[sampleOffset + 2], depth); + rgba[rgbaOffset + 3] = toByte(samples[sampleOffset + 3], depth); + } + break; + } + + case 3: { + const transparentColor = + transparency && transparency.length >= 3 + ? [ + toByte(transparency[0], depth), + toByte(transparency[1], depth), + toByte(transparency[2], depth), + ] + : undefined; + + for (let i = 0; i < pixelCount; i++) { + const sampleOffset = i * 3; + const rgbaOffset = i * 4; + + const r = toByte(samples[sampleOffset], depth); + const g = toByte(samples[sampleOffset + 1], depth); + const b = toByte(samples[sampleOffset + 2], depth); + + rgba[rgbaOffset] = r; + rgba[rgbaOffset + 1] = g; + rgba[rgbaOffset + 2] = b; + rgba[rgbaOffset + 3] = + transparentColor && + r === transparentColor[0] && + g === transparentColor[1] && + b === transparentColor[2] + ? 0 + : 255; + } + break; + } + + case 2: { + for (let i = 0; i < pixelCount; i++) { + const sampleOffset = i * 2; + const rgbaOffset = i * 4; + + const gray = toByte(samples[sampleOffset], depth); + const alpha = toByte(samples[sampleOffset + 1], depth); + + rgba[rgbaOffset] = gray; + rgba[rgbaOffset + 1] = gray; + rgba[rgbaOffset + 2] = gray; + rgba[rgbaOffset + 3] = alpha; + } + break; + } + + case 1: { + const transparentSample = + transparency && transparency.length > 0 + ? toByte(transparency[0], depth) + : undefined; + + for (let i = 0; i < pixelCount; i++) { + const rgbaOffset = i * 4; + const gray = toByte(samples[i], depth); + + rgba[rgbaOffset] = gray; + rgba[rgbaOffset + 1] = gray; + rgba[rgbaOffset + 2] = gray; + rgba[rgbaOffset + 3] = + transparentSample !== undefined && gray === transparentSample + ? 0 + : 255; + } + break; + } + + default: + throw new InvalidPngError(`Unknown channel count: ${channels}`); + } + + return rgba; +}; diff --git a/src/utils/png.ts b/src/utils/png.ts index bdbf72c12..ab419cd87 100644 --- a/src/utils/png.ts +++ b/src/utils/png.ts @@ -1,31 +1,24 @@ -import UPNGModule from '@pdf-lib/upng'; -import { - AnimatedPngNotSupportedError, - InvalidPngError, -} from 'src/utils/errors.js'; - -/** - * UPNGModule has different shapes depending on the bundler / module system. - * The following attempts to cover the most common cases. - */ -const UPNG: typeof UPNGModule = - (UPNGModule as any)?.default?.default ?? - (UPNGModule as any)?.default ?? - (UPNGModule as any); - -const mapUpngError = (error: unknown, msgPrefix: string): InvalidPngError => { +import { decode } from 'fast-png'; +import { toRgba8 } from 'src/utils/fast-png-helper.js'; +import { InvalidPngError } from 'src/utils/errors.js'; + +const mapPngError = (error: unknown, msgPrefix: string): InvalidPngError => { const message = - error instanceof Error ? `${error.name}: ${error.message}` : String(error); + error instanceof InvalidPngError + ? error.message + : error instanceof Error + ? `${error.name}: ${error.message}` + : String(error); return new InvalidPngError(`${msgPrefix} ${message}`); }; -const getImageType = (ctype: number) => { - if (ctype === 0) return PngType.Greyscale; - if (ctype === 2) return PngType.Truecolour; - if (ctype === 3) return PngType.IndexedColour; - if (ctype === 4) return PngType.GreyscaleWithAlpha; - if (ctype === 6) return PngType.TruecolourWithAlpha; - throw new InvalidPngError(`Unknown color type: ${ctype}`); +const getImageType = (channels: number, palette?: unknown[][]): PngType => { + if (palette) return PngType.IndexedColour; + if (channels === 1) return PngType.Greyscale; + if (channels === 2) return PngType.GreyscaleWithAlpha; + if (channels === 3) return PngType.Truecolour; + if (channels === 4) return PngType.TruecolourWithAlpha; + throw new InvalidPngError(`Unknown channel count: ${channels}`); }; const splitAlphaChannel = (rgbaChannel: Uint8Array) => { @@ -67,37 +60,31 @@ export class PNG { readonly bitsPerComponent: number; private constructor(pngData: Uint8Array) { - let upng: ReturnType; + let decoded: ReturnType; try { - // @ts-ignore : It internally does new Uint8Array() - upng = UPNG.decode(pngData); + decoded = decode(pngData); } catch (error) { - throw mapUpngError(error, 'Failed to decode PNG:'); + throw mapPngError(error, 'Failed to decode PNG:'); } - let frames: ArrayBuffer[]; + let rgbaBuffer: Uint8Array; try { - frames = UPNG.toRGBA8(upng); + rgbaBuffer = toRgba8(decoded); } catch (error) { - throw mapUpngError(error, 'Failed to convert PNG to RGBA8:'); - } - - if (frames.length > 1) { - throw new AnimatedPngNotSupportedError(); + throw mapPngError(error, 'Failed to convert PNG to RGBA8:'); } - const frame = new Uint8Array(frames[0]); - const { rgbChannel, alphaChannel } = splitAlphaChannel(frame); + const { rgbChannel, alphaChannel } = splitAlphaChannel(rgbaBuffer); this.rgbChannel = rgbChannel; const hasAlphaValues = alphaChannel.some((a) => a < 255); if (hasAlphaValues) this.alphaChannel = alphaChannel; - this.type = getImageType(upng.ctype); + this.type = getImageType(decoded.channels, decoded.palette); - this.width = upng.width; - this.height = upng.height; + this.width = decoded.width; + this.height = decoded.height; this.bitsPerComponent = 8; } } diff --git a/tests/api/PDFDocument.spec.ts b/tests/api/PDFDocument.spec.ts index 32e65173c..f9dc05415 100644 --- a/tests/api/PDFDocument.spec.ts +++ b/tests/api/PDFDocument.spec.ts @@ -652,6 +652,18 @@ describe(`PDFDocument`, () => { InvalidPngError, ); }); + + it(`throws an error when the PNG data is truncated`, async () => { + const pdfDoc = await PDFDocument.create(); + const truncatedPng = examplePngImage.slice( + 0, + Math.floor(examplePngImage.length / 2), + ); + + await expect(pdfDoc.embedPng(truncatedPng)).rejects.toThrow( + InvalidPngError, + ); + }); }); describe(`save() method`, () => { diff --git a/tests/utils/png.spec.ts b/tests/utils/png.spec.ts index 57f0ea93d..0d1ebf140 100644 --- a/tests/utils/png.spec.ts +++ b/tests/utils/png.spec.ts @@ -1,27 +1,385 @@ -import { PNG } from 'src/utils/png.js'; +import { encode } from 'fast-png'; +import { PNG, PngType } from 'src/utils/png.js'; describe(`PNG`, () => { - it(`can load images with alpha values greater than 1`, () => { - // This Uint8Array contains a PNG image composed of a single pixel. It was - // generated with the following code in a browser: - // ``` - // const ctx = c.getContext('2d'); - // ctx.fillStyle = 'rgba(255, 120, 80, 0.5)'; - // ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - // ``` - // The pixel has the following values: R=255, G=120, B=80, A=128 - // - // prettier-ignore - const input = new Uint8Array([ - 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, - 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137, 0, 0, 0, 13, 73, 68, 65, 84, - 24, 87, 99, 248, 95, 17, 208, 0, 0, 6, 137, 2, 72, 25, 58, 220, 62, 0, 0, - 0, 0, 73, 69, 78, 68, 174, 66, 96, 130, - ]); - - const pngImage = PNG.load(input); - - expect(pngImage.rgbChannel).toEqual(new Uint8Array([255, 120, 80])); - expect(pngImage.alphaChannel).toEqual(new Uint8Array([128])); + describe(`color type detection`, () => { + it(`detects greyscale PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 32, // pixel 1 + 64, // pixel 2 + 96, // pixel 3 + 128, // pixel 4 + ]), + channels: 1, + depth: 8, + }), + ); + + expect(pngImage.type).toBe(PngType.Greyscale); + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 32, 32, 32, // pixel 1 + 64, 64, 64, // pixel 2 + 96, 96, 96, // pixel 3 + 128, 128, 128, // pixel 4 + ]), + ); + expect(pngImage.alphaChannel).toBeUndefined(); + }); + + it(`detects truecolour PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 10, 20, 30, // pixel 1 + 40, 50, 60, // pixel 2 + 70, 80, 90, // pixel 3 + 100, 110, 120, // pixel 4 + ]), + channels: 3, + depth: 8, + }), + ); + + expect(pngImage.type).toBe(PngType.Truecolour); + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 10, 20, 30, // pixel 1 + 40, 50, 60, // pixel 2 + 70, 80, 90, // pixel 3 + 100, 110, 120, // pixel 4 + ]), + ); + expect(pngImage.alphaChannel).toBeUndefined(); + }); + + it(`detects indexed-colour PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 0, // pixel 1 + 1, // pixel 2 + 1, // pixel 3 + 0, // pixel 4 + ]), + channels: 1, + depth: 8, + palette: [ + [100, 110, 120], + [10, 20, 30], + ], + }), + ); + + expect(pngImage.type).toBe(PngType.IndexedColour); + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 100, 110, 120, // pixel 1 + 10, 20, 30, // pixel 2 + 10, 20, 30, // pixel 3 + 100, 110, 120, // pixel 4 + ]), + ); + expect(pngImage.alphaChannel).toBeUndefined(); + }); + + it(`detects greyscale-with-alpha PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 40, 200, // pixel 1 + 50, 255, // pixel 2 + 60, 128, // pixel 3 + 70, 0, // pixel 4 + ]), + channels: 2, + depth: 8, + }), + ); + + expect(pngImage.type).toBe(PngType.GreyscaleWithAlpha); + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 40, 40, 40, // pixel 1 + 50, 50, 50, // pixel 2 + 60, 60, 60, // pixel 3 + 70, 70, 70, // pixel 4 + ]), + ); + expect(pngImage.alphaChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 200, // pixel 1 + 255, // pixel 2 + 128, // pixel 3 + 0, // pixel 4 + ]), + ); + }); + + it(`detects truecolour-with-alpha PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 1, 2, 3, 4, // pixel 1 + 5, 6, 7, 255, // pixel 2 + 8, 9, 10, 0, // pixel 3 + 11, 12, 13, 128, // pixel 4 + ]), + channels: 4, + depth: 8, + }), + ); + + expect(pngImage.type).toBe(PngType.TruecolourWithAlpha); + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 1, 2, 3, // pixel 1 + 5, 6, 7, // pixel 2 + 8, 9, 10, // pixel 3 + 11, 12, 13, // pixel 4 + ]), + ); + expect(pngImage.alphaChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 4, // pixel 1 + 255, // pixel 2 + 0, // pixel 3 + 128, // pixel 4 + ]), + ); + }); + }); + + describe(`alpha channel handling`, () => { + it(`drops fully opaque alpha channels`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 9, 8, 7, 255, // pixel 1 + 1, 2, 3, 255, // pixel 2 + 4, 5, 6, 255, // pixel 3 + 7, 8, 9, 255, // pixel 4 + ]), + channels: 4, + depth: 8, + }), + ); + + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 9, 8, 7, // pixel 1 + 1, 2, 3, // pixel 2 + 4, 5, 6, // pixel 3 + 7, 8, 9, // pixel 4 + ]), + ); + expect(pngImage.alphaChannel).toBeUndefined(); + }); + + it(`preserves palette alpha for indexed PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 0, // pixel 1 + 1, // pixel 2 + 1, // pixel 3 + 0, // pixel 4 + ]), + channels: 1, // indexed + depth: 8, + palette: [ + [12, 34, 56, 128], + [98, 76, 54, 255], + ], + }), + ); + + expect(pngImage.type).toBe(PngType.IndexedColour); + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 12, 34, 56, // pixel 1 + 98, 76, 54, // pixel 2 + 98, 76, 54, // pixel 3 + 12, 34, 56, // pixel 4 + ]), + ); + expect(pngImage.alphaChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 128, // pixel 1 + 255, // pixel 2 + 255, // pixel 3 + 128, // pixel 4 + ]), + ); + }); + }); + + describe(`bit depth normalization`, () => { + it(`handles 1-bit PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 0b10000000, // row 1: pixel 1=1, pixel 2=0 + 0b01000000, // row 2: pixel 3=0, pixel 4=1 + ]), + channels: 1, // greyscale + depth: 1, + }), + ); + + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 255, 255, 255, // pixel 1 + 0, 0, 0, // pixel 2 + 0, 0, 0, // pixel 3 + 255, 255, 255, // pixel 4 + ]), + ); + }); + + it(`handles 2-bit PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 0b11000000, // row 1: pixel 1=3, pixel 2=0 + 0b01100000, // row 2: pixel 3=1, pixel 4=2 + ]), + channels: 1, // greyscale + depth: 2, + }), + ); + + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 255, 255, 255, // pixel 1 (3/3) + 0, 0, 0, // pixel 2 (0/3) + 85, 85, 85, // pixel 3 (1/3) + 170, 170, 170, // pixel 4 (2/3) + ]), + ); + }); + + it(`handles 4-bit PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 0b11110000, // row 1: pixel 1=15, pixel 2=0 + 0b10000100, // row 2: pixel 3=8, pixel 4=4 + ]), + channels: 1, // greyscale + depth: 4, + }), + ); + + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 255, 255, 255, // pixel 1 + 0, 0, 0, // pixel 2 + 136, 136, 136, // pixel 3 + 68, 68, 68, // pixel 4 + ]), + ); + }); + + it(`handles 8-bit PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint8Array([ + 12, 34, 56, // pixel 1 + 78, 90, 12, // pixel 2 + 34, 56, 78, // pixel 3 + 90, 12, 34, // pixel 4 + ]), + channels: 3, // RGB + depth: 8, + }), + ); + + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 12, 34, 56, // pixel 1 + 78, 90, 12, // pixel 2 + 34, 56, 78, // pixel 3 + 90, 12, 34, // pixel 4 + ]), + ); + }); + + it(`handles 16-bit PNGs`, () => { + const pngImage = PNG.load( + encode({ + width: 2, + height: 2, + // prettier-ignore + data: new Uint16Array([ + 0xffff, 0x8000, 0x0000, // pixel 1 + 0x0000, 0xffff, 0x8000, // pixel 2 + 0x8000, 0x0000, 0xffff, // pixel 3 + 0x4000, 0xc000, 0x2000, // pixel 4 + ]), + channels: 3, // RGB + depth: 16, + }), + ); + + expect(pngImage.rgbChannel).toEqual( + // prettier-ignore + new Uint8Array([ + 255, 128, 0, // pixel 1 + 0, 255, 128, // pixel 2 + 128, 0, 255, // pixel 3 + 64, 191, 32, // pixel 4 + ]), + ); + }); }); }); diff --git a/yarn.lock b/yarn.lock index d39adf51b..edbf2dc93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -217,13 +217,6 @@ dependencies: pako "^1.0.6" -"@pdf-lib/upng@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@pdf-lib/upng/-/upng-1.0.1.tgz#7dc9c636271aca007a9df4deaf2dd7e7960280cb" - integrity sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ== - dependencies: - pako "^1.0.10" - "@rollup/plugin-commonjs@^29.0.0": version "29.0.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz#42a6cc0eeb8ae7aabfc6f9bdc28fe22d65abd15a" @@ -959,11 +952,24 @@ expect-type@^1.2.2: resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== +fast-png@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/fast-png/-/fast-png-8.0.0.tgz#99e1070786cd3fda92895dce3b32295fcd632ad8" + integrity sha512-gCysNasJ8KEMgfdYIKd/wTDo6ENK1PWT0RJO7O+0pgmuHPw2O6tA1WvdxFRJoLf9V8yFYpG0FA1YgI8X97OhJA== + dependencies: + fflate "^0.8.2" + iobuffer "^6.0.1" + fdir@^6.2.0, fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + flamebearer@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/flamebearer/-/flamebearer-1.1.3.tgz#aac0cb56305e1af2b16528aca649c76103f75c08" @@ -1187,6 +1193,11 @@ ini@^4.1.3: resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.3.tgz#4c359675a6071a46985eb39b14e4a2c0ec98a795" integrity sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg== +iobuffer@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/iobuffer/-/iobuffer-6.0.1.tgz#e550d671384362d541b5a23e1a81ed46ecab36d8" + integrity sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q== + is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" @@ -1367,7 +1378,7 @@ pako@^0.2.5: resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== -pako@^1.0.10, pako@^1.0.6: +pako@^1.0.6: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==