modules/html.js

  1. /**
  2. * @license
  3. * Copyright (c) 2018 Erik Koopmans
  4. * Released under the MIT License.
  5. *
  6. * Licensed under the MIT License.
  7. * http://opensource.org/licenses/mit-license
  8. */
  9. import { jsPDF } from "../jspdf.js";
  10. import { normalizeFontFace } from "../libs/fontFace.js";
  11. import { globalObject } from "../libs/globalObject.js";
  12. /**
  13. * jsPDF html PlugIn
  14. *
  15. * @name html
  16. * @module
  17. */
  18. (function(jsPDFAPI) {
  19. "use strict";
  20. function loadHtml2Canvas() {
  21. return (function() {
  22. if (globalObject["html2canvas"]) {
  23. return Promise.resolve(globalObject["html2canvas"]);
  24. }
  25. // @if MODULE_FORMAT='es'
  26. return import("html2canvas");
  27. // @endif
  28. // @if MODULE_FORMAT!='es'
  29. if (typeof exports === "object" && typeof module !== "undefined") {
  30. return new Promise(function(resolve, reject) {
  31. try {
  32. resolve(require("html2canvas"));
  33. } catch (e) {
  34. reject(e);
  35. }
  36. });
  37. }
  38. if (typeof define === "function" && define.amd) {
  39. return new Promise(function(resolve, reject) {
  40. try {
  41. require(["html2canvas"], resolve);
  42. } catch (e) {
  43. reject(e);
  44. }
  45. });
  46. }
  47. return Promise.reject(new Error("Could not load html2canvas"));
  48. // @endif
  49. })()
  50. .catch(function(e) {
  51. return Promise.reject(new Error("Could not load html2canvas: " + e));
  52. })
  53. .then(function(html2canvas) {
  54. return html2canvas.default ? html2canvas.default : html2canvas;
  55. });
  56. }
  57. function loadDomPurify() {
  58. return (function() {
  59. if (globalObject["DOMPurify"]) {
  60. return Promise.resolve(globalObject["DOMPurify"]);
  61. }
  62. // @if MODULE_FORMAT='es'
  63. return import("dompurify");
  64. // @endif
  65. // @if MODULE_FORMAT!='es'
  66. if (typeof exports === "object" && typeof module !== "undefined") {
  67. return new Promise(function(resolve, reject) {
  68. try {
  69. resolve(require("dompurify"));
  70. } catch (e) {
  71. reject(e);
  72. }
  73. });
  74. }
  75. if (typeof define === "function" && define.amd) {
  76. return new Promise(function(resolve, reject) {
  77. try {
  78. require(["dompurify"], resolve);
  79. } catch (e) {
  80. reject(e);
  81. }
  82. });
  83. }
  84. return Promise.reject(new Error("Could not load dompurify"));
  85. // @endif
  86. })()
  87. .catch(function(e) {
  88. return Promise.reject(new Error("Could not load dompurify: " + e));
  89. })
  90. .then(function(dompurify) {
  91. return dompurify.default ? dompurify.default : dompurify;
  92. });
  93. }
  94. /**
  95. * Determine the type of a variable/object.
  96. *
  97. * @private
  98. * @ignore
  99. */
  100. var objType = function(obj) {
  101. var type = typeof obj;
  102. if (type === "undefined") return "undefined";
  103. else if (type === "string" || obj instanceof String) return "string";
  104. else if (type === "number" || obj instanceof Number) return "number";
  105. else if (type === "function" || obj instanceof Function) return "function";
  106. else if (!!obj && obj.constructor === Array) return "array";
  107. else if (obj && obj.nodeType === 1) return "element";
  108. else if (type === "object") return "object";
  109. else return "unknown";
  110. };
  111. /**
  112. * Create an HTML element with optional className, innerHTML, and style.
  113. *
  114. * @private
  115. * @ignore
  116. */
  117. var createElement = function(tagName, opt) {
  118. var el = document.createElement(tagName);
  119. if (opt.className) el.className = opt.className;
  120. if (opt.innerHTML && opt.dompurify) {
  121. el.innerHTML = opt.dompurify.sanitize(opt.innerHTML);
  122. }
  123. for (var key in opt.style) {
  124. el.style[key] = opt.style[key];
  125. }
  126. return el;
  127. };
  128. /**
  129. * Deep-clone a node and preserve contents/properties.
  130. *
  131. * @private
  132. * @ignore
  133. */
  134. var cloneNode = function(node, javascriptEnabled) {
  135. // Recursively clone the node.
  136. var clone =
  137. node.nodeType === 3
  138. ? document.createTextNode(node.nodeValue)
  139. : node.cloneNode(false);
  140. for (var child = node.firstChild; child; child = child.nextSibling) {
  141. if (
  142. javascriptEnabled === true ||
  143. child.nodeType !== 1 ||
  144. child.nodeName !== "SCRIPT"
  145. ) {
  146. clone.appendChild(cloneNode(child, javascriptEnabled));
  147. }
  148. }
  149. if (node.nodeType === 1) {
  150. // Preserve contents/properties of special nodes.
  151. if (node.nodeName === "CANVAS") {
  152. clone.width = node.width;
  153. clone.height = node.height;
  154. clone.getContext("2d").drawImage(node, 0, 0);
  155. } else if (node.nodeName === "TEXTAREA" || node.nodeName === "SELECT") {
  156. clone.value = node.value;
  157. }
  158. // Preserve the node's scroll position when it loads.
  159. clone.addEventListener(
  160. "load",
  161. function() {
  162. clone.scrollTop = node.scrollTop;
  163. clone.scrollLeft = node.scrollLeft;
  164. },
  165. true
  166. );
  167. }
  168. // Return the cloned node.
  169. return clone;
  170. };
  171. /* ----- CONSTRUCTOR ----- */
  172. var Worker = function Worker(opt) {
  173. // Create the root parent for the proto chain, and the starting Worker.
  174. var root = Object.assign(
  175. Worker.convert(Promise.resolve()),
  176. JSON.parse(JSON.stringify(Worker.template))
  177. );
  178. var self = Worker.convert(Promise.resolve(), root);
  179. // Set progress, optional settings, and return.
  180. self = self.setProgress(1, Worker, 1, [Worker]);
  181. self = self.set(opt);
  182. return self;
  183. };
  184. // Boilerplate for subclassing Promise.
  185. Worker.prototype = Object.create(Promise.prototype);
  186. Worker.prototype.constructor = Worker;
  187. // Converts/casts promises into Workers.
  188. Worker.convert = function convert(promise, inherit) {
  189. // Uses prototypal inheritance to receive changes made to ancestors' properties.
  190. promise.__proto__ = inherit || Worker.prototype;
  191. return promise;
  192. };
  193. Worker.template = {
  194. prop: {
  195. src: null,
  196. container: null,
  197. overlay: null,
  198. canvas: null,
  199. img: null,
  200. pdf: null,
  201. pageSize: null,
  202. callback: function() {}
  203. },
  204. progress: {
  205. val: 0,
  206. state: null,
  207. n: 0,
  208. stack: []
  209. },
  210. opt: {
  211. filename: "file.pdf",
  212. margin: [0, 0, 0, 0],
  213. enableLinks: true,
  214. x: 0,
  215. y: 0,
  216. html2canvas: {},
  217. jsPDF: {},
  218. backgroundColor: "transparent"
  219. }
  220. };
  221. /* ----- FROM / TO ----- */
  222. Worker.prototype.from = function from(src, type) {
  223. function getType(src) {
  224. switch (objType(src)) {
  225. case "string":
  226. return "string";
  227. case "element":
  228. return src.nodeName.toLowerCase() === "canvas" ? "canvas" : "element";
  229. default:
  230. return "unknown";
  231. }
  232. }
  233. return this.then(function from_main() {
  234. type = type || getType(src);
  235. switch (type) {
  236. case "string":
  237. return this.then(loadDomPurify).then(function(dompurify) {
  238. return this.set({
  239. src: createElement("div", {
  240. innerHTML: src,
  241. dompurify: dompurify
  242. })
  243. });
  244. });
  245. case "element":
  246. return this.set({ src: src });
  247. case "canvas":
  248. return this.set({ canvas: src });
  249. case "img":
  250. return this.set({ img: src });
  251. default:
  252. return this.error("Unknown source type.");
  253. }
  254. });
  255. };
  256. Worker.prototype.to = function to(target) {
  257. // Route the 'to' request to the appropriate method.
  258. switch (target) {
  259. case "container":
  260. return this.toContainer();
  261. case "canvas":
  262. return this.toCanvas();
  263. case "img":
  264. return this.toImg();
  265. case "pdf":
  266. return this.toPdf();
  267. default:
  268. return this.error("Invalid target.");
  269. }
  270. };
  271. Worker.prototype.toContainer = function toContainer() {
  272. // Set up function prerequisites.
  273. var prereqs = [
  274. function checkSrc() {
  275. return (
  276. this.prop.src || this.error("Cannot duplicate - no source HTML.")
  277. );
  278. },
  279. function checkPageSize() {
  280. return this.prop.pageSize || this.setPageSize();
  281. }
  282. ];
  283. return this.thenList(prereqs).then(function toContainer_main() {
  284. // Define the CSS styles for the container and its overlay parent.
  285. var overlayCSS = {
  286. position: "fixed",
  287. overflow: "hidden",
  288. zIndex: 1000,
  289. left: "-100000px",
  290. right: 0,
  291. bottom: 0,
  292. top: 0
  293. };
  294. var containerCSS = {
  295. position: "relative",
  296. display: "inline-block",
  297. width:
  298. (typeof this.opt.width === "number" &&
  299. !isNaN(this.opt.width) &&
  300. typeof this.opt.windowWidth === "number" &&
  301. !isNaN(this.opt.windowWidth)
  302. ? this.opt.windowWidth
  303. : Math.max(
  304. this.prop.src.clientWidth,
  305. this.prop.src.scrollWidth,
  306. this.prop.src.offsetWidth
  307. )) + "px",
  308. left: 0,
  309. right: 0,
  310. top: 0,
  311. margin: "auto",
  312. backgroundColor: this.opt.backgroundColor
  313. }; // Set the overlay to hidden (could be changed in the future to provide a print preview).
  314. var source = cloneNode(
  315. this.prop.src,
  316. this.opt.html2canvas.javascriptEnabled
  317. );
  318. if (source.tagName === "BODY") {
  319. containerCSS.height =
  320. Math.max(
  321. document.body.scrollHeight,
  322. document.body.offsetHeight,
  323. document.documentElement.clientHeight,
  324. document.documentElement.scrollHeight,
  325. document.documentElement.offsetHeight
  326. ) + "px";
  327. }
  328. this.prop.overlay = createElement("div", {
  329. className: "html2pdf__overlay",
  330. style: overlayCSS
  331. });
  332. this.prop.container = createElement("div", {
  333. className: "html2pdf__container",
  334. style: containerCSS
  335. });
  336. this.prop.container.appendChild(source);
  337. this.prop.container.firstChild.appendChild(
  338. createElement("div", {
  339. style: {
  340. clear: "both",
  341. border: "0 none transparent",
  342. margin: 0,
  343. padding: 0,
  344. height: 0
  345. }
  346. })
  347. );
  348. this.prop.container.style.float = "none";
  349. this.prop.overlay.appendChild(this.prop.container);
  350. document.body.appendChild(this.prop.overlay);
  351. this.prop.container.firstChild.style.position = "relative";
  352. this.prop.container.height =
  353. Math.max(
  354. this.prop.container.firstChild.clientHeight,
  355. this.prop.container.firstChild.scrollHeight,
  356. this.prop.container.firstChild.offsetHeight
  357. ) + "px";
  358. });
  359. };
  360. Worker.prototype.toCanvas = function toCanvas() {
  361. // Set up function prerequisites.
  362. var prereqs = [
  363. function checkContainer() {
  364. return (
  365. document.body.contains(this.prop.container) || this.toContainer()
  366. );
  367. }
  368. ];
  369. // Fulfill prereqs then create the canvas.
  370. return this.thenList(prereqs)
  371. .then(loadHtml2Canvas)
  372. .then(function toCanvas_main(html2canvas) {
  373. // Handle old-fashioned 'onrendered' argument.
  374. var options = Object.assign({}, this.opt.html2canvas);
  375. delete options.onrendered;
  376. return html2canvas(this.prop.container, options);
  377. })
  378. .then(function toCanvas_post(canvas) {
  379. // Handle old-fashioned 'onrendered' argument.
  380. var onRendered = this.opt.html2canvas.onrendered || function() {};
  381. onRendered(canvas);
  382. this.prop.canvas = canvas;
  383. document.body.removeChild(this.prop.overlay);
  384. });
  385. };
  386. Worker.prototype.toContext2d = function toContext2d() {
  387. // Set up function prerequisites.
  388. var prereqs = [
  389. function checkContainer() {
  390. return (
  391. document.body.contains(this.prop.container) || this.toContainer()
  392. );
  393. }
  394. ];
  395. // Fulfill prereqs then create the canvas.
  396. return this.thenList(prereqs)
  397. .then(loadHtml2Canvas)
  398. .then(function toContext2d_main(html2canvas) {
  399. // Handle old-fashioned 'onrendered' argument.
  400. var pdf = this.opt.jsPDF;
  401. var fontFaces = this.opt.fontFaces;
  402. var scale =
  403. typeof this.opt.width === "number" &&
  404. !isNaN(this.opt.width) &&
  405. typeof this.opt.windowWidth === "number" &&
  406. !isNaN(this.opt.windowWidth)
  407. ? this.opt.width / this.opt.windowWidth
  408. : 1;
  409. var options = Object.assign(
  410. {
  411. async: true,
  412. allowTaint: true,
  413. scale: scale,
  414. scrollX: this.opt.scrollX || 0,
  415. scrollY: this.opt.scrollY || 0,
  416. backgroundColor: "#ffffff",
  417. imageTimeout: 15000,
  418. logging: true,
  419. proxy: null,
  420. removeContainer: true,
  421. foreignObjectRendering: false,
  422. useCORS: false
  423. },
  424. this.opt.html2canvas
  425. );
  426. delete options.onrendered;
  427. pdf.context2d.autoPaging =
  428. typeof this.opt.autoPaging === "undefined"
  429. ? true
  430. : this.opt.autoPaging;
  431. pdf.context2d.posX = this.opt.x;
  432. pdf.context2d.posY = this.opt.y;
  433. pdf.context2d.margin = this.opt.margin;
  434. pdf.context2d.fontFaces = fontFaces;
  435. if (fontFaces) {
  436. for (var i = 0; i < fontFaces.length; ++i) {
  437. var font = fontFaces[i];
  438. var src = font.src.find(function(src) {
  439. return src.format === "truetype";
  440. });
  441. if (src) {
  442. pdf.addFont(src.url, font.ref.name, font.ref.style);
  443. }
  444. }
  445. }
  446. options.windowHeight = options.windowHeight || 0;
  447. options.windowHeight =
  448. options.windowHeight == 0
  449. ? Math.max(
  450. this.prop.container.clientHeight,
  451. this.prop.container.scrollHeight,
  452. this.prop.container.offsetHeight
  453. )
  454. : options.windowHeight;
  455. pdf.context2d.save(true);
  456. return html2canvas(this.prop.container, options);
  457. })
  458. .then(function toContext2d_post(canvas) {
  459. this.opt.jsPDF.context2d.restore(true);
  460. // Handle old-fashioned 'onrendered' argument.
  461. var onRendered = this.opt.html2canvas.onrendered || function() {};
  462. onRendered(canvas);
  463. this.prop.canvas = canvas;
  464. document.body.removeChild(this.prop.overlay);
  465. });
  466. };
  467. Worker.prototype.toImg = function toImg() {
  468. // Set up function prerequisites.
  469. var prereqs = [
  470. function checkCanvas() {
  471. return this.prop.canvas || this.toCanvas();
  472. }
  473. ];
  474. // Fulfill prereqs then create the image.
  475. return this.thenList(prereqs).then(function toImg_main() {
  476. var imgData = this.prop.canvas.toDataURL(
  477. "image/" + this.opt.image.type,
  478. this.opt.image.quality
  479. );
  480. this.prop.img = document.createElement("img");
  481. this.prop.img.src = imgData;
  482. });
  483. };
  484. Worker.prototype.toPdf = function toPdf() {
  485. // Set up function prerequisites.
  486. var prereqs = [
  487. function checkContext2d() {
  488. return this.toContext2d();
  489. }
  490. //function checkCanvas() { return this.prop.canvas || this.toCanvas(); }
  491. ];
  492. // Fulfill prereqs then create the image.
  493. return this.thenList(prereqs).then(function toPdf_main() {
  494. // Create local copies of frequently used properties.
  495. this.prop.pdf = this.prop.pdf || this.opt.jsPDF;
  496. });
  497. };
  498. /* ----- OUTPUT / SAVE ----- */
  499. Worker.prototype.output = function output(type, options, src) {
  500. // Redirect requests to the correct function (outputPdf / outputImg).
  501. src = src || "pdf";
  502. if (src.toLowerCase() === "img" || src.toLowerCase() === "image") {
  503. return this.outputImg(type, options);
  504. } else {
  505. return this.outputPdf(type, options);
  506. }
  507. };
  508. Worker.prototype.outputPdf = function outputPdf(type, options) {
  509. // Set up function prerequisites.
  510. var prereqs = [
  511. function checkPdf() {
  512. return this.prop.pdf || this.toPdf();
  513. }
  514. ];
  515. // Fulfill prereqs then perform the appropriate output.
  516. return this.thenList(prereqs).then(function outputPdf_main() {
  517. /* Currently implemented output types:
  518. * https://rawgit.com/MrRio/jsPDF/master/docs/jspdf.js.html#line992
  519. * save(options), arraybuffer, blob, bloburi/bloburl,
  520. * datauristring/dataurlstring, dataurlnewwindow, datauri/dataurl
  521. */
  522. return this.prop.pdf.output(type, options);
  523. });
  524. };
  525. Worker.prototype.outputImg = function outputImg(type) {
  526. // Set up function prerequisites.
  527. var prereqs = [
  528. function checkImg() {
  529. return this.prop.img || this.toImg();
  530. }
  531. ];
  532. // Fulfill prereqs then perform the appropriate output.
  533. return this.thenList(prereqs).then(function outputImg_main() {
  534. switch (type) {
  535. case undefined:
  536. case "img":
  537. return this.prop.img;
  538. case "datauristring":
  539. case "dataurlstring":
  540. return this.prop.img.src;
  541. case "datauri":
  542. case "dataurl":
  543. return (document.location.href = this.prop.img.src);
  544. default:
  545. throw 'Image output type "' + type + '" is not supported.';
  546. }
  547. });
  548. };
  549. Worker.prototype.save = function save(filename) {
  550. // Set up function prerequisites.
  551. var prereqs = [
  552. function checkPdf() {
  553. return this.prop.pdf || this.toPdf();
  554. }
  555. ];
  556. // Fulfill prereqs, update the filename (if provided), and save the PDF.
  557. return this.thenList(prereqs)
  558. .set(filename ? { filename: filename } : null)
  559. .then(function save_main() {
  560. this.prop.pdf.save(this.opt.filename);
  561. });
  562. };
  563. Worker.prototype.doCallback = function doCallback() {
  564. // Set up function prerequisites.
  565. var prereqs = [
  566. function checkPdf() {
  567. return this.prop.pdf || this.toPdf();
  568. }
  569. ];
  570. // Fulfill prereqs, update the filename (if provided), and save the PDF.
  571. return this.thenList(prereqs).then(function doCallback_main() {
  572. this.prop.callback(this.prop.pdf);
  573. });
  574. };
  575. /* ----- SET / GET ----- */
  576. Worker.prototype.set = function set(opt) {
  577. // TODO: Implement ordered pairs?
  578. // Silently ignore invalid or empty input.
  579. if (objType(opt) !== "object") {
  580. return this;
  581. }
  582. // Build an array of setter functions to queue.
  583. var fns = Object.keys(opt || {}).map(function(key) {
  584. if (key in Worker.template.prop) {
  585. // Set pre-defined properties.
  586. return function set_prop() {
  587. this.prop[key] = opt[key];
  588. };
  589. } else {
  590. switch (key) {
  591. case "margin":
  592. return this.setMargin.bind(this, opt.margin);
  593. case "jsPDF":
  594. return function set_jsPDF() {
  595. this.opt.jsPDF = opt.jsPDF;
  596. return this.setPageSize();
  597. };
  598. case "pageSize":
  599. return this.setPageSize.bind(this, opt.pageSize);
  600. default:
  601. // Set any other properties in opt.
  602. return function set_opt() {
  603. this.opt[key] = opt[key];
  604. };
  605. }
  606. }
  607. }, this);
  608. // Set properties within the promise chain.
  609. return this.then(function set_main() {
  610. return this.thenList(fns);
  611. });
  612. };
  613. Worker.prototype.get = function get(key, cbk) {
  614. return this.then(function get_main() {
  615. // Fetch the requested property, either as a predefined prop or in opt.
  616. var val = key in Worker.template.prop ? this.prop[key] : this.opt[key];
  617. return cbk ? cbk(val) : val;
  618. });
  619. };
  620. Worker.prototype.setMargin = function setMargin(margin) {
  621. return this.then(function setMargin_main() {
  622. // Parse the margin property.
  623. switch (objType(margin)) {
  624. case "number":
  625. margin = [margin, margin, margin, margin];
  626. // eslint-disable-next-line no-fallthrough
  627. case "array":
  628. if (margin.length === 2) {
  629. margin = [margin[0], margin[1], margin[0], margin[1]];
  630. }
  631. if (margin.length === 4) {
  632. break;
  633. }
  634. // eslint-disable-next-line no-fallthrough
  635. default:
  636. return this.error("Invalid margin array.");
  637. }
  638. // Set the margin property, then update pageSize.
  639. this.opt.margin = margin;
  640. }).then(this.setPageSize);
  641. };
  642. Worker.prototype.setPageSize = function setPageSize(pageSize) {
  643. function toPx(val, k) {
  644. return Math.floor(((val * k) / 72) * 96);
  645. }
  646. return this.then(function setPageSize_main() {
  647. // Retrieve page-size based on jsPDF settings, if not explicitly provided.
  648. pageSize = pageSize || jsPDF.getPageSize(this.opt.jsPDF);
  649. // Add 'inner' field if not present.
  650. if (!pageSize.hasOwnProperty("inner")) {
  651. pageSize.inner = {
  652. width: pageSize.width - this.opt.margin[1] - this.opt.margin[3],
  653. height: pageSize.height - this.opt.margin[0] - this.opt.margin[2]
  654. };
  655. pageSize.inner.px = {
  656. width: toPx(pageSize.inner.width, pageSize.k),
  657. height: toPx(pageSize.inner.height, pageSize.k)
  658. };
  659. pageSize.inner.ratio = pageSize.inner.height / pageSize.inner.width;
  660. }
  661. // Attach pageSize to this.
  662. this.prop.pageSize = pageSize;
  663. });
  664. };
  665. Worker.prototype.setProgress = function setProgress(val, state, n, stack) {
  666. // Immediately update all progress values.
  667. if (val != null) this.progress.val = val;
  668. if (state != null) this.progress.state = state;
  669. if (n != null) this.progress.n = n;
  670. if (stack != null) this.progress.stack = stack;
  671. this.progress.ratio = this.progress.val / this.progress.state;
  672. // Return this for command chaining.
  673. return this;
  674. };
  675. Worker.prototype.updateProgress = function updateProgress(
  676. val,
  677. state,
  678. n,
  679. stack
  680. ) {
  681. // Immediately update all progress values, using setProgress.
  682. return this.setProgress(
  683. val ? this.progress.val + val : null,
  684. state ? state : null,
  685. n ? this.progress.n + n : null,
  686. stack ? this.progress.stack.concat(stack) : null
  687. );
  688. };
  689. /* ----- PROMISE MAPPING ----- */
  690. Worker.prototype.then = function then(onFulfilled, onRejected) {
  691. // Wrap `this` for encapsulation.
  692. var self = this;
  693. return this.thenCore(onFulfilled, onRejected, function then_main(
  694. onFulfilled,
  695. onRejected
  696. ) {
  697. // Update progress while queuing, calling, and resolving `then`.
  698. self.updateProgress(null, null, 1, [onFulfilled]);
  699. return Promise.prototype.then
  700. .call(this, function then_pre(val) {
  701. self.updateProgress(null, onFulfilled);
  702. return val;
  703. })
  704. .then(onFulfilled, onRejected)
  705. .then(function then_post(val) {
  706. self.updateProgress(1);
  707. return val;
  708. });
  709. });
  710. };
  711. Worker.prototype.thenCore = function thenCore(
  712. onFulfilled,
  713. onRejected,
  714. thenBase
  715. ) {
  716. // Handle optional thenBase parameter.
  717. thenBase = thenBase || Promise.prototype.then;
  718. // Wrap `this` for encapsulation and bind it to the promise handlers.
  719. var self = this;
  720. if (onFulfilled) {
  721. onFulfilled = onFulfilled.bind(self);
  722. }
  723. if (onRejected) {
  724. onRejected = onRejected.bind(self);
  725. }
  726. // Cast self into a Promise to avoid polyfills recursively defining `then`.
  727. var isNative =
  728. Promise.toString().indexOf("[native code]") !== -1 &&
  729. Promise.name === "Promise";
  730. var selfPromise = isNative
  731. ? self
  732. : Worker.convert(Object.assign({}, self), Promise.prototype);
  733. // Return the promise, after casting it into a Worker and preserving props.
  734. var returnVal = thenBase.call(selfPromise, onFulfilled, onRejected);
  735. return Worker.convert(returnVal, self.__proto__);
  736. };
  737. Worker.prototype.thenExternal = function thenExternal(
  738. onFulfilled,
  739. onRejected
  740. ) {
  741. // Call `then` and return a standard promise (exits the Worker chain).
  742. return Promise.prototype.then.call(this, onFulfilled, onRejected);
  743. };
  744. Worker.prototype.thenList = function thenList(fns) {
  745. // Queue a series of promise 'factories' into the promise chain.
  746. var self = this;
  747. fns.forEach(function thenList_forEach(fn) {
  748. self = self.thenCore(fn);
  749. });
  750. return self;
  751. };
  752. Worker.prototype["catch"] = function(onRejected) {
  753. // Bind `this` to the promise handler, call `catch`, and return a Worker.
  754. if (onRejected) {
  755. onRejected = onRejected.bind(this);
  756. }
  757. var returnVal = Promise.prototype["catch"].call(this, onRejected);
  758. return Worker.convert(returnVal, this);
  759. };
  760. Worker.prototype.catchExternal = function catchExternal(onRejected) {
  761. // Call `catch` and return a standard promise (exits the Worker chain).
  762. return Promise.prototype["catch"].call(this, onRejected);
  763. };
  764. Worker.prototype.error = function error(msg) {
  765. // Throw the error in the Promise chain.
  766. return this.then(function error_main() {
  767. throw new Error(msg);
  768. });
  769. };
  770. /* ----- ALIASES ----- */
  771. Worker.prototype.using = Worker.prototype.set;
  772. Worker.prototype.saveAs = Worker.prototype.save;
  773. Worker.prototype.export = Worker.prototype.output;
  774. Worker.prototype.run = Worker.prototype.then;
  775. // Get dimensions of a PDF page, as determined by jsPDF.
  776. jsPDF.getPageSize = function(orientation, unit, format) {
  777. // Decode options object
  778. if (typeof orientation === "object") {
  779. var options = orientation;
  780. orientation = options.orientation;
  781. unit = options.unit || unit;
  782. format = options.format || format;
  783. }
  784. // Default options
  785. unit = unit || "mm";
  786. format = format || "a4";
  787. orientation = ("" + (orientation || "P")).toLowerCase();
  788. var format_as_string = ("" + format).toLowerCase();
  789. // Size in pt of various paper formats
  790. var pageFormats = {
  791. a0: [2383.94, 3370.39],
  792. a1: [1683.78, 2383.94],
  793. a2: [1190.55, 1683.78],
  794. a3: [841.89, 1190.55],
  795. a4: [595.28, 841.89],
  796. a5: [419.53, 595.28],
  797. a6: [297.64, 419.53],
  798. a7: [209.76, 297.64],
  799. a8: [147.4, 209.76],
  800. a9: [104.88, 147.4],
  801. a10: [73.7, 104.88],
  802. b0: [2834.65, 4008.19],
  803. b1: [2004.09, 2834.65],
  804. b2: [1417.32, 2004.09],
  805. b3: [1000.63, 1417.32],
  806. b4: [708.66, 1000.63],
  807. b5: [498.9, 708.66],
  808. b6: [354.33, 498.9],
  809. b7: [249.45, 354.33],
  810. b8: [175.75, 249.45],
  811. b9: [124.72, 175.75],
  812. b10: [87.87, 124.72],
  813. c0: [2599.37, 3676.54],
  814. c1: [1836.85, 2599.37],
  815. c2: [1298.27, 1836.85],
  816. c3: [918.43, 1298.27],
  817. c4: [649.13, 918.43],
  818. c5: [459.21, 649.13],
  819. c6: [323.15, 459.21],
  820. c7: [229.61, 323.15],
  821. c8: [161.57, 229.61],
  822. c9: [113.39, 161.57],
  823. c10: [79.37, 113.39],
  824. dl: [311.81, 623.62],
  825. letter: [612, 792],
  826. "government-letter": [576, 756],
  827. legal: [612, 1008],
  828. "junior-legal": [576, 360],
  829. ledger: [1224, 792],
  830. tabloid: [792, 1224],
  831. "credit-card": [153, 243]
  832. };
  833. var k;
  834. // Unit conversion
  835. switch (unit) {
  836. case "pt":
  837. k = 1;
  838. break;
  839. case "mm":
  840. k = 72 / 25.4;
  841. break;
  842. case "cm":
  843. k = 72 / 2.54;
  844. break;
  845. case "in":
  846. k = 72;
  847. break;
  848. case "px":
  849. k = 72 / 96;
  850. break;
  851. case "pc":
  852. k = 12;
  853. break;
  854. case "em":
  855. k = 12;
  856. break;
  857. case "ex":
  858. k = 6;
  859. break;
  860. default:
  861. throw "Invalid unit: " + unit;
  862. }
  863. var pageHeight = 0;
  864. var pageWidth = 0;
  865. // Dimensions are stored as user units and converted to points on output
  866. if (pageFormats.hasOwnProperty(format_as_string)) {
  867. pageHeight = pageFormats[format_as_string][1] / k;
  868. pageWidth = pageFormats[format_as_string][0] / k;
  869. } else {
  870. try {
  871. pageHeight = format[1];
  872. pageWidth = format[0];
  873. } catch (err) {
  874. throw new Error("Invalid format: " + format);
  875. }
  876. }
  877. var tmp;
  878. // Handle page orientation
  879. if (orientation === "p" || orientation === "portrait") {
  880. orientation = "p";
  881. if (pageWidth > pageHeight) {
  882. tmp = pageWidth;
  883. pageWidth = pageHeight;
  884. pageHeight = tmp;
  885. }
  886. } else if (orientation === "l" || orientation === "landscape") {
  887. orientation = "l";
  888. if (pageHeight > pageWidth) {
  889. tmp = pageWidth;
  890. pageWidth = pageHeight;
  891. pageHeight = tmp;
  892. }
  893. } else {
  894. throw "Invalid orientation: " + orientation;
  895. }
  896. // Return information (k is the unit conversion ratio from pts)
  897. var info = {
  898. width: pageWidth,
  899. height: pageHeight,
  900. unit: unit,
  901. k: k,
  902. orientation: orientation
  903. };
  904. return info;
  905. };
  906. /**
  907. * @typedef FontFace
  908. *
  909. * The font-face type implements an interface similar to that of the font-face CSS rule,
  910. * and is used by jsPDF to match fonts when the font property of CanvasRenderingContext2D
  911. * is updated.
  912. *
  913. * All properties expect values similar to those in the font-face CSS rule. A difference
  914. * is the font-family, which do not need to be enclosed in double-quotes when containing
  915. * spaces like in CSS.
  916. *
  917. * @property {string} family The name of the font-family.
  918. * @property {string|undefined} style The style that this font-face defines, e.g. 'italic'.
  919. * @property {string|number|undefined} weight The weight of the font, either as a string or a number (400, 500, 600, e.g.)
  920. * @property {string|undefined} stretch The stretch of the font, e.g. condensed, normal, expanded.
  921. * @property {Object[]} src A list of URLs from where fonts of various formats can be fetched.
  922. * @property {string} [src] url A URL to a font of a specific format.
  923. * @property {string} [src] format Format of the font referenced by the URL.
  924. */
  925. /**
  926. * Generate a PDF from an HTML element or string using.
  927. *
  928. * @name html
  929. * @function
  930. * @param {HTMLElement|string} source The source HTMLElement or a string containing HTML.
  931. * @param {Object} [options] Collection of settings
  932. * @param {function} [options.callback] The mandatory callback-function gets as first parameter the current jsPDF instance
  933. * @param {(number|number[])=} [options.margin] Page margins [top, right, bottom, left]. Default is 0.
  934. * @param {(boolean|'slice'|'text')=} [options.autoPaging] The auto paging mode.
  935. * <ul>
  936. * <li>
  937. * <code>false</code>: Auto paging is disabled.
  938. * </li>
  939. * <li>
  940. * <code>true</code> or <code>'slice'</code>: Will cut shapes or text chunks across page breaks. Will possibly
  941. * slice text in half, making it difficult to read.
  942. * </li>
  943. * <li>
  944. * <code>'text'</code>: Trys not to cut text in half across page breaks. Works best for documents consisting
  945. * mostly of a single column of text.
  946. * </li>
  947. * </ul>
  948. * Default is <code>true</code>.
  949. * @param {string} [options.filename] name of the file
  950. * @param {HTMLOptionImage} [options.image] image settings when converting HTML to image
  951. * @param {Html2CanvasOptions} [options.html2canvas] html2canvas options
  952. * @param {FontFace[]} [options.fontFaces] A list of font-faces to match when resolving fonts. Fonts will be added to the PDF based on the specified URL. If omitted, the font match algorithm falls back to old algorithm.
  953. * @param {jsPDF} [options.jsPDF] jsPDF instance
  954. * @param {number=} [options.x] x position on the PDF document in jsPDF units.
  955. * @param {number=} [options.y] y position on the PDF document in jsPDF units.
  956. * @param {number=} [options.width] The target width in the PDF document in jsPDF units. The rendered element will be
  957. * scaled such that it fits into the specified width. Has no effect if either the <code>html2canvas.scale<code> is
  958. * specified or the <code>windowWidth</code> option is NOT specified.
  959. * @param {number=} [options.windowWidth] The window width in CSS pixels. In contrast to the
  960. * <code>html2canvas.windowWidth</code> option, this option affects the actual container size while rendering and
  961. * does NOT affect CSS media queries. This option only has an effect, if the <code>width<code> option is also specified.
  962. *
  963. * @example
  964. * var doc = new jsPDF();
  965. *
  966. * doc.html(document.body, {
  967. * callback: function (doc) {
  968. * doc.save();
  969. * },
  970. * x: 10,
  971. * y: 10
  972. * });
  973. */
  974. jsPDFAPI.html = function(src, options) {
  975. "use strict";
  976. options = options || {};
  977. options.callback = options.callback || function() {};
  978. options.html2canvas = options.html2canvas || {};
  979. options.html2canvas.canvas = options.html2canvas.canvas || this.canvas;
  980. options.jsPDF = options.jsPDF || this;
  981. options.fontFaces = options.fontFaces
  982. ? options.fontFaces.map(normalizeFontFace)
  983. : null;
  984. // Create a new worker with the given options.
  985. var worker = new Worker(options);
  986. if (!options.worker) {
  987. // If worker is not set to true, perform the traditional 'simple' operation.
  988. return worker.from(src).doCallback();
  989. } else {
  990. // Otherwise, return the worker for new Promise-based operation.
  991. return worker;
  992. }
  993. };
  994. })(jsPDF.API);