modules/addimage.js

  1. /** @license
  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. /**
  31. * @name addImage
  32. * @module
  33. */
  34. import { jsPDF } from "../jspdf.js";
  35. import { atob, btoa } from "../libs/AtobBtoa.js";
  36. (function(jsPDFAPI) {
  37. "use strict";
  38. var namespace = "addImage_";
  39. jsPDFAPI.__addimage__ = {};
  40. var UNKNOWN = "UNKNOWN";
  41. // Heuristic selection of a good batch for large array .apply. Not limiting make the call overflow.
  42. // With too small batch iteration will be slow as more calls are made,
  43. // higher values cause larger and slower garbage collection.
  44. var ARRAY_APPLY_BATCH = 8192;
  45. var imageFileTypeHeaders = {
  46. PNG: [[0x89, 0x50, 0x4e, 0x47]],
  47. TIFF: [
  48. [0x4d, 0x4d, 0x00, 0x2a], //Motorola
  49. [0x49, 0x49, 0x2a, 0x00] //Intel
  50. ],
  51. JPEG: [
  52. [
  53. 0xff,
  54. 0xd8,
  55. 0xff,
  56. 0xe0,
  57. undefined,
  58. undefined,
  59. 0x4a,
  60. 0x46,
  61. 0x49,
  62. 0x46,
  63. 0x00
  64. ], //JFIF
  65. [
  66. 0xff,
  67. 0xd8,
  68. 0xff,
  69. 0xe1,
  70. undefined,
  71. undefined,
  72. 0x45,
  73. 0x78,
  74. 0x69,
  75. 0x66,
  76. 0x00,
  77. 0x00
  78. ], //Exif
  79. [0xff, 0xd8, 0xff, 0xdb], //JPEG RAW
  80. [0xff, 0xd8, 0xff, 0xee] //EXIF RAW
  81. ],
  82. JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]],
  83. GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]],
  84. GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]],
  85. WEBP: [
  86. [
  87. 0x52,
  88. 0x49,
  89. 0x46,
  90. 0x46,
  91. undefined,
  92. undefined,
  93. undefined,
  94. undefined,
  95. 0x57,
  96. 0x45,
  97. 0x42,
  98. 0x50
  99. ]
  100. ],
  101. BMP: [
  102. [0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc.
  103. [0x42, 0x41], //BA - OS/2 struct bitmap array
  104. [0x43, 0x49], //CI - OS/2 struct color icon
  105. [0x43, 0x50], //CP - OS/2 const color pointer
  106. [0x49, 0x43], //IC - OS/2 struct icon
  107. [0x50, 0x54] //PT - OS/2 pointer
  108. ]
  109. };
  110. /**
  111. * Recognize filetype of Image by magic-bytes
  112. *
  113. * https://en.wikipedia.org/wiki/List_of_file_signatures
  114. *
  115. * @name getImageFileTypeByImageData
  116. * @public
  117. * @function
  118. * @param {string|arraybuffer} imageData imageData as binary String or arraybuffer
  119. * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG'
  120. *
  121. * @returns {string} filetype of Image
  122. */
  123. var getImageFileTypeByImageData = (jsPDFAPI.__addimage__.getImageFileTypeByImageData = function(
  124. imageData,
  125. fallbackFormat
  126. ) {
  127. fallbackFormat = fallbackFormat || UNKNOWN;
  128. var i;
  129. var j;
  130. var result = UNKNOWN;
  131. var headerSchemata;
  132. var compareResult;
  133. var fileType;
  134. if (
  135. fallbackFormat === "RGBA" ||
  136. (imageData.data !== undefined &&
  137. imageData.data instanceof Uint8ClampedArray &&
  138. "height" in imageData &&
  139. "width" in imageData)
  140. ) {
  141. return "RGBA";
  142. }
  143. if (isArrayBufferView(imageData)) {
  144. for (fileType in imageFileTypeHeaders) {
  145. headerSchemata = imageFileTypeHeaders[fileType];
  146. for (i = 0; i < headerSchemata.length; i += 1) {
  147. compareResult = true;
  148. for (j = 0; j < headerSchemata[i].length; j += 1) {
  149. if (headerSchemata[i][j] === undefined) {
  150. continue;
  151. }
  152. if (headerSchemata[i][j] !== imageData[j]) {
  153. compareResult = false;
  154. break;
  155. }
  156. }
  157. if (compareResult === true) {
  158. result = fileType;
  159. break;
  160. }
  161. }
  162. }
  163. } else {
  164. for (fileType in imageFileTypeHeaders) {
  165. headerSchemata = imageFileTypeHeaders[fileType];
  166. for (i = 0; i < headerSchemata.length; i += 1) {
  167. compareResult = true;
  168. for (j = 0; j < headerSchemata[i].length; j += 1) {
  169. if (headerSchemata[i][j] === undefined) {
  170. continue;
  171. }
  172. if (headerSchemata[i][j] !== imageData.charCodeAt(j)) {
  173. compareResult = false;
  174. break;
  175. }
  176. }
  177. if (compareResult === true) {
  178. result = fileType;
  179. break;
  180. }
  181. }
  182. }
  183. }
  184. if (result === UNKNOWN && fallbackFormat !== UNKNOWN) {
  185. result = fallbackFormat;
  186. }
  187. return result;
  188. });
  189. // Image functionality ported from pdf.js
  190. var putImage = function(image) {
  191. var out = this.internal.write;
  192. var putStream = this.internal.putStream;
  193. var getFilters = this.internal.getFilters;
  194. var filter = getFilters();
  195. while (filter.indexOf("FlateEncode") !== -1) {
  196. filter.splice(filter.indexOf("FlateEncode"), 1);
  197. }
  198. image.objectId = this.internal.newObject();
  199. var additionalKeyValues = [];
  200. additionalKeyValues.push({ key: "Type", value: "/XObject" });
  201. additionalKeyValues.push({ key: "Subtype", value: "/Image" });
  202. additionalKeyValues.push({ key: "Width", value: image.width });
  203. additionalKeyValues.push({ key: "Height", value: image.height });
  204. if (image.colorSpace === color_spaces.INDEXED) {
  205. additionalKeyValues.push({
  206. key: "ColorSpace",
  207. value:
  208. "[/Indexed /DeviceRGB " +
  209. // if an indexed png defines more than one colour with transparency, we've created a sMask
  210. (image.palette.length / 3 - 1) +
  211. " " +
  212. ("sMask" in image && typeof image.sMask !== "undefined"
  213. ? image.objectId + 2
  214. : image.objectId + 1) +
  215. " 0 R]"
  216. });
  217. } else {
  218. additionalKeyValues.push({
  219. key: "ColorSpace",
  220. value: "/" + image.colorSpace
  221. });
  222. if (image.colorSpace === color_spaces.DEVICE_CMYK) {
  223. additionalKeyValues.push({ key: "Decode", value: "[1 0 1 0 1 0 1 0]" });
  224. }
  225. }
  226. additionalKeyValues.push({
  227. key: "BitsPerComponent",
  228. value: image.bitsPerComponent
  229. });
  230. if (
  231. "decodeParameters" in image &&
  232. typeof image.decodeParameters !== "undefined"
  233. ) {
  234. additionalKeyValues.push({
  235. key: "DecodeParms",
  236. value: "<<" + image.decodeParameters + ">>"
  237. });
  238. }
  239. if ("transparency" in image && Array.isArray(image.transparency)) {
  240. var transparency = "",
  241. i = 0,
  242. len = image.transparency.length;
  243. for (; i < len; i++)
  244. transparency +=
  245. image.transparency[i] + " " + image.transparency[i] + " ";
  246. additionalKeyValues.push({
  247. key: "Mask",
  248. value: "[" + transparency + "]"
  249. });
  250. }
  251. if (typeof image.sMask !== "undefined") {
  252. additionalKeyValues.push({
  253. key: "SMask",
  254. value: image.objectId + 1 + " 0 R"
  255. });
  256. }
  257. var alreadyAppliedFilters =
  258. typeof image.filter !== "undefined" ? ["/" + image.filter] : undefined;
  259. putStream({
  260. data: image.data,
  261. additionalKeyValues: additionalKeyValues,
  262. alreadyAppliedFilters: alreadyAppliedFilters,
  263. objectId: image.objectId
  264. });
  265. out("endobj");
  266. // Soft mask
  267. if ("sMask" in image && typeof image.sMask !== "undefined") {
  268. var decodeParameters =
  269. "/Predictor " +
  270. image.predictor +
  271. " /Colors 1 /BitsPerComponent " +
  272. image.bitsPerComponent +
  273. " /Columns " +
  274. image.width;
  275. var sMask = {
  276. width: image.width,
  277. height: image.height,
  278. colorSpace: "DeviceGray",
  279. bitsPerComponent: image.bitsPerComponent,
  280. decodeParameters: decodeParameters,
  281. data: image.sMask
  282. };
  283. if ("filter" in image) {
  284. sMask.filter = image.filter;
  285. }
  286. putImage.call(this, sMask);
  287. }
  288. //Palette
  289. if (image.colorSpace === color_spaces.INDEXED) {
  290. var objId = this.internal.newObject();
  291. //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
  292. //putStream(zlib.compress(img['pal']));
  293. putStream({
  294. data: arrayBufferToBinaryString(new Uint8Array(image.palette)),
  295. objectId: objId
  296. });
  297. out("endobj");
  298. }
  299. };
  300. var putResourcesCallback = function() {
  301. var images = this.internal.collections[namespace + "images"];
  302. for (var i in images) {
  303. putImage.call(this, images[i]);
  304. }
  305. };
  306. var putXObjectsDictCallback = function() {
  307. var images = this.internal.collections[namespace + "images"],
  308. out = this.internal.write,
  309. image;
  310. for (var i in images) {
  311. image = images[i];
  312. out("/I" + image.index, image.objectId, "0", "R");
  313. }
  314. };
  315. var checkCompressValue = function(value) {
  316. if (value && typeof value === "string") value = value.toUpperCase();
  317. return value in jsPDFAPI.image_compression ? value : image_compression.NONE;
  318. };
  319. var initialize = function() {
  320. if (!this.internal.collections[namespace + "images"]) {
  321. this.internal.collections[namespace + "images"] = {};
  322. this.internal.events.subscribe("putResources", putResourcesCallback);
  323. this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback);
  324. }
  325. };
  326. var getImages = function() {
  327. var images = this.internal.collections[namespace + "images"];
  328. initialize.call(this);
  329. return images;
  330. };
  331. var getImageIndex = function() {
  332. return Object.keys(this.internal.collections[namespace + "images"]).length;
  333. };
  334. var notDefined = function(value) {
  335. return typeof value === "undefined" || value === null || value.length === 0;
  336. };
  337. var generateAliasFromImageData = function(imageData) {
  338. if (typeof imageData === "string" || isArrayBufferView(imageData)) {
  339. return sHashCode(imageData);
  340. } else if (isArrayBufferView(imageData.data)) {
  341. return sHashCode(imageData.data);
  342. }
  343. return null;
  344. };
  345. var isImageTypeSupported = function(type) {
  346. return typeof jsPDFAPI["process" + type.toUpperCase()] === "function";
  347. };
  348. var isDOMElement = function(object) {
  349. return typeof object === "object" && object.nodeType === 1;
  350. };
  351. var getImageDataFromElement = function(element, format) {
  352. //if element is an image which uses data url definition, just return the dataurl
  353. if (element.nodeName === "IMG" && element.hasAttribute("src")) {
  354. var src = "" + element.getAttribute("src");
  355. //is base64 encoded dataUrl, directly process it
  356. if (src.indexOf("data:image/") === 0) {
  357. return atob(
  358. unescape(src)
  359. .split("base64,")
  360. .pop()
  361. );
  362. }
  363. //it is probably an url, try to load it
  364. var tmpImageData = jsPDFAPI.loadFile(src, true);
  365. if (tmpImageData !== undefined) {
  366. return tmpImageData;
  367. }
  368. }
  369. if (element.nodeName === "CANVAS") {
  370. if (element.width === 0 || element.height === 0) {
  371. throw new Error(
  372. "Given canvas must have data. Canvas width: " +
  373. element.width +
  374. ", height: " +
  375. element.height
  376. );
  377. }
  378. var mimeType;
  379. switch (format) {
  380. case "PNG":
  381. mimeType = "image/png";
  382. break;
  383. case "WEBP":
  384. mimeType = "image/webp";
  385. break;
  386. case "JPEG":
  387. case "JPG":
  388. default:
  389. mimeType = "image/jpeg";
  390. break;
  391. }
  392. return atob(
  393. element
  394. .toDataURL(mimeType, 1.0)
  395. .split("base64,")
  396. .pop()
  397. );
  398. }
  399. };
  400. var checkImagesForAlias = function(alias) {
  401. var images = this.internal.collections[namespace + "images"];
  402. if (images) {
  403. for (var e in images) {
  404. if (alias === images[e].alias) {
  405. return images[e];
  406. }
  407. }
  408. }
  409. };
  410. var determineWidthAndHeight = function(width, height, image) {
  411. if (!width && !height) {
  412. width = -96;
  413. height = -96;
  414. }
  415. if (width < 0) {
  416. width = (-1 * image.width * 72) / width / this.internal.scaleFactor;
  417. }
  418. if (height < 0) {
  419. height = (-1 * image.height * 72) / height / this.internal.scaleFactor;
  420. }
  421. if (width === 0) {
  422. width = (height * image.width) / image.height;
  423. }
  424. if (height === 0) {
  425. height = (width * image.height) / image.width;
  426. }
  427. return [width, height];
  428. };
  429. var writeImageToPDF = function(x, y, width, height, image, rotation) {
  430. var dims = determineWidthAndHeight.call(this, width, height, image),
  431. coord = this.internal.getCoordinateString,
  432. vcoord = this.internal.getVerticalCoordinateString;
  433. var images = getImages.call(this);
  434. width = dims[0];
  435. height = dims[1];
  436. images[image.index] = image;
  437. if (rotation) {
  438. rotation *= Math.PI / 180;
  439. var c = Math.cos(rotation);
  440. var s = Math.sin(rotation);
  441. //like in pdf Reference do it 4 digits instead of 2
  442. var f4 = function(number) {
  443. return number.toFixed(4);
  444. };
  445. var rotationTransformationMatrix = [
  446. f4(c),
  447. f4(s),
  448. f4(s * -1),
  449. f4(c),
  450. 0,
  451. 0,
  452. "cm"
  453. ];
  454. }
  455. this.internal.write("q"); //Save graphics state
  456. if (rotation) {
  457. this.internal.write(
  458. [1, "0", "0", 1, coord(x), vcoord(y + height), "cm"].join(" ")
  459. ); //Translate
  460. this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate
  461. this.internal.write(
  462. [coord(width), "0", "0", coord(height), "0", "0", "cm"].join(" ")
  463. ); //Scale
  464. } else {
  465. this.internal.write(
  466. [
  467. coord(width),
  468. "0",
  469. "0",
  470. coord(height),
  471. coord(x),
  472. vcoord(y + height),
  473. "cm"
  474. ].join(" ")
  475. ); //Translate and Scale
  476. }
  477. if (this.isAdvancedAPI()) {
  478. // draw image bottom up when in "advanced" API mode
  479. this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" "));
  480. }
  481. this.internal.write("/I" + image.index + " Do"); //Paint Image
  482. this.internal.write("Q"); //Restore graphics state
  483. };
  484. /**
  485. * COLOR SPACES
  486. */
  487. var color_spaces = (jsPDFAPI.color_spaces = {
  488. DEVICE_RGB: "DeviceRGB",
  489. DEVICE_GRAY: "DeviceGray",
  490. DEVICE_CMYK: "DeviceCMYK",
  491. CAL_GREY: "CalGray",
  492. CAL_RGB: "CalRGB",
  493. LAB: "Lab",
  494. ICC_BASED: "ICCBased",
  495. INDEXED: "Indexed",
  496. PATTERN: "Pattern",
  497. SEPARATION: "Separation",
  498. DEVICE_N: "DeviceN"
  499. });
  500. /**
  501. * DECODE METHODS
  502. */
  503. jsPDFAPI.decode = {
  504. DCT_DECODE: "DCTDecode",
  505. FLATE_DECODE: "FlateDecode",
  506. LZW_DECODE: "LZWDecode",
  507. JPX_DECODE: "JPXDecode",
  508. JBIG2_DECODE: "JBIG2Decode",
  509. ASCII85_DECODE: "ASCII85Decode",
  510. ASCII_HEX_DECODE: "ASCIIHexDecode",
  511. RUN_LENGTH_DECODE: "RunLengthDecode",
  512. CCITT_FAX_DECODE: "CCITTFaxDecode"
  513. };
  514. /**
  515. * IMAGE COMPRESSION TYPES
  516. */
  517. var image_compression = (jsPDFAPI.image_compression = {
  518. NONE: "NONE",
  519. FAST: "FAST",
  520. MEDIUM: "MEDIUM",
  521. SLOW: "SLOW"
  522. });
  523. /**
  524. * @name sHashCode
  525. * @function
  526. * @param {string} data
  527. * @returns {string}
  528. */
  529. var sHashCode = (jsPDFAPI.__addimage__.sHashCode = function(data) {
  530. var hash = 0,
  531. i,
  532. len;
  533. if (typeof data === "string") {
  534. len = data.length;
  535. for (i = 0; i < len; i++) {
  536. hash = (hash << 5) - hash + data.charCodeAt(i);
  537. hash |= 0; // Convert to 32bit integer
  538. }
  539. } else if (isArrayBufferView(data)) {
  540. len = data.byteLength / 2;
  541. for (i = 0; i < len; i++) {
  542. hash = (hash << 5) - hash + data[i];
  543. hash |= 0; // Convert to 32bit integer
  544. }
  545. }
  546. return hash;
  547. });
  548. /**
  549. * Validates if given String is a valid Base64-String
  550. *
  551. * @name validateStringAsBase64
  552. * @public
  553. * @function
  554. * @param {String} possible Base64-String
  555. *
  556. * @returns {boolean}
  557. */
  558. var validateStringAsBase64 = (jsPDFAPI.__addimage__.validateStringAsBase64 = function(
  559. possibleBase64String
  560. ) {
  561. possibleBase64String = possibleBase64String || "";
  562. possibleBase64String.toString().trim();
  563. var result = true;
  564. if (possibleBase64String.length === 0) {
  565. result = false;
  566. }
  567. if (possibleBase64String.length % 4 !== 0) {
  568. result = false;
  569. }
  570. if (
  571. /^[A-Za-z0-9+/]+$/.test(
  572. possibleBase64String.substr(0, possibleBase64String.length - 2)
  573. ) === false
  574. ) {
  575. result = false;
  576. }
  577. if (
  578. /^[A-Za-z0-9/][A-Za-z0-9+/]|[A-Za-z0-9+/]=|==$/.test(
  579. possibleBase64String.substr(-2)
  580. ) === false
  581. ) {
  582. result = false;
  583. }
  584. return result;
  585. });
  586. /**
  587. * Strips out and returns info from a valid base64 data URI
  588. *
  589. * @name extractImageFromDataUrl
  590. * @function
  591. * @param {string} dataUrl a valid data URI of format 'data:[<MIME-type>][;base64],<data>'
  592. * @returns {string} The raw Base64-encoded data.
  593. */
  594. var extractImageFromDataUrl = (jsPDFAPI.__addimage__.extractImageFromDataUrl = function(
  595. dataUrl
  596. ) {
  597. if (dataUrl == null) {
  598. return null;
  599. }
  600. // avoid using a regexp for parsing because it might be vulnerable against ReDoS attacks
  601. dataUrl = dataUrl.trim();
  602. if (!dataUrl.startsWith("data:")) {
  603. return null;
  604. }
  605. const commaIndex = dataUrl.indexOf(",");
  606. if (commaIndex < 0) {
  607. return null;
  608. }
  609. const dataScheme = dataUrl.substring(0, commaIndex).trim();
  610. if (!dataScheme.endsWith("base64")) {
  611. return null;
  612. }
  613. return dataUrl.substring(commaIndex + 1);
  614. });
  615. /**
  616. * Check to see if ArrayBuffer is supported
  617. *
  618. * @name supportsArrayBuffer
  619. * @function
  620. * @returns {boolean}
  621. */
  622. var supportsArrayBuffer = (jsPDFAPI.__addimage__.supportsArrayBuffer = function() {
  623. return (
  624. typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"
  625. );
  626. });
  627. /**
  628. * Tests supplied object to determine if ArrayBuffer
  629. *
  630. * @name isArrayBuffer
  631. * @function
  632. * @param {Object} object an Object
  633. *
  634. * @returns {boolean}
  635. */
  636. jsPDFAPI.__addimage__.isArrayBuffer = function(object) {
  637. return supportsArrayBuffer() && object instanceof ArrayBuffer;
  638. };
  639. /**
  640. * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface
  641. *
  642. * @name isArrayBufferView
  643. * @function
  644. * @param {Object} object an Object
  645. * @returns {boolean}
  646. */
  647. var isArrayBufferView = (jsPDFAPI.__addimage__.isArrayBufferView = function(
  648. object
  649. ) {
  650. return (
  651. supportsArrayBuffer() &&
  652. typeof Uint32Array !== "undefined" &&
  653. (object instanceof Int8Array ||
  654. object instanceof Uint8Array ||
  655. (typeof Uint8ClampedArray !== "undefined" &&
  656. object instanceof Uint8ClampedArray) ||
  657. object instanceof Int16Array ||
  658. object instanceof Uint16Array ||
  659. object instanceof Int32Array ||
  660. object instanceof Uint32Array ||
  661. object instanceof Float32Array ||
  662. object instanceof Float64Array)
  663. );
  664. });
  665. /**
  666. * Convert Binary String to ArrayBuffer
  667. *
  668. * @name binaryStringToUint8Array
  669. * @public
  670. * @function
  671. * @param {string} BinaryString with ImageData
  672. * @returns {Uint8Array}
  673. */
  674. var binaryStringToUint8Array = (jsPDFAPI.__addimage__.binaryStringToUint8Array = function(
  675. binary_string
  676. ) {
  677. var len = binary_string.length;
  678. var bytes = new Uint8Array(len);
  679. for (var i = 0; i < len; i++) {
  680. bytes[i] = binary_string.charCodeAt(i);
  681. }
  682. return bytes;
  683. });
  684. /**
  685. * Convert the Buffer to a Binary String
  686. *
  687. * @name arrayBufferToBinaryString
  688. * @public
  689. * @function
  690. * @param {ArrayBuffer|ArrayBufferView} ArrayBuffer buffer or bufferView with ImageData
  691. *
  692. * @returns {String}
  693. */
  694. var arrayBufferToBinaryString = (jsPDFAPI.__addimage__.arrayBufferToBinaryString = function(
  695. buffer
  696. ) {
  697. var out = "";
  698. // There are calls with both ArrayBuffer and already converted Uint8Array or other BufferView.
  699. // Do not copy the array if input is already an array.
  700. var buf = isArrayBufferView(buffer) ? buffer : new Uint8Array(buffer);
  701. for (var i = 0; i < buf.length; i += ARRAY_APPLY_BATCH) {
  702. // Limit the amount of characters being parsed to prevent overflow.
  703. // Note that while TextDecoder would be faster, it does not have the same
  704. // functionality as fromCharCode with any provided encodings as of 3/2021.
  705. out += String.fromCharCode.apply(
  706. null,
  707. buf.subarray(i, i + ARRAY_APPLY_BATCH)
  708. );
  709. }
  710. return out;
  711. });
  712. /**
  713. * Possible parameter for addImage, an RGBA buffer with size.
  714. *
  715. * @typedef {Object} RGBAData
  716. * @property {Uint8ClampedArray} data - Single dimensional array of RGBA values. For example from canvas getImageData.
  717. * @property {number} width - Image width as the data does not carry this information in itself.
  718. * @property {number} height - Image height as the data does not carry this information in itself.
  719. */
  720. /**
  721. * Adds an Image to the PDF.
  722. *
  723. * @name addImage
  724. * @public
  725. * @function
  726. * @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array|RGBAData} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement or object containing RGBA array (like output from canvas.getImageData).
  727. * @param {string} format format of file if filetype-recognition fails or in case of a Canvas-Element needs to be specified (default for Canvas is JPEG), e.g. 'JPEG', 'PNG', 'WEBP'
  728. * @param {number} x x Coordinate (in units declared at inception of PDF document) against left edge of the page
  729. * @param {number} y y Coordinate (in units declared at inception of PDF document) against upper edge of the page
  730. * @param {number} width width of the image (in units declared at inception of PDF document)
  731. * @param {number} height height of the Image (in units declared at inception of PDF document)
  732. * @param {string} alias alias of the image (if used multiple times)
  733. * @param {string} compression compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW'
  734. * @param {number} rotation rotation of the image in degrees (0-359)
  735. *
  736. * @returns jsPDF
  737. */
  738. jsPDFAPI.addImage = function() {
  739. var imageData, format, x, y, w, h, alias, compression, rotation;
  740. imageData = arguments[0];
  741. if (typeof arguments[1] === "number") {
  742. format = UNKNOWN;
  743. x = arguments[1];
  744. y = arguments[2];
  745. w = arguments[3];
  746. h = arguments[4];
  747. alias = arguments[5];
  748. compression = arguments[6];
  749. rotation = arguments[7];
  750. } else {
  751. format = arguments[1];
  752. x = arguments[2];
  753. y = arguments[3];
  754. w = arguments[4];
  755. h = arguments[5];
  756. alias = arguments[6];
  757. compression = arguments[7];
  758. rotation = arguments[8];
  759. }
  760. if (
  761. typeof imageData === "object" &&
  762. !isDOMElement(imageData) &&
  763. "imageData" in imageData
  764. ) {
  765. var options = imageData;
  766. imageData = options.imageData;
  767. format = options.format || format || UNKNOWN;
  768. x = options.x || x || 0;
  769. y = options.y || y || 0;
  770. w = options.w || options.width || w;
  771. h = options.h || options.height || h;
  772. alias = options.alias || alias;
  773. compression = options.compression || compression;
  774. rotation = options.rotation || options.angle || rotation;
  775. }
  776. //If compression is not explicitly set, determine if we should use compression
  777. var filter = this.internal.getFilters();
  778. if (compression === undefined && filter.indexOf("FlateEncode") !== -1) {
  779. compression = "SLOW";
  780. }
  781. if (isNaN(x) || isNaN(y)) {
  782. throw new Error("Invalid coordinates passed to jsPDF.addImage");
  783. }
  784. initialize.call(this);
  785. var image = processImageData.call(
  786. this,
  787. imageData,
  788. format,
  789. alias,
  790. compression
  791. );
  792. writeImageToPDF.call(this, x, y, w, h, image, rotation);
  793. return this;
  794. };
  795. var processImageData = function(imageData, format, alias, compression) {
  796. var result, dataAsBinaryString;
  797. if (
  798. typeof imageData === "string" &&
  799. getImageFileTypeByImageData(imageData) === UNKNOWN
  800. ) {
  801. imageData = unescape(imageData);
  802. var tmpImageData = convertBase64ToBinaryString(imageData, false);
  803. if (tmpImageData !== "") {
  804. imageData = tmpImageData;
  805. } else {
  806. tmpImageData = jsPDFAPI.loadFile(imageData, true);
  807. if (tmpImageData !== undefined) {
  808. imageData = tmpImageData;
  809. }
  810. }
  811. }
  812. if (isDOMElement(imageData)) {
  813. imageData = getImageDataFromElement(imageData, format);
  814. }
  815. format = getImageFileTypeByImageData(imageData, format);
  816. if (!isImageTypeSupported(format)) {
  817. throw new Error(
  818. "addImage does not support files of type '" +
  819. format +
  820. "', please ensure that a plugin for '" +
  821. format +
  822. "' support is added."
  823. );
  824. }
  825. // now do the heavy lifting
  826. if (notDefined(alias)) {
  827. alias = generateAliasFromImageData(imageData);
  828. }
  829. result = checkImagesForAlias.call(this, alias);
  830. if (!result) {
  831. if (supportsArrayBuffer()) {
  832. // no need to convert if imageData is already uint8array
  833. if (!(imageData instanceof Uint8Array) && format !== "RGBA") {
  834. dataAsBinaryString = imageData;
  835. imageData = binaryStringToUint8Array(imageData);
  836. }
  837. }
  838. result = this["process" + format.toUpperCase()](
  839. imageData,
  840. getImageIndex.call(this),
  841. alias,
  842. checkCompressValue(compression),
  843. dataAsBinaryString
  844. );
  845. }
  846. if (!result) {
  847. throw new Error("An unknown error occurred whilst processing the image.");
  848. }
  849. return result;
  850. };
  851. /**
  852. * @name convertBase64ToBinaryString
  853. * @function
  854. * @param {string} stringData
  855. * @returns {string} binary string
  856. */
  857. var convertBase64ToBinaryString = (jsPDFAPI.__addimage__.convertBase64ToBinaryString = function(
  858. stringData,
  859. throwError
  860. ) {
  861. throwError = typeof throwError === "boolean" ? throwError : true;
  862. var imageData = "";
  863. var rawData;
  864. if (typeof stringData === "string") {
  865. rawData = extractImageFromDataUrl(stringData) ?? stringData;
  866. try {
  867. imageData = atob(rawData);
  868. } catch (e) {
  869. if (throwError) {
  870. if (!validateStringAsBase64(rawData)) {
  871. throw new Error(
  872. "Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString "
  873. );
  874. } else {
  875. throw new Error(
  876. "atob-Error in jsPDF.convertBase64ToBinaryString " + e.message
  877. );
  878. }
  879. }
  880. }
  881. }
  882. return imageData;
  883. });
  884. /**
  885. * @name getImageProperties
  886. * @function
  887. * @param {Object} imageData
  888. * @returns {Object}
  889. */
  890. jsPDFAPI.getImageProperties = function(imageData) {
  891. var image;
  892. var tmpImageData = "";
  893. var format;
  894. if (isDOMElement(imageData)) {
  895. imageData = getImageDataFromElement(imageData);
  896. }
  897. if (
  898. typeof imageData === "string" &&
  899. getImageFileTypeByImageData(imageData) === UNKNOWN
  900. ) {
  901. tmpImageData = convertBase64ToBinaryString(imageData, false);
  902. if (tmpImageData === "") {
  903. tmpImageData = jsPDFAPI.loadFile(imageData) || "";
  904. }
  905. imageData = tmpImageData;
  906. }
  907. format = getImageFileTypeByImageData(imageData);
  908. if (!isImageTypeSupported(format)) {
  909. throw new Error(
  910. "addImage does not support files of type '" +
  911. format +
  912. "', please ensure that a plugin for '" +
  913. format +
  914. "' support is added."
  915. );
  916. }
  917. if (supportsArrayBuffer() && !(imageData instanceof Uint8Array)) {
  918. imageData = binaryStringToUint8Array(imageData);
  919. }
  920. image = this["process" + format.toUpperCase()](imageData);
  921. if (!image) {
  922. throw new Error("An unknown error occurred whilst processing the image");
  923. }
  924. image.fileType = format;
  925. return image;
  926. };
  927. })(jsPDF.API);