text: /** @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 15 /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) {
  		// TODO: Alias dynamic generation from imageData's checksum/hash
  		return undefined;
  	}
  	, 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) {
  
  		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 == 'png' ? 'image/png' : 'image/jpeg');
  	}
  	,checkImagesForAlias = function(imageData, images) {
  		var cached_info;
  		if(images) {
  			for(var e in images) {
  				if(imageData === 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',
  		SEPERATION:'Seperation',
  		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.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(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) {
  		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;
  
  		return info;
  	};
  
  	jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias, compression) {
  		'use strict'
  
  		if(typeof format === 'number') {
  			var tmp = h;
  			h = w;
  			w = y;
  			y = x;
  			x = format;
  			format = tmp;
  		}
  
  		var images = getImages.call(this),//initalises internals and events on first run
  			cached_info,
  			dataAsBinaryString;
  
  		compression = checkCompressValue(compression);
  		format = (format || 'JPEG').toLowerCase();
  
  		if(notDefined(alias))
  			alias = generateAliasFromData(imageData);
  
  		if(isDOMElement(imageData))
  			imageData = createDataURIFromElement(imageData, format);
  
  		if(this.isString(imageData)) {
  
  			var base64Info = this.extractInfoFromBase64DataURI(imageData);
  
  			if(base64Info) {
  
  				format = base64Info[2];
  				imageData = atob(base64Info[3]);//convert to binary string
  
  				/*
  				 * need to test if it's more efficent to convert all binary strings
  				 * to TypedArray - or should we just leave and process as string?
  				 */
  				if(this.supportsArrayBuffer()) {
  					dataAsBinaryString = imageData;
  					imageData = this.binaryStringToUint8Array(imageData);
  				}
  
  			}else{
  				// This is neither raw jpeg-data nor a data uri; alias?
  				if(imageData.charCodeAt(0) !== 0xff)
  					cached_info = checkImagesForAlias(imageData, images);
  			}
  		}
  
  		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');
  
  		var imageIndex = getImageIndex(images),
  			info = cached_info;
  
  		if(!info)
  			info = this['process' + format.toUpperCase()](imageData, imageIndex, alias, compression, dataAsBinaryString);
  
  		if(!info)
  			throw new Error('An unkwown error occurred whilst processing the image');
  
  		writeImageToPDF.call(this, x, y, w, h, info, imageIndex, 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;
  		// 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);
  				return [width, height];
  			} 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;
  
  		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];
  				return {width:width, height:height};
  			}
  
  			pos+=2;
  		}
  
  		throw new Error('getJpegSizeFromBytes could not find the size of the image');
  	}
  	, readBytes = function(data, offset) {
  		return data.subarray(offset, offset+ 4);
  	};
  
  	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], 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, colorSpace, bpc, filter, index, alias);
  		}
  
  		return null;
  	};
  
  	jsPDFAPI.processJPG = function(/*data, index, alias, compression, dataAsBinaryString*/) {
  		return this.processJPEG.apply(this, arguments);
  	}
  
  })(jsPDF.API);
  