/** @preserve
* jsPDF addImage plugin
* Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/
* 2013 Chris Dowling, https://github.com/gingerchris
* 2013 Trinh Ho, https://github.com/ineedfat
* 2013 Edwin Alejandro Perez, https://github.com/eaparango
* 2013 Norah Smith, https://github.com/burnburnrocket
* 2014 Diego Casorran, https://github.com/diegocr
* 2014 James Robb, https://github.com/jamesbrobb
*
* 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.
*/
(function(jsPDFAPI) {
"use strict";
var namespace = "addImage_";
var imageFileTypeHeaders = {
PNG: [[0x89, 0x50, 0x4e, 0x47]],
TIFF: [
[0x4d, 0x4d, 0x00, 0x2a], //Motorola
[0x49, 0x49, 0x2a, 0x00] //Intel
],
JPEG: [
[0xff, 0xd8, 0xff, 0xe0, undefined, undefined, 0x4a, 0x46, 0x49, 0x46, 0x00], //JFIF
[0xff, 0xd8, 0xff, 0xe1, undefined, undefined, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00] //Exif
],
JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]],
GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]],
GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]],
BMP: [
[0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc.
[0x42, 0x41], //BA - OS/2 struct bitmap array
[0x43, 0x49], //CI - OS/2 struct color icon
[0x43, 0x50], //CP - OS/2 const color pointer
[0x49, 0x43], //IC - OS/2 struct icon
[0x50, 0x54] //PT - OS/2 pointer
]
};
/**
* Recognize filetype of Image by magic-bytes
*
* https://en.wikipedia.org/wiki/List_of_file_signatures
*
* @name getImageFileTypeByImageData
* @public
* @function
* @param {String} imageData as base64 encoded DataUrl
* @param {String} format of file if filetype-recognition fails, e.g. 'JPEG'
*
* @returns {String} filetype of Image
* @methodOf jsPDF#
*/
jsPDFAPI.getImageFileTypeByImageData = function(imageData, fallbackFormat) {
fallbackFormat = fallbackFormat || "UNKNOWN";
var i;
var j;
var result = "UNKNOWN";
var headerSchemata;
var compareResult;
var fileType;
for (fileType in imageFileTypeHeaders) {
headerSchemata = imageFileTypeHeaders[fileType];
for (i = 0; i < headerSchemata.length; i += 1) {
compareResult = true;
for (j = 0; j < headerSchemata[i].length; j += 1) {
if (headerSchemata[i][j] === undefined) {
continue;
}
if (headerSchemata[i][j] !== imageData.charCodeAt(j)) {
compareResult = false;
break;
}
}
if (compareResult === true) {
result = fileType;
break;
}
}
}
if (result === "UNKOWN" && fallbackFormat !== "UNKNOWN") {
console.warn('FileType of Image not recognized. Processing image as "' + fallbackFormat + '".');
result = fallbackFormat;
}
return result;
};
// Image functionality ported from pdf.js
var putImage = function(img) {
var objectNumber = this.internal.newObject(),
out = this.internal.write,
putStream = this.internal.putStream;
img["n"] = objectNumber;
out("<</Type /XObject");
out("/Subtype /Image");
out("/Width " + img["w"]);
out("/Height " + img["h"]);
if (img["cs"] === this.color_spaces.INDEXED) {
out(
"/ColorSpace [/Indexed /DeviceRGB " +
// if an indexed png defines more than one colour with transparency, we've created a smask
(img["pal"].length / 3 - 1) +
" " +
("smask" in img ? objectNumber + 2 : objectNumber + 1) +
" 0 R]"
);
} else {
out("/ColorSpace /" + img["cs"]);
if (img["cs"] === this.color_spaces.DEVICE_CMYK) {
out("/Decode [1 0 1 0 1 0 1 0]");
}
}
out("/BitsPerComponent " + img["bpc"]);
if ("f" in img) {
out("/Filter /" + img["f"]);
}
if ("dp" in img) {
out("/DecodeParms <<" + img["dp"] + ">>");
}
if ("trns" in img && img["trns"].constructor == Array) {
var trns = "",
i = 0,
len = img["trns"].length;
for (; i < len; i++) trns += img["trns"][i] + " " + img["trns"][i] + " ";
out("/Mask [" + trns + "]");
}
if ("smask" in img) {
out("/SMask " + (objectNumber + 1) + " 0 R");
}
out("/Length " + img["data"].length + ">>");
putStream(img["data"]);
out("endobj");
// Soft mask
if ("smask" in img) {
var dp = "/Predictor " + img["p"] + " /Colors 1 /BitsPerComponent " + img["bpc"] + " /Columns " + img["w"];
var smask = {
w: img["w"],
h: img["h"],
cs: "DeviceGray",
bpc: img["bpc"],
dp: dp,
data: img["smask"]
};
if ("f" in img) smask.f = img["f"];
putImage.call(this, smask);
}
//Palette
if (img["cs"] === this.color_spaces.INDEXED) {
this.internal.newObject();
//out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
//putStream(zlib.compress(img['pal']));
out("<< /Length " + img["pal"].length + ">>");
putStream(this.arrayBufferToBinaryString(new Uint8Array(img["pal"])));
out("endobj");
}
},
putResourcesCallback = function() {
var images = this.internal.collections[namespace + "images"];
for (var i in images) {
putImage.call(this, images[i]);
}
},
putXObjectsDictCallback = function() {
var images = this.internal.collections[namespace + "images"],
out = this.internal.write,
image;
for (var i in images) {
image = images[i];
out("/I" + image["i"], image["n"], "0", "R");
}
},
checkCompressValue = function(value) {
if (value && typeof value === "string") value = value.toUpperCase();
return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE;
},
getImages = function() {
var images = this.internal.collections[namespace + "images"];
//first run, so initialise stuff
if (!images) {
this.internal.collections[namespace + "images"] = images = {};
this.internal.events.subscribe("putResources", putResourcesCallback);
this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback);
}
return images;
},
getImageIndex = function(images) {
var imageIndex = 0;
if (images) {
// this is NOT the first time this method is ran on this instance of jsPDF object.
imageIndex = Object.keys
? Object.keys(images).length
: (function(o) {
var i = 0;
for (var e in o) {
if (o.hasOwnProperty(e)) {
i++;
}
}
return i;
})(images);
}
return imageIndex;
},
notDefined = function(value) {
return typeof value === "undefined" || value === null || value.length === 0;
},
generateAliasFromData = function(data) {
return typeof data === "string" && jsPDFAPI.sHashCode(data);
},
isImageTypeSupported = function(type) {
return typeof jsPDFAPI["process" + type.toUpperCase()] === "function";
},
isDOMElement = function(object) {
return typeof object === "object" && object.nodeType === 1;
},
createDataURIFromElement = function(element, format) {
//if element is an image which uses data url definition, just return the dataurl
if (element.nodeName === "IMG" && element.hasAttribute("src")) {
var src = "" + element.getAttribute("src");
if (src.indexOf("data:image/") === 0) return src;
// only if the user doesn't care about a format
if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = "png";
}
if (element.nodeName === "CANVAS") {
var canvas = element;
} else {
var canvas = document.createElement("canvas");
canvas.width = element.clientWidth || element.width;
canvas.height = element.clientHeight || element.height;
var ctx = canvas.getContext("2d");
if (!ctx) {
throw "addImage requires canvas to be supported by browser.";
}
ctx.drawImage(element, 0, 0, canvas.width, canvas.height);
}
return canvas.toDataURL(("" + format).toLowerCase() == "png" ? "image/png" : "image/jpeg");
},
checkImagesForAlias = function(alias, images) {
var cached_info;
if (images) {
for (var e in images) {
if (alias === images[e].alias) {
cached_info = images[e];
break;
}
}
}
return cached_info;
},
determineWidthAndHeight = function(w, h, info) {
if (!w && !h) {
w = -96;
h = -96;
}
if (w < 0) {
w = (-1 * info["w"] * 72) / w / this.internal.scaleFactor;
}
if (h < 0) {
h = (-1 * info["h"] * 72) / h / this.internal.scaleFactor;
}
if (w === 0) {
w = (h * info["w"]) / info["h"];
}
if (h === 0) {
h = (w * info["h"]) / info["w"];
}
return [w, h];
},
writeImageToPDF = function(x, y, w, h, info, index, images, rotation) {
var dims = determineWidthAndHeight.call(this, w, h, info),
coord = this.internal.getCoordinateString,
vcoord = this.internal.getVerticalCoordinateString;
w = dims[0];
h = dims[1];
images[index] = info;
if (rotation) {
rotation *= Math.PI / 180;
var c = Math.cos(rotation);
var s = Math.sin(rotation);
//like in pdf Reference do it 4 digits instead of 2
var f4 = function(number) {
return number.toFixed(4);
};
var rotationTransformationMatrix = [f4(c), f4(s), f4(s * -1), f4(c), 0, 0, "cm"];
}
this.internal.write("q"); //Save graphics state
if (rotation) {
this.internal.write([1, "0", "0", 1, coord(x), vcoord(y + h), "cm"].join(" ")); //Translate
this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate
this.internal.write([coord(w), "0", "0", coord(h), "0", "0", "cm"].join(" ")); //Scale
} else {
this.internal.write([coord(w), "0", "0", coord(h), coord(x), vcoord(y + h), "cm"].join(" ")); //Translate and Scale
}
if (this.isAdvancedAPI()) {
// draw image bottom up when in "advanced" API mode
this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" "));
}
this.internal.write("/I" + info["i"] + " Do"); //Paint Image
this.internal.write("Q"); //Restore graphics state
};
/**
* COLOR SPACES
*/
jsPDFAPI.color_spaces = {
DEVICE_RGB: "DeviceRGB",
DEVICE_GRAY: "DeviceGray",
DEVICE_CMYK: "DeviceCMYK",
CAL_GREY: "CalGray",
CAL_RGB: "CalRGB",
LAB: "Lab",
ICC_BASED: "ICCBased",
INDEXED: "Indexed",
PATTERN: "Pattern",
SEPARATION: "Separation",
DEVICE_N: "DeviceN"
};
/**
* DECODE METHODS
*/
jsPDFAPI.decode = {
DCT_DECODE: "DCTDecode",
FLATE_DECODE: "FlateDecode",
LZW_DECODE: "LZWDecode",
JPX_DECODE: "JPXDecode",
JBIG2_DECODE: "JBIG2Decode",
ASCII85_DECODE: "ASCII85Decode",
ASCII_HEX_DECODE: "ASCIIHexDecode",
RUN_LENGTH_DECODE: "RunLengthDecode",
CCITT_FAX_DECODE: "CCITTFaxDecode"
};
/**
* IMAGE COMPRESSION TYPES
*/
jsPDFAPI.image_compression = {
NONE: "NONE",
FAST: "FAST",
MEDIUM: "MEDIUM",
SLOW: "SLOW"
};
jsPDFAPI.sHashCode = function(str) {
str = str || "";
return (
Array.prototype.reduce &&
str.split("").reduce(function(a, b) {
a = (a << 5) - a + b.charCodeAt(0);
return a & a;
}, 0)
);
};
jsPDFAPI.isString = function(object) {
return typeof object === "string";
};
/**
* Validates if given String is a valid Base64-String
*
* @name validateStringAsBase64
* @public
* @function
* @param {String} possible Base64-String
*
* @returns {boolean}
* @methodOf jsPDF#
*/
jsPDFAPI.validateStringAsBase64 = function(possibleBase64String) {
possibleBase64String = possibleBase64String || "";
var result = true;
if (possibleBase64String.length % 4 !== 0) {
result = false;
}
if (/[A-Za-z0-9\/]+/.test(possibleBase64String.substr(0, possibleBase64String.length - 2)) === false) {
result = false;
}
if (/[A-Za-z0-9\/][A-Za-z0-9+\/]|[A-Za-z0-9+\/]=|==/.test(possibleBase64String.substr(-2)) === false) {
result = false;
}
return result;
};
/**
* Strips out and returns info from a valid base64 data URI
* @param {String[dataURI]} a valid data URI of format 'data:[<MIME-type>][;base64],<data>'
* @returns an Array containing the following
* [0] the complete data URI
* [1] <MIME-type>
* [2] format - the second part of the mime-type i.e 'png' in 'image/png'
* [4] <data>
*/
jsPDFAPI.extractInfoFromBase64DataURI = function(dataURI) {
return /^data:([\w]+?\/([\w]+?));base64,(.+)$/g.exec(dataURI);
};
/**
* Check to see if ArrayBuffer is supported
*
* @returns {boolean}
* @methodOf jsPDF#
*/
jsPDFAPI.supportsArrayBuffer = function() {
return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
};
/**
* Tests supplied object to determine if ArrayBuffer
* @param {Object[object]}
*
* @returns {boolean}
* @methodOf jsPDF#
*/
jsPDFAPI.isArrayBuffer = function(object) {
if (!this.supportsArrayBuffer()) return false;
return object instanceof ArrayBuffer;
};
/**
* Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface
* @param {Object[object]}
*/
jsPDFAPI.isArrayBufferView = function(object) {
if (!this.supportsArrayBuffer()) return false;
if (typeof Uint32Array === "undefined") return false;
return (
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
);
};
/**
* Convert the Buffer to a Binary String
*
* @name binaryStringToUint8Array
* @public
* @function
* @param {ArrayBuffer} BinaryString with ImageData
*
* @returns {Uint8Array}
*/
jsPDFAPI.binaryStringToUint8Array = function(binary_string) {
/*
* not sure how efficient this will be will bigger files. Is there a native method?
*/
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes;
};
/**
* Convert the Buffer to a Binary String
*
* @name arrayBufferToBinaryString
* @public
* @function
* @param {ArrayBuffer} ArrayBuffer with ImageData
*
* @returns {String}
*/
jsPDFAPI.arrayBufferToBinaryString = function(buffer) {
if (typeof atob === "function") {
return atob(this.arrayBufferToBase64(buffer));
}
if (typeof TextDecoder === "function") {
var decoder = new TextDecoder("ascii");
// test if the encoding is supported
if (decoder.encoding === "ascii") {
return decoder.decode(buffer);
}
}
//Fallback-solution
var data = this.isArrayBuffer(buffer) ? buffer : new Uint8Array(buffer);
var chunkSizeForSlice = 0x5000;
var binary_string = "";
var slicesCount = Math.ceil(data.byteLength / chunkSizeForSlice);
for (var i = 0; i < slicesCount; i++) {
binary_string += String.fromCharCode.apply(
null,
data.slice(i * chunkSizeForSlice, i * chunkSizeForSlice + chunkSizeForSlice)
);
}
return binary_string;
};
/**
* Converts an ArrayBuffer directly to base64
*
* Taken from http://jsperf.com/encoding-xhr-image-data/31
*
* Need to test if this is a better solution for larger files
*
* @name arrayBufferToBase64
* @public
* @function
*
* @returns {String}
*/
jsPDFAPI.arrayBufferToBase64 = function(arrayBuffer) {
var base64 = "";
var encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var bytes = new Uint8Array(arrayBuffer);
var byteLength = bytes.byteLength;
var byteRemainder = byteLength % 3;
var mainLength = byteLength - byteRemainder;
var a, b, c, d;
var chunk;
// Main loop deals with bytes in chunks of 3
for (var i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
d = chunk & 63; // 63 = 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
}
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength];
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + "==";
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + "=";
}
return base64;
};
/**
* Converts an ArrayBuffer directly to base64
*
* Taken from http://jsperf.com/encoding-xhr-image-data/31
*
* Need to test if this is a better solution for larger files
*
* @public
* @function
*
* @returns {String}
*/
jsPDFAPI.createImageInfo = function(data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask, p) {
var info = {
alias: alias,
w: wd,
h: ht,
cs: cs,
bpc: bpc,
i: imageIndex,
data: data
// n: objectNumber will be added by putImage code
};
if (f) info.f = f;
if (dp) info.dp = dp;
if (trns) info.trns = trns;
if (pal) info.pal = pal;
if (smask) info.smask = smask;
if (p) info.p = p; // predictor parameter for PNG compression
return info;
};
/**
* Adds an Image to the PDF.
*
* @name addImage
* @public
* @function
* @param {String/Image-Element/Canvas-Element/Uint8Array} imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement
* @param {String} format of file if filetype-recognition fails, e.g. 'JPEG'
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} width of the image (in units declared at inception of PDF document)
* @param {Number} height of the Image (in units declared at inception of PDF document)
* @param {String} alias of the image (if used multiple times)
* @param {String} compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW'
* @param {Number} rotation of the image in degrees (0-359)
*
* @returns jsPDF
* @methodOf jsPDF#
*/
jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias, compression, rotation) {
"use strict";
var tmpImageData = "";
if (typeof format !== "string") {
var tmp = h;
h = w;
w = y;
y = x;
x = format;
format = tmp;
}
if (typeof imageData === "object" && !isDOMElement(imageData) && "imageData" in imageData) {
var options = imageData;
imageData = options.imageData;
format = options.format || format;
x = options.x || x || 0;
y = options.y || y || 0;
w = options.w || w;
h = options.h || h;
alias = options.alias || alias;
compression = options.compression || compression;
rotation = options.rotation || options.angle || rotation;
}
if (isNaN(x) || isNaN(y)) {
console.error("jsPDF.addImage: Invalid coordinates", arguments);
throw new Error("Invalid coordinates passed to jsPDF.addImage");
}
var images = getImages.call(this),
info;
if (!(info = checkImagesForAlias(imageData, images))) {
var dataAsBinaryString;
if (isDOMElement(imageData)) imageData = createDataURIFromElement(imageData, format);
if (notDefined(alias)) alias = generateAliasFromData(imageData);
if (!(info = checkImagesForAlias(alias, images))) {
if (this.isString(imageData)) {
tmpImageData = this.convertStringToImageData(imageData);
if (tmpImageData !== "") {
imageData = tmpImageData;
} else {
tmpImageData = this.loadImageFile(imageData);
if (tmpImageData !== undefined) {
imageData = tmpImageData;
}
}
}
format = this.getImageFileTypeByImageData(imageData, format);
if (!isImageTypeSupported(format))
throw new Error(
"addImage does not support files of type '" +
format +
"', please ensure that a plugin for '" +
format +
"' support is added."
);
/**
* need to test if it's more efficient to convert all binary strings
* to TypedArray - or should we just leave and process as string?
*/
if (this.supportsArrayBuffer()) {
// no need to convert if imageData is already uint8array
if (!(imageData instanceof Uint8Array)) {
dataAsBinaryString = imageData;
imageData = this.binaryStringToUint8Array(imageData);
}
}
info = this["process" + format.toUpperCase()](
imageData,
getImageIndex(images),
alias,
checkCompressValue(compression),
dataAsBinaryString
);
if (!info) throw new Error("An unkwown error occurred whilst processing the image");
}
}
writeImageToPDF.call(this, x, y, w, h, info, info.i, images, rotation);
return this;
};
jsPDFAPI.convertStringToImageData = function(stringData) {
var base64Info;
var imageData = "";
if (this.isString(stringData)) {
var base64Info = this.extractInfoFromBase64DataURI(stringData);
if (base64Info !== null) {
if (jsPDFAPI.validateStringAsBase64(base64Info[3])) {
imageData = atob(base64Info[3]); //convert to binary string
}
} else if (jsPDFAPI.validateStringAsBase64(stringData)) {
imageData = atob(stringData);
}
}
return imageData;
};
/**
* JPEG SUPPORT
**/
//takes a string imgData containing the raw bytes of
//a jpeg image and returns [width, height]
//Algorithm from: http://www.64lines.com/jpeg-width-height
var getJpegSize = function(imgData) {
"use strict";
var width, height, numcomponents;
// Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00
if (
!imgData.charCodeAt(0) === 0xff ||
!imgData.charCodeAt(1) === 0xd8 ||
!imgData.charCodeAt(2) === 0xff ||
!imgData.charCodeAt(3) === 0xe0 ||
!imgData.charCodeAt(6) === "J".charCodeAt(0) ||
!imgData.charCodeAt(7) === "F".charCodeAt(0) ||
!imgData.charCodeAt(8) === "I".charCodeAt(0) ||
!imgData.charCodeAt(9) === "F".charCodeAt(0) ||
!imgData.charCodeAt(10) === 0x00
) {
throw new Error("getJpegSize requires a binary string jpeg file");
}
var blockLength = imgData.charCodeAt(4) * 256 + imgData.charCodeAt(5);
var i = 4,
len = imgData.length;
while (i < len) {
i += blockLength;
if (imgData.charCodeAt(i) !== 0xff) {
throw new Error("getJpegSize could not find the size of the image");
}
if (
imgData.charCodeAt(i + 1) === 0xc0 || //(SOF) Huffman - Baseline DCT
imgData.charCodeAt(i + 1) === 0xc1 || //(SOF) Huffman - Extended sequential DCT
imgData.charCodeAt(i + 1) === 0xc2 || // Progressive DCT (SOF2)
imgData.charCodeAt(i + 1) === 0xc3 || // Spatial (sequential) lossless (SOF3)
imgData.charCodeAt(i + 1) === 0xc4 || // Differential sequential DCT (SOF5)
imgData.charCodeAt(i + 1) === 0xc5 || // Differential progressive DCT (SOF6)
imgData.charCodeAt(i + 1) === 0xc6 || // Differential spatial (SOF7)
imgData.charCodeAt(i + 1) === 0xc7
) {
height = imgData.charCodeAt(i + 5) * 256 + imgData.charCodeAt(i + 6);
width = imgData.charCodeAt(i + 7) * 256 + imgData.charCodeAt(i + 8);
numcomponents = imgData.charCodeAt(i + 9);
return [width, height, numcomponents];
} else {
i += 2;
blockLength = imgData.charCodeAt(i) * 256 + imgData.charCodeAt(i + 1);
}
}
},
getJpegSizeFromBytes = function(data) {
var hdr = (data[0] << 8) | data[1];
if (hdr !== 0xffd8) throw new Error("Supplied data is not a JPEG");
var len = data.length,
block = (data[4] << 8) + data[5],
pos = 4,
bytes,
width,
height,
numcomponents;
while (pos < len) {
pos += block;
bytes = readBytes(data, pos);
block = (bytes[2] << 8) + bytes[3];
if ((bytes[1] === 0xc0 || bytes[1] === 0xc2) && bytes[0] === 0xff && block > 7) {
bytes = readBytes(data, pos + 5);
width = (bytes[2] << 8) + bytes[3];
height = (bytes[0] << 8) + bytes[1];
numcomponents = bytes[4];
return { width: width, height: height, numcomponents: numcomponents };
}
pos += 2;
}
throw new Error("getJpegSizeFromBytes could not find the size of the image");
},
readBytes = function(data, offset) {
return data.subarray(offset, offset + 5);
};
jsPDFAPI.processJPEG = function(data, index, alias, compression, dataAsBinaryString, colorSpace) {
"use strict";
var filter = this.decode.DCT_DECODE,
bpc = 8,
dims;
if (!this.isString(data) && !this.isArrayBuffer(data) && !this.isArrayBufferView(data)) {
return null;
}
if (this.isString(data)) {
dims = getJpegSize(data);
}
if (this.isArrayBuffer(data)) {
data = new Uint8Array(data);
}
if (this.isArrayBufferView(data)) {
dims = getJpegSizeFromBytes(data);
// if we already have a stored binary string rep use that
data = dataAsBinaryString || this.arrayBufferToBinaryString(data);
}
if (colorSpace === undefined) {
switch (dims.numcomponents) {
case 1:
colorSpace = this.color_spaces.DEVICE_GRAY;
break;
case 4:
colorSpace = this.color_spaces.DEVICE_CMYK;
break;
default:
case 3:
colorSpace = this.color_spaces.DEVICE_RGB;
break;
}
}
return this.createImageInfo(data, dims.width, dims.height, colorSpace, bpc, filter, index, alias);
};
jsPDFAPI.processJPG = function(/*data, index, alias, compression, dataAsBinaryString*/) {
return this.processJPEG.apply(this, arguments);
};
jsPDFAPI.loadImageFile = function(path, sync, callback) {
sync = sync || true;
callback = callback || function() {};
var isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
var xhrMethod = function(url, sync, callback) {
var req = new XMLHttpRequest();
var byteArray = [];
var i = 0;
var sanitizeUnicode = function(data) {
var dataLength = data.length;
var StringFromCharCode = String.fromCharCode;
//Transform Unicode to ASCII
for (i = 0; i < dataLength; i += 1) {
byteArray.push(StringFromCharCode(data.charCodeAt(i) & 0xff));
}
return byteArray.join("");
};
req.open("GET", url, !sync);
// XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
req.overrideMimeType("text/plain; charset=x-user-defined");
if (sync === false) {
req.onload = function() {
return sanitizeUnicode(this.responseText);
};
}
req.send(null);
if (req.status !== 200) {
console.warn('Unable to load file "' + url + '"');
return;
}
if (sync) {
return sanitizeUnicode(req.responseText);
}
};
//we have a browser and probably no CORS-Problem
if (typeof window !== undefined && typeof location === "object" && location.protocol.substr(0, 4) === "http") {
return xhrMethod(path, sync, callback);
}
};
jsPDFAPI.getImageProperties = function(imageData) {
var info;
var tmpImageData = "";
var format;
var dataAsBinaryString;
if (isDOMElement(imageData)) {
imageData = createDataURIFromElement(imageData);
}
if (this.isString(imageData)) {
tmpImageData = this.convertStringToImageData(imageData);
if (tmpImageData !== "") {
imageData = tmpImageData;
} else {
tmpImageData = this.loadImageFile(imageData);
if (tmpImageData !== undefined) {
imageData = tmpImageData;
}
}
}
format = this.getImageFileTypeByImageData(imageData);
if (!isImageTypeSupported(format))
throw new Error(
"addImage does not support files of type '" +
format +
"', please ensure that a plugin for '" +
format +
"' support is added."
);
/**
* need to test if it's more efficient to convert all binary strings
* to TypedArray - or should we just leave and process as string?
*/
if (this.supportsArrayBuffer()) {
// no need to convert if imageData is already uint8array
if (!(imageData instanceof Uint8Array)) {
dataAsBinaryString = imageData;
imageData = this.binaryStringToUint8Array(imageData);
}
}
info = this["process" + format.toUpperCase()](imageData);
if (!info) {
throw new Error("An unkwown error occurred whilst processing the image");
}
return {
fileType: format,
width: info.w,
height: info.h,
colorSpace: info.cs,
compressionMode: info.f,
bitsPerComponent: info.bpc
};
};
})(jsPDF.API);