jspdf.plugin.addimage.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. /** @preserve
  2. * jsPDF addImage plugin
  3. * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/
  4. * 2013 Chris Dowling, https://github.com/gingerchris
  5. * 2013 Trinh Ho, https://github.com/ineedfat
  6. * 2013 Edwin Alejandro Perez, https://github.com/eaparango
  7. * 2013 Norah Smith, https://github.com/burnburnrocket
  8. * 2014 Diego Casorran, https://github.com/diegocr
  9. * 2014 James Robb, https://github.com/jamesbrobb
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining
  12. * a copy of this software and associated documentation files (the
  13. * "Software"), to deal in the Software without restriction, including
  14. * without limitation the rights to use, copy, modify, merge, publish,
  15. * distribute, sublicense, and/or sell copies of the Software, and to
  16. * permit persons to whom the Software is furnished to do so, subject to
  17. * the following conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be
  20. * included in all copies or substantial portions of the Software.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. */
  30. ;(function(jsPDFAPI) {
  31. 'use strict'
  32. var namespace = 'addImage_',
  33. supported_image_types = ['jpeg', 'jpg', 'png'];
  34. // Image functionality ported from pdf.js
  35. var putImage = function(img) {
  36. var objectNumber = this.internal.newObject()
  37. , out = this.internal.write
  38. , putStream = this.internal.putStream
  39. img['n'] = objectNumber
  40. out('<</Type /XObject')
  41. out('/Subtype /Image')
  42. out('/Width ' + img['w'])
  43. out('/Height ' + img['h'])
  44. if (img['cs'] === this.color_spaces.INDEXED) {
  45. out('/ColorSpace [/Indexed /DeviceRGB '
  46. // if an indexed png defines more than one colour with transparency, we've created a smask
  47. + (img['pal'].length / 3 - 1) + ' ' + ('smask' in img ? objectNumber + 2 : objectNumber + 1)
  48. + ' 0 R]');
  49. } else {
  50. out('/ColorSpace /' + img['cs']);
  51. if (img['cs'] === this.color_spaces.DEVICE_CMYK) {
  52. out('/Decode [1 0 1 0 1 0 1 0]');
  53. }
  54. }
  55. out('/BitsPerComponent ' + img['bpc']);
  56. if ('f' in img) {
  57. out('/Filter /' + img['f']);
  58. }
  59. if ('dp' in img) {
  60. out('/DecodeParms <<' + img['dp'] + '>>');
  61. }
  62. if ('trns' in img && img['trns'].constructor == Array) {
  63. var trns = '',
  64. i = 0,
  65. len = img['trns'].length;
  66. for (; i < len; i++)
  67. trns += (img['trns'][i] + ' ' + img['trns'][i] + ' ');
  68. out('/Mask [' + trns + ']');
  69. }
  70. if ('smask' in img) {
  71. out('/SMask ' + (objectNumber + 1) + ' 0 R');
  72. }
  73. out('/Length ' + img['data'].length + '>>');
  74. putStream(img['data']);
  75. out('endobj');
  76. // Soft mask
  77. if ('smask' in img) {
  78. var dp = '/Predictor 15 /Colors 1 /BitsPerComponent ' + img['bpc'] + ' /Columns ' + img['w'];
  79. var smask = {'w': img['w'], 'h': img['h'], 'cs': 'DeviceGray', 'bpc': img['bpc'], 'dp': dp, 'data': img['smask']};
  80. if ('f' in img)
  81. smask.f = img['f'];
  82. putImage.call(this, smask);
  83. }
  84. //Palette
  85. if (img['cs'] === this.color_spaces.INDEXED) {
  86. this.internal.newObject();
  87. //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
  88. //putStream(zlib.compress(img['pal']));
  89. out('<< /Length ' + img['pal'].length + '>>');
  90. putStream(this.arrayBufferToBinaryString(new Uint8Array(img['pal'])));
  91. out('endobj');
  92. }
  93. }
  94. , putResourcesCallback = function() {
  95. var images = this.internal.collections[namespace + 'images']
  96. for ( var i in images ) {
  97. putImage.call(this, images[i])
  98. }
  99. }
  100. , putXObjectsDictCallback = function(){
  101. var images = this.internal.collections[namespace + 'images']
  102. , out = this.internal.write
  103. , image
  104. for (var i in images) {
  105. image = images[i]
  106. out(
  107. '/I' + image['i']
  108. , image['n']
  109. , '0'
  110. , 'R'
  111. )
  112. }
  113. }
  114. , checkCompressValue = function(value) {
  115. if(value && typeof value === 'string')
  116. value = value.toUpperCase();
  117. return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE;
  118. }
  119. , getImages = function() {
  120. var images = this.internal.collections[namespace + 'images'];
  121. //first run, so initialise stuff
  122. if(!images) {
  123. this.internal.collections[namespace + 'images'] = images = {};
  124. this.internal.events.subscribe('putResources', putResourcesCallback);
  125. this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback);
  126. }
  127. return images;
  128. }
  129. , getImageIndex = function(images) {
  130. var imageIndex = 0;
  131. if (images){
  132. // this is NOT the first time this method is ran on this instance of jsPDF object.
  133. imageIndex = Object.keys ?
  134. Object.keys(images).length :
  135. (function(o){
  136. var i = 0
  137. for (var e in o){if(o.hasOwnProperty(e)){ i++ }}
  138. return i
  139. })(images)
  140. }
  141. return imageIndex;
  142. }
  143. , notDefined = function(value) {
  144. return typeof value === 'undefined' || value === null;
  145. }
  146. , generateAliasFromData = function(data) {
  147. // TODO: Alias dynamic generation from imageData's checksum/hash
  148. return undefined;
  149. }
  150. , doesNotSupportImageType = function(type) {
  151. return supported_image_types.indexOf(type) === -1;
  152. }
  153. , processMethodNotEnabled = function(type) {
  154. return typeof jsPDFAPI['process' + type.toUpperCase()] !== 'function';
  155. }
  156. , isDOMElement = function(object) {
  157. return typeof object === 'object' && object.nodeType === 1;
  158. }
  159. , createDataURIFromElement = function(element, format) {
  160. //if element is an image which uses data url defintion, just return the dataurl
  161. if (element.nodeName === 'IMG' && element.hasAttribute('src')) {
  162. var src = ''+element.getAttribute('src');
  163. if (src.indexOf('data:image/') === 0) return src;
  164. // only if the user doesn't care about a format
  165. if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = 'png';
  166. }
  167. if(element.nodeName === 'CANVAS') {
  168. var canvas = element;
  169. } else {
  170. var canvas = document.createElement('canvas');
  171. canvas.width = element.clientWidth || element.width;
  172. canvas.height = element.clientHeight || element.height;
  173. var ctx = canvas.getContext('2d');
  174. if (!ctx) {
  175. throw ('addImage requires canvas to be supported by browser.');
  176. }
  177. ctx.drawImage(element, 0, 0, canvas.width, canvas.height);
  178. }
  179. return canvas.toDataURL((''+format).toLowerCase() == 'png' ? 'image/png' : 'image/jpeg');
  180. }
  181. ,checkImagesForAlias = function(imageData, images) {
  182. var cached_info;
  183. if(images) {
  184. for(var e in images) {
  185. if(imageData === images[e].alias) {
  186. cached_info = images[e];
  187. break;
  188. }
  189. }
  190. }
  191. return cached_info;
  192. }
  193. ,determineWidthAndHeight = function(w, h, info) {
  194. if (!w && !h) {
  195. w = -96;
  196. h = -96;
  197. }
  198. if (w < 0) {
  199. w = (-1) * info['w'] * 72 / w / this.internal.scaleFactor;
  200. }
  201. if (h < 0) {
  202. h = (-1) * info['h'] * 72 / h / this.internal.scaleFactor;
  203. }
  204. if (w === 0) {
  205. w = h * info['w'] / info['h'];
  206. }
  207. if (h === 0) {
  208. h = w * info['h'] / info['w'];
  209. }
  210. return [w, h];
  211. }
  212. , writeImageToPDF = function(x, y, w, h, info, index, images) {
  213. var dims = determineWidthAndHeight.call(this, w, h, info),
  214. coord = this.internal.getCoordinateString,
  215. vcoord = this.internal.getVerticalCoordinateString;
  216. w = dims[0];
  217. h = dims[1];
  218. images[index] = info;
  219. this.internal.write(
  220. 'q'
  221. , coord(w)
  222. , '0 0'
  223. , coord(h) // TODO: check if this should be shifted by vcoord
  224. , coord(x)
  225. , vcoord(y + h)
  226. , 'cm /I'+info['i']
  227. , 'Do Q'
  228. )
  229. };
  230. /**
  231. * COLOR SPACES
  232. */
  233. jsPDFAPI.color_spaces = {
  234. DEVICE_RGB:'DeviceRGB',
  235. DEVICE_GRAY:'DeviceGray',
  236. DEVICE_CMYK:'DeviceCMYK',
  237. CAL_GREY:'CalGray',
  238. CAL_RGB:'CalRGB',
  239. LAB:'Lab',
  240. ICC_BASED:'ICCBased',
  241. INDEXED:'Indexed',
  242. PATTERN:'Pattern',
  243. SEPERATION:'Seperation',
  244. DEVICE_N:'DeviceN'
  245. };
  246. /**
  247. * DECODE METHODS
  248. */
  249. jsPDFAPI.decode = {
  250. DCT_DECODE:'DCTDecode',
  251. FLATE_DECODE:'FlateDecode',
  252. LZW_DECODE:'LZWDecode',
  253. JPX_DECODE:'JPXDecode',
  254. JBIG2_DECODE:'JBIG2Decode',
  255. ASCII85_DECODE:'ASCII85Decode',
  256. ASCII_HEX_DECODE:'ASCIIHexDecode',
  257. RUN_LENGTH_DECODE:'RunLengthDecode',
  258. CCITT_FAX_DECODE:'CCITTFaxDecode'
  259. };
  260. /**
  261. * IMAGE COMPRESSION TYPES
  262. */
  263. jsPDFAPI.image_compression = {
  264. NONE: 'NONE',
  265. FAST: 'FAST',
  266. MEDIUM: 'MEDIUM',
  267. SLOW: 'SLOW'
  268. };
  269. jsPDFAPI.isString = function(object) {
  270. return typeof object === 'string';
  271. };
  272. /**
  273. * Strips out and returns info from a valid base64 data URI
  274. * @param {String[dataURI]} a valid data URI of format 'data:[<MIME-type>][;base64],<data>'
  275. * @returns an Array containing the following
  276. * [0] the complete data URI
  277. * [1] <MIME-type>
  278. * [2] format - the second part of the mime-type i.e 'png' in 'image/png'
  279. * [4] <data>
  280. */
  281. jsPDFAPI.extractInfoFromBase64DataURI = function(dataURI) {
  282. return /^data:([\w]+?\/([\w]+?));base64,(.+?)$/g.exec(dataURI);
  283. };
  284. /**
  285. * Check to see if ArrayBuffer is supported
  286. */
  287. jsPDFAPI.supportsArrayBuffer = function() {
  288. return typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined';
  289. };
  290. /**
  291. * Tests supplied object to determine if ArrayBuffer
  292. * @param {Object[object]}
  293. */
  294. jsPDFAPI.isArrayBuffer = function(object) {
  295. if(!this.supportsArrayBuffer())
  296. return false;
  297. return object instanceof ArrayBuffer;
  298. };
  299. /**
  300. * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface
  301. * @param {Object[object]}
  302. */
  303. jsPDFAPI.isArrayBufferView = function(object) {
  304. if(!this.supportsArrayBuffer())
  305. return false;
  306. if(typeof Uint32Array === 'undefined')
  307. return false;
  308. return (object instanceof Int8Array ||
  309. object instanceof Uint8Array ||
  310. (typeof Uint8ClampedArray !== 'undefined' && object instanceof Uint8ClampedArray) ||
  311. object instanceof Int16Array ||
  312. object instanceof Uint16Array ||
  313. object instanceof Int32Array ||
  314. object instanceof Uint32Array ||
  315. object instanceof Float32Array ||
  316. object instanceof Float64Array );
  317. };
  318. /**
  319. * Exactly what it says on the tin
  320. */
  321. jsPDFAPI.binaryStringToUint8Array = function(binary_string) {
  322. /*
  323. * not sure how efficient this will be will bigger files. Is there a native method?
  324. */
  325. var len = binary_string.length;
  326. var bytes = new Uint8Array( len );
  327. for (var i = 0; i < len; i++) {
  328. bytes[i] = binary_string.charCodeAt(i);
  329. }
  330. return bytes;
  331. };
  332. /**
  333. * @see this discussion
  334. * http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
  335. *
  336. * As stated, i imagine the method below is highly inefficent for large files.
  337. *
  338. * Also of note from Mozilla,
  339. *
  340. * "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)."
  341. *
  342. * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView
  343. *
  344. * Although i'm strugglig to see how StringView solves this issue? Doesn't appear to be a direct method for conversion?
  345. *
  346. * Async method using Blob and FileReader could be best, but i'm not sure how to fit it into the flow?
  347. */
  348. jsPDFAPI.arrayBufferToBinaryString = function(buffer) {
  349. if(this.isArrayBuffer(buffer))
  350. buffer = new Uint8Array(buffer);
  351. var binary_string = '';
  352. var len = buffer.byteLength;
  353. for (var i = 0; i < len; i++) {
  354. binary_string += String.fromCharCode(buffer[i]);
  355. }
  356. return binary_string;
  357. /*
  358. * Another solution is the method below - convert array buffer straight to base64 and then use atob
  359. */
  360. //return atob(this.arrayBufferToBase64(buffer));
  361. };
  362. /**
  363. * Converts an ArrayBuffer directly to base64
  364. *
  365. * Taken from here
  366. *
  367. * http://jsperf.com/encoding-xhr-image-data/31
  368. *
  369. * Need to test if this is a better solution for larger files
  370. *
  371. */
  372. jsPDFAPI.arrayBufferToBase64 = function(arrayBuffer) {
  373. var base64 = ''
  374. var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  375. var bytes = new Uint8Array(arrayBuffer)
  376. var byteLength = bytes.byteLength
  377. var byteRemainder = byteLength % 3
  378. var mainLength = byteLength - byteRemainder
  379. var a, b, c, d
  380. var chunk
  381. // Main loop deals with bytes in chunks of 3
  382. for (var i = 0; i < mainLength; i = i + 3) {
  383. // Combine the three bytes into a single integer
  384. chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
  385. // Use bitmasks to extract 6-bit segments from the triplet
  386. a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
  387. b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
  388. c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
  389. d = chunk & 63 // 63 = 2^6 - 1
  390. // Convert the raw binary segments to the appropriate ASCII encoding
  391. base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
  392. }
  393. // Deal with the remaining bytes and padding
  394. if (byteRemainder == 1) {
  395. chunk = bytes[mainLength]
  396. a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
  397. // Set the 4 least significant bits to zero
  398. b = (chunk & 3) << 4 // 3 = 2^2 - 1
  399. base64 += encodings[a] + encodings[b] + '=='
  400. } else if (byteRemainder == 2) {
  401. chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
  402. a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
  403. b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
  404. // Set the 2 least significant bits to zero
  405. c = (chunk & 15) << 2 // 15 = 2^4 - 1
  406. base64 += encodings[a] + encodings[b] + encodings[c] + '='
  407. }
  408. return base64
  409. };
  410. jsPDFAPI.createImageInfo = function(data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask) {
  411. var info = {
  412. alias:alias,
  413. w : wd,
  414. h : ht,
  415. cs : cs,
  416. bpc : bpc,
  417. i : imageIndex,
  418. data : data
  419. // n: objectNumber will be added by putImage code
  420. };
  421. if(f) info.f = f;
  422. if(dp) info.dp = dp;
  423. if(trns) info.trns = trns;
  424. if(pal) info.pal = pal;
  425. if(smask) info.smask = smask;
  426. return info;
  427. };
  428. jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias, compression) {
  429. 'use strict'
  430. if(typeof format === 'number') {
  431. var tmp = h;
  432. h = w;
  433. w = y;
  434. y = x;
  435. x = format;
  436. format = tmp;
  437. }
  438. var images = getImages.call(this),//initalises internals and events on first run
  439. dataAsBinaryString;
  440. compression = checkCompressValue(compression);
  441. if(notDefined(alias))
  442. alias = generateAliasFromData(imageData);
  443. if(isDOMElement(imageData))
  444. imageData = createDataURIFromElement(imageData, format);
  445. if(this.isString(imageData)) {
  446. var base64Info = this.extractInfoFromBase64DataURI(imageData);
  447. if(base64Info) {
  448. format = base64Info[2];
  449. imageData = atob(base64Info[3]);//convert to binary string
  450. } else {
  451. if (imgData.charCodeAt(0) === 0x89 &&
  452. imgData.charCodeAt(1) === 0x50 &&
  453. imgData.charCodeAt(2) === 0x4e &&
  454. imgData.charCodeAt(3) === 0x47 ) format = 'png';
  455. }
  456. }
  457. format = (format || 'JPEG').toLowerCase();
  458. if(doesNotSupportImageType(format))
  459. throw new Error('addImage currently only supports formats ' + supported_image_types + ', not \''+format+'\'');
  460. if(processMethodNotEnabled(format))
  461. throw new Error('please ensure that the plugin for \''+format+'\' support is added');
  462. /*
  463. * need to test if it's more efficent to convert all binary strings
  464. * to TypedArray - or should we just leave and process as string?
  465. */
  466. if(this.supportsArrayBuffer()) {
  467. dataAsBinaryString = imageData;
  468. imageData = this.binaryStringToUint8Array(imageData);
  469. }
  470. var imageIndex = getImageIndex(images),
  471. info = checkImagesForAlias(dataAsBinaryString || imageData, images);
  472. if(!info)
  473. info = this['process' + format.toUpperCase()](imageData, imageIndex, alias, compression, dataAsBinaryString);
  474. if(!info)
  475. throw new Error('An unkwown error occurred whilst processing the image');
  476. writeImageToPDF.call(this, x, y, w, h, info, imageIndex, images);
  477. return this
  478. };
  479. /**
  480. * JPEG SUPPORT
  481. **/
  482. //takes a string imgData containing the raw bytes of
  483. //a jpeg image and returns [width, height]
  484. //Algorithm from: http://www.64lines.com/jpeg-width-height
  485. var getJpegSize = function(imgData) {
  486. 'use strict'
  487. var width, height, numcomponents;
  488. // Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00
  489. if (!imgData.charCodeAt(0) === 0xff ||
  490. !imgData.charCodeAt(1) === 0xd8 ||
  491. !imgData.charCodeAt(2) === 0xff ||
  492. !imgData.charCodeAt(3) === 0xe0 ||
  493. !imgData.charCodeAt(6) === 'J'.charCodeAt(0) ||
  494. !imgData.charCodeAt(7) === 'F'.charCodeAt(0) ||
  495. !imgData.charCodeAt(8) === 'I'.charCodeAt(0) ||
  496. !imgData.charCodeAt(9) === 'F'.charCodeAt(0) ||
  497. !imgData.charCodeAt(10) === 0x00) {
  498. throw new Error('getJpegSize requires a binary string jpeg file')
  499. }
  500. var blockLength = imgData.charCodeAt(4)*256 + imgData.charCodeAt(5);
  501. var i = 4, len = imgData.length;
  502. while ( i < len ) {
  503. i += blockLength;
  504. if (imgData.charCodeAt(i) !== 0xff) {
  505. throw new Error('getJpegSize could not find the size of the image');
  506. }
  507. if (imgData.charCodeAt(i+1) === 0xc0 || //(SOF) Huffman - Baseline DCT
  508. imgData.charCodeAt(i+1) === 0xc1 || //(SOF) Huffman - Extended sequential DCT
  509. imgData.charCodeAt(i+1) === 0xc2 || // Progressive DCT (SOF2)
  510. imgData.charCodeAt(i+1) === 0xc3 || // Spatial (sequential) lossless (SOF3)
  511. imgData.charCodeAt(i+1) === 0xc4 || // Differential sequential DCT (SOF5)
  512. imgData.charCodeAt(i+1) === 0xc5 || // Differential progressive DCT (SOF6)
  513. imgData.charCodeAt(i+1) === 0xc6 || // Differential spatial (SOF7)
  514. imgData.charCodeAt(i+1) === 0xc7) {
  515. height = imgData.charCodeAt(i+5)*256 + imgData.charCodeAt(i+6);
  516. width = imgData.charCodeAt(i+7)*256 + imgData.charCodeAt(i+8);
  517. numcomponents = imgData.charCodeAt(i+9);
  518. return [width, height, numcomponents];
  519. } else {
  520. i += 2;
  521. blockLength = imgData.charCodeAt(i)*256 + imgData.charCodeAt(i+1)
  522. }
  523. }
  524. }
  525. , getJpegSizeFromBytes = function(data) {
  526. var hdr = (data[0] << 8) | data[1];
  527. if(hdr !== 0xFFD8)
  528. throw new Error('Supplied data is not a JPEG');
  529. var len = data.length,
  530. block = (data[4] << 8) + data[5],
  531. pos = 4,
  532. bytes, width, height, numcomponents;
  533. while(pos < len) {
  534. pos += block;
  535. bytes = readBytes(data, pos);
  536. block = (bytes[2] << 8) + bytes[3];
  537. if((bytes[1] === 0xC0 || bytes[1] === 0xC2) && bytes[0] === 0xFF && block > 7) {
  538. bytes = readBytes(data, pos + 5);
  539. width = (bytes[2] << 8) + bytes[3];
  540. height = (bytes[0] << 8) + bytes[1];
  541. numcomponents = bytes[4];
  542. return {width:width, height:height, numcomponents: numcomponents};
  543. }
  544. pos+=2;
  545. }
  546. throw new Error('getJpegSizeFromBytes could not find the size of the image');
  547. }
  548. , readBytes = function(data, offset) {
  549. return data.subarray(offset, offset+ 5);
  550. };
  551. jsPDFAPI.processJPEG = function(data, index, alias, compression, dataAsBinaryString) {
  552. 'use strict'
  553. var colorSpace = this.color_spaces.DEVICE_RGB,
  554. filter = this.decode.DCT_DECODE,
  555. bpc = 8,
  556. dims;
  557. if(this.isString(data)) {
  558. dims = getJpegSize(data);
  559. return this.createImageInfo(data, dims[0], dims[1], dims[3] == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias);
  560. }
  561. if(this.isArrayBuffer(data))
  562. data = new Uint8Array(data);
  563. if(this.isArrayBufferView(data)) {
  564. dims = getJpegSizeFromBytes(data);
  565. // if we already have a stored binary string rep use that
  566. data = dataAsBinaryString || this.arrayBufferToBinaryString(data);
  567. return this.createImageInfo(data, dims.width, dims.height, dims.numcomponents == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias);
  568. }
  569. return null;
  570. };
  571. jsPDFAPI.processJPG = function(/*data, index, alias, compression, dataAsBinaryString*/) {
  572. return this.processJPEG.apply(this, arguments);
  573. }
  574. })(jsPDF.API);