pdf-lib
Version:
Library for creating and modifying PDF files in JavaScript
118 lines (117 loc) • 5.97 kB
JavaScript
import pako from 'pako';
import PNG from 'png-ts';
import { PDFArray, PDFDictionary, PDFName, PDFNumber, PDFRawStream, } from '../../pdf-objects';
import { isInstance, validate } from '../../../utils/validate';
/**
* A note of thanks to the developers of https://github.com/devongovett/pdfkit,
* as this class borrows heavily from:
* https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/png.coffee
*/
var PNGXObjectFactory = /** @class */ (function () {
function PNGXObjectFactory(data) {
var _this = this;
this.embedImageIn = function (document) {
_this.document = document;
_this.xObjDict = PDFDictionary.from({
Type: PDFName.from('XObject'),
Subtype: PDFName.from('Image'),
BitsPerComponent: PDFNumber.fromNumber(_this.image.bits),
Width: PDFNumber.fromNumber(_this.width),
Height: PDFNumber.fromNumber(_this.height),
Filter: PDFName.from('FlateDecode'),
}, document.index);
if (!_this.image.hasAlphaChannel) {
var params = PDFDictionary.from({
Predictor: PDFNumber.fromNumber(15),
Colors: PDFNumber.fromNumber(_this.image.colors),
BitsPerComponent: PDFNumber.fromNumber(_this.image.bits),
Columns: PDFNumber.fromNumber(_this.width),
}, document.index);
_this.xObjDict.set('DecodeParms', params);
}
if (_this.image.palette.length === 0) {
_this.xObjDict.set('ColorSpace', PDFName.from(_this.image.colorSpace));
}
else {
var paletteStream = document.register(PDFRawStream.from(PDFDictionary.from({
Length: PDFNumber.fromNumber(_this.image.palette.length),
}, document.index), new Uint8Array(_this.image.palette)));
_this.xObjDict.set('ColorSpace', PDFArray.fromArray([
PDFName.from('Indexed'),
PDFName.from('DeviceRGB'),
PDFNumber.fromNumber(_this.image.palette.length / 3 - 1),
paletteStream,
], document.index));
}
// TODO: Handle the following two transparency types. They don't seem to be
// fully handled in:
// https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/png.coffee
// if (this.image.transparency.grayscale)
// if (this.image.transparency.rgb)
// prettier-ignore
return (_this.image.transparency.indexed ? _this.loadIndexedAlphaChannel()
: _this.image.hasAlphaChannel ? _this.splitAlphaChannel()
: _this.finalize());
};
/** @hidden */
this.finalize = function () {
if (_this.alphaChannel) {
var deflatedAlphaChannel = pako.deflate(_this.alphaChannel);
var alphaStreamDict = PDFDictionary.from({
Type: PDFName.from('XObject'),
Subtype: PDFName.from('Image'),
Height: PDFNumber.fromNumber(_this.height),
Width: PDFNumber.fromNumber(_this.width),
BitsPerComponent: PDFNumber.fromNumber(8),
Filter: PDFName.from('FlateDecode'),
ColorSpace: PDFName.from('DeviceGray'),
Decode: PDFArray.fromArray([PDFNumber.fromNumber(0), PDFNumber.fromNumber(1)], _this.document.index),
Length: PDFNumber.fromNumber(deflatedAlphaChannel.length),
}, _this.document.index);
var smaskStream = _this.document.register(PDFRawStream.from(alphaStreamDict, deflatedAlphaChannel));
_this.xObjDict.set('SMask', smaskStream);
}
_this.xObjDict.set('Length', PDFNumber.fromNumber(_this.imgData.length));
var xObj = _this.document.register(PDFRawStream.from(_this.xObjDict, _this.imgData));
return xObj;
};
/** @hidden */
this.splitAlphaChannel = function () {
var pixels = _this.image.decodePixels();
var colorByteSize = (_this.image.colors * _this.image.bits) / 8;
var pixelCount = _this.image.width * _this.image.height;
_this.imgData = new Uint8Array(pixelCount * colorByteSize);
_this.alphaChannel = new Uint8Array(pixelCount);
var i = 0;
var p = 0;
var a = 0;
while (i < pixels.length) {
_this.imgData[p++] = pixels[i++];
_this.imgData[p++] = pixels[i++];
_this.imgData[p++] = pixels[i++];
_this.alphaChannel[a++] = pixels[i++];
}
_this.imgData = pako.deflate(_this.imgData);
return _this.finalize();
};
/** @hidden */
this.loadIndexedAlphaChannel = function () {
var transparency = _this.image.transparency.indexed;
var pixels = _this.image.decodePixels();
_this.alphaChannel = new Uint8Array(_this.width * _this.height);
// Can't use forEach here, because it's missing on React Native Android
for (var idx = 0; idx < pixels.length; idx++) {
_this.alphaChannel[idx] = transparency[pixels[idx]];
}
return _this.finalize();
};
validate(data, isInstance(Uint8Array), '"data" must be an instance of Uint8Array');
this.image = PNG.load(data);
this.width = this.image.width;
this.height = this.image.height;
this.imgData = this.image.imgData;
}
PNGXObjectFactory.for = function (data) { return new PNGXObjectFactory(data); };
return PNGXObjectFactory;
}());
export default PNGXObjectFactory;