/** @preserve jsPDF addImage plugin (JPEG only at this time) Copyright (c) 2012 https://github.com/siefkenj/ */ /** * 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_' // 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; // 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 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) { height = imgData.charCodeAt(i+5)*256 + imgData.charCodeAt(i+6); width = imgData.charCodeAt(i+7)*256 + imgData.charCodeAt(i+8); return [width, height]; } else { i += 2; blockLength = imgData.charCodeAt(i)*256 + imgData.charCodeAt(i+1) } } } // Image functionality ported from pdf.js , putImage = function(img) { var objectNumber = this.internal.newObject() , out = this.internal.write , putStream = this.internal.putStream img['n'] = objectNumber out('<>'); } if ('trns' in img && img['trns'].constructor == Array) { var trns = ''; for ( var i = 0; i < img['trns'].length; 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'); } , 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' ) } } jsPDFAPI.addImage = function(imageData, format, x, y, w, h) { 'use strict' if (typeof imageData === 'object' && imageData.nodeType === 1) { var canvas = document.createElement('canvas'); canvas.width = imageData.clientWidth; canvas.height = imageData.clientHeight; var ctx = canvas.getContext('2d'); if (!ctx) { throw ('addImage requires canvas to be supported by browser.'); } ctx.drawImage(imageData, 0, 0, canvas.width, canvas.height); imageData = canvas.toDataURL('image/jpeg'); format = "JPEG"; } if (format.toUpperCase() !== 'JPEG') { throw new Error('addImage currently only supports format \'JPEG\', not \''+format+'\''); } var imageIndex , images = this.internal.collections[namespace + 'images'] , coord = this.internal.getCoordinateString , vcoord = this.internal.getVerticalCoordinateString; // Detect if the imageData is raw binary or Data URL if (imageData.substring(0, 23) === 'data:image/jpeg;base64,') { imageData = atob(imageData.replace('data:image/jpeg;base64,', '')); } 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) } else { // this is the first time this method is ran on this instance of jsPDF object. imageIndex = 0 this.internal.collections[namespace + 'images'] = images = {} this.internal.events.subscribe('putResources', putResourcesCallback) this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback) } var dims = getJpegSize(imageData); var info = { w : dims[0], h : dims[1], cs : 'DeviceRGB', bpc : 8, f : 'DCTDecode', i : imageIndex, data : imageData // n: objectNumber will be added by putImage code }; images[imageIndex] = 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']; } 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' ) return this } })(jsPDF.API)