733 lines
22 KiB
JavaScript
733 lines
22 KiB
JavaScript
/** @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_',
|
|
supported_image_types = ['jpeg', 'jpg', 'png'];
|
|
|
|
// 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;
|
|
}
|
|
, generateAliasFromData = function(data) {
|
|
return typeof data === 'string' && jsPDFAPI.sHashCode(data);
|
|
}
|
|
, doesNotSupportImageType = function(type) {
|
|
return supported_image_types.indexOf(type) === -1;
|
|
}
|
|
, processMethodNotEnabled = function(type) {
|
|
return typeof jsPDFAPI['process' + type.toUpperCase()] !== 'function';
|
|
}
|
|
, isDOMElement = function(object) {
|
|
return typeof object === 'object' && object.nodeType === 1;
|
|
}
|
|
, createDataURIFromElement = function(element, format, angle) {
|
|
|
|
//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 (!angle && 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.');
|
|
}
|
|
if (angle) {
|
|
var x, y, b, c, s, w, h, to_radians = Math.PI/180, angleInRadians;
|
|
|
|
if (typeof angle === 'object') {
|
|
x = angle.x;
|
|
y = angle.y;
|
|
b = angle.bg;
|
|
angle = angle.angle;
|
|
}
|
|
angleInRadians = angle*to_radians;
|
|
c = Math.abs(Math.cos(angleInRadians));
|
|
s = Math.abs(Math.sin(angleInRadians));
|
|
w = canvas.width;
|
|
h = canvas.height;
|
|
canvas.width = h * s + w * c;
|
|
canvas.height = h * c + w * s;
|
|
|
|
if (isNaN(x)) x = canvas.width / 2;
|
|
if (isNaN(y)) y = canvas.height / 2;
|
|
|
|
ctx.clearRect(0,0,canvas.width, canvas.height);
|
|
ctx.fillStyle = b || 'white';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.rotate(angleInRadians);
|
|
ctx.drawImage(element, -(w/2), -(h/2));
|
|
ctx.rotate(-angleInRadians);
|
|
ctx.translate(-x, -y);
|
|
ctx.restore();
|
|
} else {
|
|
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) {
|
|
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;
|
|
|
|
this.internal.write(
|
|
'q'
|
|
, coord(w)
|
|
, '0 0'
|
|
, coord(h) // TODO: check if this should be shifted by vcoord
|
|
, coord(x)
|
|
, vcoord(y + h)
|
|
, 'cm /I'+info['i']
|
|
, 'Do Q'
|
|
)
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
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';
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
jsPDFAPI.supportsArrayBuffer = function() {
|
|
return typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined';
|
|
};
|
|
|
|
/**
|
|
* Tests supplied object to determine if ArrayBuffer
|
|
* @param {Object[object]}
|
|
*/
|
|
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 );
|
|
};
|
|
|
|
/**
|
|
* Exactly what it says on the tin
|
|
*/
|
|
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;
|
|
};
|
|
|
|
/**
|
|
* @see this discussion
|
|
* http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
|
|
*
|
|
* As stated, i imagine the method below is highly inefficent for large files.
|
|
*
|
|
* Also of note from Mozilla,
|
|
*
|
|
* "However, this is slow and error-prone, due to the need for multiple conversions (especially if the binary data is not actually byte-format data, but, for example, 32-bit integers or floats)."
|
|
*
|
|
* https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView
|
|
*
|
|
* Although i'm strugglig to see how StringView solves this issue? Doesn't appear to be a direct method for conversion?
|
|
*
|
|
* Async method using Blob and FileReader could be best, but i'm not sure how to fit it into the flow?
|
|
*/
|
|
jsPDFAPI.arrayBufferToBinaryString = function(buffer) {
|
|
/*if('TextDecoder' in window){
|
|
var decoder = new TextDecoder('ascii');
|
|
return decoder.decode(buffer);
|
|
}*/
|
|
|
|
if(this.isArrayBuffer(buffer))
|
|
buffer = new Uint8Array(buffer);
|
|
|
|
var binary_string = '';
|
|
var len = buffer.byteLength;
|
|
for (var i = 0; i < len; i++) {
|
|
binary_string += String.fromCharCode(buffer[i]);
|
|
}
|
|
return binary_string;
|
|
/*
|
|
* Another solution is the method below - convert array buffer straight to base64 and then use atob
|
|
*/
|
|
//return atob(this.arrayBufferToBase64(buffer));
|
|
};
|
|
|
|
/**
|
|
* Converts an ArrayBuffer directly to base64
|
|
*
|
|
* Taken from here
|
|
*
|
|
* http://jsperf.com/encoding-xhr-image-data/31
|
|
*
|
|
* Need to test if this is a better solution for larger files
|
|
*
|
|
*/
|
|
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
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias, compression, rotation) {
|
|
'use strict'
|
|
|
|
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, rotation);
|
|
|
|
if(notDefined(alias))
|
|
alias = generateAliasFromData(imageData);
|
|
|
|
if (!(info = checkImagesForAlias(alias, images))) {
|
|
|
|
if(this.isString(imageData)) {
|
|
|
|
var base64Info = this.extractInfoFromBase64DataURI(imageData);
|
|
|
|
if(base64Info) {
|
|
|
|
format = base64Info[2];
|
|
imageData = atob(base64Info[3]);//convert to binary string
|
|
|
|
} else {
|
|
|
|
if (imageData.charCodeAt(0) === 0x89 &&
|
|
imageData.charCodeAt(1) === 0x50 &&
|
|
imageData.charCodeAt(2) === 0x4e &&
|
|
imageData.charCodeAt(3) === 0x47 ) format = 'png';
|
|
}
|
|
}
|
|
format = (format || 'JPEG').toLowerCase();
|
|
|
|
if(doesNotSupportImageType(format))
|
|
throw new Error('addImage currently only supports formats ' + supported_image_types + ', not \''+format+'\'');
|
|
|
|
if(processMethodNotEnabled(format))
|
|
throw new Error('please ensure that the 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);
|
|
|
|
return this
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
'use strict'
|
|
var colorSpace = this.color_spaces.DEVICE_RGB,
|
|
filter = this.decode.DCT_DECODE,
|
|
bpc = 8,
|
|
dims;
|
|
|
|
if(this.isString(data)) {
|
|
dims = getJpegSize(data);
|
|
return this.createImageInfo(data, dims[0], dims[1], dims[3] == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias);
|
|
}
|
|
|
|
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);
|
|
|
|
return this.createImageInfo(data, dims.width, dims.height, dims.numcomponents == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
jsPDFAPI.processJPG = function(/*data, index, alias, compression, dataAsBinaryString*/) {
|
|
return this.processJPEG.apply(this, arguments);
|
|
}
|
|
|
|
})(jsPDF.API);
|