@syncfusion/ej2-pdf
Version:
Feature-rich JavaScript PDF library with built-in support for loading and manipulating PDF document.
951 lines (950 loc) • 39.2 kB
JavaScript
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import { _ImageDecoder } from './image-decoder';
import { _PdfStream } from './../../base-stream';
import { _PdfDictionary, _PdfName } from './../../pdf-primitives';
import { _ImageFormat } from './../../enumerator';
import { _bytesToString } from './../../utils';
import { _DeflateStream } from './../../compression/deflate-stream';
/* eslint-disable */
/**
* PNG image decoder that parses PNG chunks, inflates compressed data,
* applies PNG scanline filters None/Sub/Up/Average/Paeth, reconstructs
* pixels (including alpha/mask when present), and exposes a PDF image stream.
*
* @private
*/
var _PngDecoder = /** @class */ (function (_super) {
__extends(_PngDecoder, _super);
/**
* Initializes a new instance of the `_PngDecoder` class.
*
* @private
* @param {Uint8Array} stream byte array.
*/
function _PngDecoder(stream) {
var _this = _super.call(this) || this;
_this._stream = stream;
_this._format = _ImageFormat.png;
_this._isRedGreenBlue = false;
_this._isDecode = false;
_this._shades = false;
_this._ideateDecode = true;
_this._colors = 0;
_this._bitsPerPixel = 0;
_this._idatLength = 0;
_this._inputBands = 0;
_this._position = 8;
_this._initialize();
return _this;
}
/**
* Iterates through the PNG stream, reading chunk headers and dispatching
* handlers (IHDR, IDAT, PLTE, tRNS, etc.) until end of image or stream end.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._initialize = function () {
var header;
var result = this._hasValidChunkType(header);
while (result.hasValidChunk) {
header = result.type;
switch (header) {
case _PngChunkTypes.iHDR:
this._readHeader();
break;
case _PngChunkTypes.iDAT:
this._readImageData();
break;
case _PngChunkTypes.sRGB:
this._isRedGreenBlue = true;
this._ignoreChunk();
break;
case _PngChunkTypes.pLTE:
this._readPhotoPlate();
break;
case _PngChunkTypes.iEND:
this._decodeImageData();
break;
case _PngChunkTypes.tRNS:
this._readTransparency();
break;
case _PngChunkTypes.tEXt:
case _PngChunkTypes.iTXt:
case _PngChunkTypes.zTXt:
case _PngChunkTypes.hIST:
case _PngChunkTypes.sBIT:
case _PngChunkTypes.iCCP:
case _PngChunkTypes.pHYs:
case _PngChunkTypes.tIME:
case _PngChunkTypes.bKGD:
case _PngChunkTypes.gAMA:
case _PngChunkTypes.cHRM:
case _PngChunkTypes.unknown:
this._ignoreChunk();
break;
default:
break;
}
result = this._hasValidChunkType(header);
}
};
/**
* Reads the next chunk length and type, mapping it to an internal chunk enum.
*
* @private
* @param {_PngChunkTypes} type Placeholder for the detected chunk type (ignored input).
* @returns {{type: _PngChunkTypes, hasValidChunk: boolean}} The detected type and whether a valid chunk is available.
*/
_PngDecoder.prototype._hasValidChunkType = function (type) {
type = _PngChunkTypes.unknown;
if (this._position + 8 <= this._stream.byteLength) {
this._currentChunkLength = this._readUnsigned32(this._position);
this._seek(4);
var chunk = this._readString(4);
var header = this._getChunkType(chunk);
if (typeof header !== 'undefined' && header !== null) {
type = header;
return { 'type': type, 'hasValidChunk': true };
}
if (this._stream.byteLength === this._position) {
return { 'type': type, 'hasValidChunk': false };
}
else {
return { 'type': type, 'hasValidChunk': true };
}
}
else {
return { 'type': type, 'hasValidChunk': false };
}
};
/**
* Skips over the current chunk payload and CRC.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._ignoreChunk = function () {
if (this._currentChunkLength > 0) {
this._seek(this._currentChunkLength + 4);
}
};
/**
* Parses the IHDR chunk and initializes decoder fields such as dimensions,
* bit depth, color type, interlace, and derived attributes.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._readHeader = function () {
this._header = new _PngHeader();
this._header._width = this._readUnsigned32(this._position);
this._seek(4);
this._header._height = this._readUnsigned32(this._position);
this._seek(4);
this._header._bitDepth = this._readByte();
this._header._colorType = this._readByte();
this._header._compression = this._readByte();
this._header._filter = this._getFilterType(this._readByte());
this._header._interlace = this._readByte();
this._colors = (this._header._colorType === 3 || (this._header._colorType & 2) === 0) ? 1 : 3;
this._width = this._header._width;
this._height = this._header._height;
this._bitsPerComponent = this._header._bitDepth;
this._setBitsPerPixel();
this._seek(4);
};
/**
* Computes input bands, IDAT storage length hints, and effective bytes-per-pixel
* based on color type and bit depth.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._setBitsPerPixel = function () {
this._bitsPerPixel = this._header._bitDepth === 16 ? 2 : 1;
if (this._header._colorType === 0) {
this._idatLength = Math.floor((this._bitsPerComponent * this._width + 7) / 8) * this._height;
this._inputBands = 1;
}
else if (this._header._colorType === 2) {
this._idatLength = this._width * this._height * 3;
this._inputBands = 3;
this._bitsPerPixel *= 3;
}
else if (this._header._colorType === 3) {
if (this._header._interlace === 1 || this._header._interlace === 0) {
this._idatLength = Math.floor((this._header._bitDepth * this._width + 7) / 8) * this._height;
}
this._inputBands = 1;
this._bitsPerPixel = 1;
}
else if (this._header._colorType === 4) {
this._idatLength = this._width * this._height;
this._inputBands = 2;
this._bitsPerPixel *= 2;
}
else if (this._header._colorType === 6) {
this._idatLength = this._width * 3 * this._height;
this._inputBands = 4;
this._bitsPerPixel *= 4;
}
};
/**
* Appends the current IDAT chunk payload to the internal compressed buffer
* and advances the stream position past CRC.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._readImageData = function () {
if (!this._encodedStream) {
var estimatedSize = Math.max(this._currentChunkLength * 2, 1024);
this._encodedStream = new Uint8Array(estimatedSize);
this._encodedStreamLength = 0;
}
if (this._currentChunkLength <= this._stream.byteLength &&
this._stream.byteLength - this._position >= this._currentChunkLength) {
var currentLength = this._encodedStreamLength;
var newLength = currentLength + this._currentChunkLength;
if (this._encodedStream.length < newLength) {
var currentCapacity = this._encodedStream.length;
var doubleCapacity = currentCapacity * 2;
var capacity = Math.max(newLength, doubleCapacity);
var newArray = new Uint8Array(capacity);
newArray.set(this._encodedStream.subarray(0, currentLength));
this._encodedStream = newArray;
}
this._encodedStream.set(this._stream.subarray(this._position, this._position + this._currentChunkLength), currentLength);
this._encodedStreamLength = newLength;
this._position += this._currentChunkLength;
}
this._seek(4);
};
/**
* Reads the PLTE palette for indexed color images and prepares the
* Indexed color space structure in PDF terms.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._readPhotoPlate = function () {
if (this._header._colorType === 3) {
this._colorSpace = [];
this._colorSpace.push(_PdfName.get('Indexed'));
this._colorSpace.push(this._getPngColorSpace());
this._colorSpace.push(this._currentChunkLength / 3 - 1);
var buffer = new Uint8Array(this._currentChunkLength);
this._read(buffer, 0, this._currentChunkLength);
this._colorSpace.push(_bytesToString(buffer));
this._seek(4);
}
else {
this._ignoreChunk();
}
};
/**
* Reads the tRNS transparency table and caches the
* alpha bytes while detecting partial transparency.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._readTransparency = function () {
if (this._header._colorType === 3) {
var alpha = new Uint8Array(this._currentChunkLength);
this._read(alpha, 0, this._currentChunkLength);
this._seek(4);
var alphaLength = alpha.length;
this._alpha = new Uint8Array(alphaLength);
var hasShades = false;
for (var i = 0; i < alphaLength; i++) {
var alphaByte = alpha[i] & 0xff;
this._alpha[i] = alphaByte;
if (!hasShades && alphaByte !== 0 && alphaByte !== 255) {
hasShades = true;
}
}
this._shades = hasShades;
}
else {
this._ignoreChunk();
}
};
/**
* Resolves the effective PDF color space for the image. Returns either
* `DeviceGray`/`DeviceRGB` or a calibrated `CalRGB` array when sRGB is present.
*
* @private
* @returns {any} A color space name object or an array describing a calibrated RGB space.
*/
_PngDecoder.prototype._getPngColorSpace = function () {
if (!this._isRedGreenBlue) {
if ((this._header._colorType & 2) === 0) {
return _PdfName.get('DeviceGray');
}
else {
return _PdfName.get('DeviceRGB');
}
}
else {
var colorspace = [];
var calRGB = new _PdfDictionary();
var whitePoint = [1, 1, 1];
var gammaArray = [2.2, 2.2, 2.2];
calRGB.set('Gamma', gammaArray);
if (this._isRedGreenBlue) {
var wpX = 0.3127;
var wpY = 0.329;
var redX = 0.64;
var redY = 0.33;
var greenX = 0.3;
var greenY = 0.6;
var bX = 0.15;
var bY = 0.06;
var t = wpY * ((greenX - bX) * redY - (redX - bX) * greenY + (redX - greenX) * bY);
var alphaY = redY * ((greenX - bX) * wpY - (wpX - bX) * greenY + (wpX - greenX) * bY) / t;
var alphaX = alphaY * redX / redY;
var alphaZ = alphaY * ((1 - redX) / redY - 1);
var blueY = -greenY * ((redX - bX) * wpY - (wpX - bX) * redY + (wpX - redX) * bY) / t;
var blueX = blueY * greenX / greenY;
var blueZ = blueY * ((1 - greenX) / greenY - 1);
var colorY = bY * ((redX - greenX) * wpY - (wpX - greenX) * wpY + (wpX - redX) * greenY) / t;
var colorX = colorY * bX / bY;
var colorZ = colorY * ((1 - bX) / bY - 1);
var whiteX = alphaX + blueX + colorX;
var whiteY = 1;
var whiteZ = alphaZ + blueZ + colorZ;
whitePoint = [whiteX, whiteY, whiteZ];
calRGB.set('Matrix', [alphaX, alphaY, alphaZ, blueX, blueY, blueZ, colorX, colorY, colorZ]);
}
calRGB.set('WhitePoint', whitePoint);
colorspace.push(_PdfName.get('CalRGB'));
colorspace.push(calRGB);
return colorspace;
}
};
/**
* Determines whether raw decode is required interlaced, 16-bit, or with alpha/shades.
* If so, inflates IDAT data, allocates buffers, and reconstructs pixels; otherwise
* reuses the encoded stream directly.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._decodeImageData = function () {
var header = this._header;
this._isDecode = (header._interlace === 1) || (header._bitDepth === 16) || ((header._colorType & 4) !== 0) || this._shades;
if (this._isDecode) {
if ((header._colorType & 4) !== 0 || this._shades) {
this._maskData = new Uint8Array(this._width * this._height);
}
if (this._encodedStream) {
this._dataStream = this._getDeflatedData(this._encodedStream.subarray(0, this._encodedStreamLength));
this._dataStreamOffset = 0;
}
if (this._idatLength > 0) {
this._decodedImageData = new Uint8Array(this._idatLength);
}
this._readDecodeData();
if (this._decodedImageData && this._decodedImageData.length === 0 && this._shades) {
this._ideateDecode = false;
this._decodedImageData = this._encodedStream.subarray(0, this._encodedStreamLength);
}
}
else {
this._ideateDecode = false;
this._decodedImageData = this._encodedStream.subarray(0, this._encodedStreamLength);
}
};
/**
* Inflates the zlib-compressed IDAT payload omitting zlib header and Adler-32 trailer
* into a raw byte array.
*
* @private
* @param {Uint8Array} data The zlib-wrapped IDAT bytes (concatenated).
* @returns {Uint8Array} The inflated byte array.
*/
_PngDecoder.prototype._getDeflatedData = function (data) {
var idatData = data.subarray(2, data.length - 4);
var deflateStream = new _DeflateStream(Array.from(idatData), 0, true);
var chunkSize = 4096;
var outputChunks = [];
var totalLength = 0;
while (true) {
var tempBuffer = new Array(chunkSize);
var result = deflateStream._read(tempBuffer, 0, chunkSize);
var count = result.count;
var resultData = result.data;
if (count <= 0) {
break;
}
var chunk = new Uint8Array(count);
for (var i = 0; i < count; i++) {
chunk[i] = resultData[i];
}
outputChunks.push(chunk);
totalLength += count;
}
var outputData = new Uint8Array(totalLength);
var offset = 0;
for (var _i = 0, outputChunks_1 = outputChunks; _i < outputChunks_1.length; _i++) {
var chunk = outputChunks_1[_i];
outputData.set(chunk, offset);
offset += chunk.length;
}
return outputData;
};
/**
* Dispatches pixel reconstruction by interlace method: a single pass for
* non-interlaced images or seven Adam7 passes for interlaced images.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._readDecodeData = function () {
if (this._header._interlace !== 1) {
this._decodeData(0, 0, 1, 1, this._width, this._height);
}
else {
this._decodeData(0, 0, 8, 8, Math.floor((this._width + 7) / 8), Math.floor((this._height + 7) / 8));
this._decodeData(4, 0, 8, 8, Math.floor((this._width + 3) / 8), Math.floor((this._height + 7) / 8));
this._decodeData(0, 4, 4, 8, Math.floor((this._width + 3) / 4), Math.floor((this._height + 3) / 8));
this._decodeData(2, 0, 4, 4, Math.floor((this._width + 1) / 4), Math.floor((this._height + 3) / 4));
this._decodeData(0, 2, 2, 4, Math.floor((this._width + 1) / 2), Math.floor((this._height + 1) / 4));
this._decodeData(1, 0, 2, 2, Math.floor(this._width / 2), Math.floor((this._height + 1) / 2));
this._decodeData(0, 1, 1, 2, this._width, Math.floor(this._height / 2));
}
};
/**
* Reconstructs scanlines for a pass/region by reading filter type, applying the
* corresponding PNG filter, and writing pixels (and alpha/mask) to output buffers.
*
* @private
* @param {number} xOffset Starting x offset in destination image for this pass.
* @param {number} yOffset Starting y offset in destination image for this pass.
* @param {number} xStep X stride (per Adam7 pass or 1 for non-interlaced).
* @param {number} yStep Y stride (per Adam7 pass or 1 for non-interlaced).
* @param {number} width Width of this pass/region in pixels.
* @param {number} height Height of this pass/region in pixels.
* @returns {void}
*/
_PngDecoder.prototype._decodeData = function (xOffset, yOffset, xStep, yStep, width, height) {
var _a;
if (width === 0 || height === 0) {
return;
}
var bytesPerRow = Math.floor((this._inputBands * width * this._header._bitDepth + 7) / 8);
var current = new Uint8Array(bytesPerRow);
var prior = new Uint8Array(bytesPerRow);
for (var sourceY = 0, destinationY = yOffset; sourceY < height; sourceY++, destinationY += yStep) {
var filter = this._dataStream[this._dataStreamOffset++];
this._dataStreamOffset = this._readStream(this._dataStream, this._dataStreamOffset, current, bytesPerRow);
switch (this._getFilterType(filter)) {
case _PngFilterTypes.none:
break;
case _PngFilterTypes.sub:
this._decompressSub(current, bytesPerRow, this._bitsPerPixel);
break;
case _PngFilterTypes.up:
this._decompressUp(current, prior, bytesPerRow);
break;
case _PngFilterTypes.average:
this._decompressAverage(current, prior, bytesPerRow, this._bitsPerPixel);
break;
case _PngFilterTypes.paeth:
this._decompressPaeth(current, prior, bytesPerRow, this._bitsPerPixel);
break;
default:
throw new Error('Unknown PNG filter');
}
this._processPixels(current, xOffset, xStep, destinationY, width);
_a = [current, prior], prior = _a[0], current = _a[1];
}
};
/**
* Reads `count` bytes from a numeric array stream into `data` using the common
* `_read` routine, returning the updated stream offset.
*
* @private
* @param {Uint8Array} stream The source byte array.
* @param {number} streamOffset The current read offset in the stream.
* @param {Uint8Array} data The destination buffer to fill.
* @param {number} count The number of bytes to read.
* @returns {number} The new stream offset after reading.
* @throws {Error} If insufficient data is available.
*/
_PngDecoder.prototype._readStream = function (stream, streamOffset, data, count) {
if (streamOffset + count > stream.length) {
throw new Error('Insufficient data');
}
data.set(stream.subarray(streamOffset, streamOffset + count));
return streamOffset + count;
};
/**
* Applies the PNG Sub filter (type 1) in-place to the current scanline.
*
* @private
* @param {Uint8Array} data The scanline bytes to modify.
* @param {number} count Number of bytes in the scanline row.
* @param {number} bitsPerPixel Bytes-per-pixel (not bit-depth) for subtraction reference.
* @returns {void}
*/
_PngDecoder.prototype._decompressSub = function (data, count, bitsPerPixel) {
for (var i = bitsPerPixel; i < count; i++) {
data[i] = (data[i] + data[i - bitsPerPixel]) & 0xff;
}
};
/**
* Applies the PNG Up filter (type 2) in-place using the prior scanline.
*
* @private
* @param {Uint8Array} data The current scanline bytes.
* @param {Uint8Array} pData The prior scanline bytes.
* @param {number} count Number of bytes per row.
* @returns {void}
*/
_PngDecoder.prototype._decompressUp = function (data, pData, count) {
for (var i = 0; i < count; i++) {
data[i] = (data[i] + pData[i]) & 0xff;
}
};
/**
* Applies the PNG Average filter (type 3) in-place using left and prior values.
*
* @private
* @param {Uint8Array} data The current scanline bytes.
* @param {Uint8Array} pData The prior scanline bytes.
* @param {number} count Number of bytes per row.
* @param {number} bitsPerPixel Bytes-per-pixel (not bit-depth) for left reference.
* @returns {void}
*/
_PngDecoder.prototype._decompressAverage = function (data, pData, count, bitsPerPixel) {
for (var i = 0; i < bitsPerPixel; i++) {
data[i] = (data[i] + (pData[i] >> 1)) & 0xff;
}
for (var i = bitsPerPixel; i < count; i++) {
data[i] = (data[i] + ((data[i - bitsPerPixel] + pData[i]) >> 1)) & 0xff;
}
};
/**
* Applies the PNG Paeth filter (type 4) in-place using left, up, and up-left predictors.
*
* @private
* @param {Uint8Array} data The current scanline bytes.
* @param {Uint8Array} pData The prior scanline bytes.
* @param {number} count Number of bytes per row.
* @param {number} bitsPerPixel Bytes-per-pixel (not bit-depth) for left reference.
* @returns {void}
*/
_PngDecoder.prototype._decompressPaeth = function (data, pData, count, bitsPerPixel) {
for (var i = 0; i < bitsPerPixel; i++) {
data[i] = (data[i] + pData[i]) & 0xff;
}
for (var i = bitsPerPixel; i < count; i++) {
data[i] = (data[i] + this._paethPredictor(data[i - bitsPerPixel], pData[i], pData[i - bitsPerPixel])) & 0xff;
}
};
/**
* Computes the Paeth predictor from left (`a`), up (`b`), and up-left (`c`) neighbors.
*
* @private
* @param {number} a The left pixel sample.
* @param {number} b The above pixel sample.
* @param {number} c The upper-left pixel sample.
* @returns {number} The chosen predictor value.
*/
_PngDecoder.prototype._paethPredictor = function (a, b, c) {
var p = a + b - c;
var pa = Math.abs(p - a);
var pb = Math.abs(p - b);
var pc = Math.abs(p - c);
return (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
};
/**
* Converts a filtered scanline row to pixel values and writes them into the
* decoded image buffer, also writing an 8-bit mask row when alpha/shades are present.
*
* @private
* @param {Uint8Array} data The unfiltered scanline bytes for this pass.
* @param {number} x Destination x start (accounting for pass offset).
* @param {number} step Destination x increment (per Adam7 pass or 1).
* @param {number} y Destination y coordinate.
* @param {number} width The number of pixels to process from this row.
* @returns {void}
*/
_PngDecoder.prototype._processPixels = function (data, x, step, y, width) {
var sourceX = 0;
var destX = 0;
var size = 0;
var pixel = this._getPixel(data);
if (this._header._colorType === 0 || this._header._colorType === 3 || this._header._colorType === 4) {
size = 1;
}
else if (this._header._colorType === 2 || this._header._colorType === 6) {
size = 3;
}
if (this._decodedImageData && this._decodedImageData.length > 0) {
destX = x;
var depth = (this._header._bitDepth === 16) ? 8 : this._header._bitDepth;
var yStep = Math.floor((size * width * depth + 7) / 8);
for (sourceX = 0; sourceX < width; sourceX++) {
this._setPixel(this._decodedImageData, pixel, this._inputBands * sourceX, size, destX, y, this._header._bitDepth, yStep);
destX += step;
}
}
var shades = (this._header._colorType & 4) !== 0 || this._shades;
if (shades) {
if ((this._header._colorType & 4) !== 0) {
if (this._header._bitDepth === 16) {
var typedPixel = pixel;
for (var i = 0; i < width; ++i) {
var temp = i * this._inputBands + size;
typedPixel[temp] = typedPixel[temp] >> 8;
}
}
var yStep = width;
destX = x;
for (sourceX = 0; sourceX < width; sourceX++) {
this._setPixel(this._maskData, pixel, this._inputBands * sourceX + size, 1, destX, y, 8, yStep);
destX += step;
}
}
else {
var yStep = width;
var dt = new Uint8Array(1);
destX = x;
for (sourceX = 0; sourceX < width; sourceX++) {
var index = pixel[sourceX];
dt[0] = index < this._alpha.length ? this._alpha[index] : 255;
this._setPixel(this._maskData, dt, 0, 1, destX, y, 8, yStep);
destX += step;
}
}
}
};
/**
* Expands a scanline's filtered bytes into sample values based on bit depth:
* 8-bit (1:1), 16-bit (merge pairs), or packed sub-byte samples.
*
* @private
* @param {Uint8Array} data The scanline bytes.
* @returns {Uint8Array | Uint16Array} The expanded per-sample values for the row.
*/
_PngDecoder.prototype._getPixel = function (data) {
var bitDepth = this._header._bitDepth;
if (bitDepth === 8) {
return data;
}
else if (bitDepth === 16) {
var pixelLength = Math.floor(data.length / 2);
var pixel = new Uint16Array(pixelLength);
for (var i = 0; i < pixelLength; i++) {
pixel[i] = (data[i * 2] << 8) | data[i * 2 + 1];
}
return pixel;
}
else {
var pixelLength = Math.floor((data.length * 8) / bitDepth);
var pixel = new Uint8Array(pixelLength);
var index = 0;
var p = Math.floor(8 / bitDepth);
var mask = (1 << bitDepth) - 1;
for (var n = 0; n < data.length; n++) {
var d = data[n];
for (var i = p - 1; i >= 0; i--) {
pixel[index++] = (d >> (bitDepth * i)) & mask;
}
}
return pixel;
}
};
/**
* Writes one pixel or scalar sample to the destination buffer, handling
* 8/16-bit storage or sub-byte packing as required.
*
* @private
* @param {Uint8Array} imageData The destination buffer (image or mask).
* @param {Uint8Array | Uint16Array} data The source per-sample array.
* @param {number} offset Offset into source sample array.
* @param {number} size Number of samples to write (1=gray/alpha, 3=RGB).
* @param {number} x Destination x coordinate.
* @param {number} y Destination y coordinate.
* @param {number} bitDepth Source bit depth (8/16 or packed).
* @param {number} bpr Bytes-per-row in the destination buffer.
* @returns {void}
*/
_PngDecoder.prototype._setPixel = function (imageData, data, offset, size, x, y, bitDepth, bpr) {
if (bitDepth === 8) {
var position = bpr * y + size * x;
for (var i = 0; i < size; ++i) {
imageData[position + i] = data[i + offset];
}
}
else if (bitDepth === 16) {
var position = bpr * y + size * x;
for (var i = 0; i < size; ++i) {
imageData[position + i] = data[i + offset] >> 8;
}
}
else {
var position = Math.floor((bpr * y + x) / (8 / bitDepth));
var t = data[offset] << (8 - bitDepth * (x % (8 / bitDepth)) - bitDepth);
imageData[position] = imageData[position] | (t & 0xff);
}
};
/**
* Builds the PDF image stream for the PNG image, wiring the
* core dictionary entries, optional `FlateDecode`, and optional `DecodeParms`,
* and attaches the mask if present.
*
* @private
* @returns {_PdfStream} The image stream suitable for embedding.
*/
_PngDecoder.prototype._getImageDictionary = function () {
if (this._imageStream && this._imageStream.length > 0) {
return this._imageStream;
}
else {
var data = []; // eslint-disable
this._imageStream = new _PdfStream(data, new _PdfDictionary());
this._imageStream.isImageStream = true;
this._imageStream.bytes = this._decodedImageData;
this._imageStream._isCompress = this._isDecode && this._ideateDecode;
var dictionary = new _PdfDictionary();
dictionary.set('Type', new _PdfName('XObject'));
dictionary.set('Subtype', new _PdfName('Image'));
dictionary.set('Width', this._width);
dictionary.set('Height', this._height);
if (this._bitsPerComponent === 16) {
dictionary.set('BitsPerComponent', 8);
}
else {
dictionary.set('BitsPerComponent', this._bitsPerComponent);
}
if (!this._isDecode || !this._ideateDecode) {
dictionary.set('Filter', new _PdfName('FlateDecode'));
}
if ((this._header._colorType & 2) === 0) {
dictionary.set('ColorSpace', _PdfName.get('DeviceGray'));
}
else {
dictionary.set('ColorSpace', _PdfName.get('DeviceRGB'));
}
if (!this._isDecode || this._shades && !this._ideateDecode) {
dictionary.set('DecodeParms', this._getDecodeParams());
}
this._imageStream.dictionary = dictionary;
this._imageStream.end = this._imageStream.bytes.length;
this._imageStream.dictionary._updated = true;
this._setMask();
return this._imageStream;
}
};
/**
* Creates and assigns the soft-mask image stream from the alpha (or shades)
* buffer when available.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype._setMask = function () {
if (this._maskData && this._maskData.length > 0) {
this._maskStream = new _PdfStream(this._maskData, new _PdfDictionary());
this._maskStream.bytes = this._maskData;
this._maskStream._isCompress = this._isDecode && this._ideateDecode;
var dictionary = new _PdfDictionary();
dictionary.set('Type', new _PdfName('XObject'));
dictionary.set('Subtype', new _PdfName('Image'));
dictionary.set('Width', this._width);
dictionary.set('Height', this._height);
if (this._bitsPerComponent === 16) {
dictionary.set('BitsPerComponent', 8);
}
else {
dictionary.set('BitsPerComponent', this._bitsPerComponent);
}
dictionary.set('ColorSpace', _PdfName.get('DeviceGray'));
this._maskStream.dictionary = dictionary;
this._maskStream.end = this._maskStream.bytes.length;
this._maskStream.dictionary._updated = true;
}
};
/**
* Creates the `DecodeParms` dictionary (Columns/Colors/Predictor/BitsPerComponent)
* for use with `FlateDecode` streams.
*
* @private
* @returns {_PdfDictionary} The decode parameters dictionary.
*/
_PngDecoder.prototype._getDecodeParams = function () {
var decodeParams = new _PdfDictionary();
decodeParams.set('Columns', this._width);
decodeParams.set('Colors', this._colors);
decodeParams.set('Predictor', 15);
decodeParams.set('BitsPerComponent', this._bitsPerComponent);
return decodeParams;
};
/**
* Maps a 4 character PNG chunk label to the internal enum value.
*
* @private
* @param {string} chunk The chunk name.
* @returns {_PngChunkTypes} The mapped chunk type or `null` if unknown.
*/
_PngDecoder.prototype._getChunkType = function (chunk) {
switch (chunk) {
case 'IHDR':
return _PngChunkTypes.iHDR;
case 'PLTE':
return _PngChunkTypes.pLTE;
case 'IDAT':
return _PngChunkTypes.iDAT;
case 'IEND':
return _PngChunkTypes.iEND;
case 'bKGD':
return _PngChunkTypes.bKGD;
case 'cHRM':
return _PngChunkTypes.cHRM;
case 'gAMA':
return _PngChunkTypes.gAMA;
case 'hIST':
return _PngChunkTypes.hIST;
case 'pHYs':
return _PngChunkTypes.pHYs;
case 'sBIT':
return _PngChunkTypes.sBIT;
case 'tEXt':
return _PngChunkTypes.tEXt;
case 'tIME':
return _PngChunkTypes.tIME;
case 'tRNS':
return _PngChunkTypes.tRNS;
case 'zTXt':
return _PngChunkTypes.zTXt;
case 'sRGB':
return _PngChunkTypes.sRGB;
case 'iCCP':
return _PngChunkTypes.iCCP;
case 'iTXt':
return _PngChunkTypes.iTXt;
case 'Unknown':
return _PngChunkTypes.unknown;
default:
return null;
}
};
/**
* Maps a numeric filter byte to the corresponding PNG filter type enum.
*
* @private
* @param {number} type The PNG filter byte (0..4).
* @returns {_PngFilterTypes} The filter enum value.
*/
_PngDecoder.prototype._getFilterType = function (type) {
switch (type) {
case 1:
return _PngFilterTypes.sub;
case 2:
return _PngFilterTypes.up;
case 3:
return _PngFilterTypes.average;
case 4:
return _PngFilterTypes.paeth;
default:
return _PngFilterTypes.none;
}
};
/**
* Releases decoder-held buffers and references to allow GC.
*
* @private
* @returns {void}
*/
_PngDecoder.prototype.dispose = function () {
this._encodedStream = null;
this._maskData = null;
this._alpha = null;
this._dataStream = null;
this._decodedImageData = null;
this._colorSpace = null;
};
return _PngDecoder;
}(_ImageDecoder));
export { _PngDecoder };
/**
* Holds parsed values from the PNG `IHDR` chunk, including size, color type,
* compression, filter method, and interlace mode; used to guide decoding.
*
* @private
*/
var _PngHeader = /** @class */ (function () {
function _PngHeader() {
this._width = 0;
this._height = 0;
this._colorType = 0;
this._compression = 0;
this._bitDepth = 0;
this._interlace = 0;
this._filter = _PngFilterTypes.none;
}
return _PngHeader;
}());
/**
* Enumerates PNG chunk types recognized by the decoder, including core chunks
* and optional metadata/ancillary chunks.
*
* @private
*/
var _PngChunkTypes;
(function (_PngChunkTypes) {
_PngChunkTypes[_PngChunkTypes["iHDR"] = 0] = "iHDR";
_PngChunkTypes[_PngChunkTypes["pLTE"] = 1] = "pLTE";
_PngChunkTypes[_PngChunkTypes["iDAT"] = 2] = "iDAT";
_PngChunkTypes[_PngChunkTypes["iEND"] = 3] = "iEND";
_PngChunkTypes[_PngChunkTypes["bKGD"] = 4] = "bKGD";
_PngChunkTypes[_PngChunkTypes["cHRM"] = 5] = "cHRM";
_PngChunkTypes[_PngChunkTypes["gAMA"] = 6] = "gAMA";
_PngChunkTypes[_PngChunkTypes["hIST"] = 7] = "hIST";
_PngChunkTypes[_PngChunkTypes["pHYs"] = 8] = "pHYs";
_PngChunkTypes[_PngChunkTypes["sBIT"] = 9] = "sBIT";
_PngChunkTypes[_PngChunkTypes["tEXt"] = 10] = "tEXt";
_PngChunkTypes[_PngChunkTypes["tIME"] = 11] = "tIME";
_PngChunkTypes[_PngChunkTypes["tRNS"] = 12] = "tRNS";
_PngChunkTypes[_PngChunkTypes["zTXt"] = 13] = "zTXt";
_PngChunkTypes[_PngChunkTypes["sRGB"] = 14] = "sRGB";
_PngChunkTypes[_PngChunkTypes["iCCP"] = 15] = "iCCP";
_PngChunkTypes[_PngChunkTypes["iTXt"] = 16] = "iTXt";
_PngChunkTypes[_PngChunkTypes["unknown"] = 17] = "unknown";
})(_PngChunkTypes || (_PngChunkTypes = {}));
/**
* Enumerates the PNG scanline filter types applied to each row before compression.
* These filters improve Deflate efficiency by transforming pixel values:
* - `none` : No filtering applied.
* - `sub` : Uses the left pixel as predictor.
* - `up` : Uses the pixel above as predictor.
* - `average` : Averages the left and above pixels.
* - `paeth` : Applies the Paeth predictor using left, above, and upper-left.
*
* @private
*/
var _PngFilterTypes;
(function (_PngFilterTypes) {
_PngFilterTypes[_PngFilterTypes["none"] = 0] = "none";
_PngFilterTypes[_PngFilterTypes["sub"] = 1] = "sub";
_PngFilterTypes[_PngFilterTypes["up"] = 2] = "up";
_PngFilterTypes[_PngFilterTypes["average"] = 3] = "average";
_PngFilterTypes[_PngFilterTypes["paeth"] = 4] = "paeth";
})(_PngFilterTypes || (_PngFilterTypes = {}));