# modules/html.js

    /**
     * @license
     * Copyright (c) 2018 Erik Koopmans
     * Released under the MIT License.
     *
     * Licensed under the MIT License.
     * http://opensource.org/licenses/mit-license
     */

    import { jsPDF } from "../jspdf.js";
    import { normalizeFontFace } from "../libs/fontFace.js";
    import { globalObject } from "../libs/globalObject.js";

    /**
     * jsPDF html PlugIn
     *
     * @name html
     * @module
     */
    (function(jsPDFAPI) {
      "use strict";

      function loadHtml2Canvas() {
        return (function() {
          if (globalObject["html2canvas"]) {
            return Promise.resolve(globalObject["html2canvas"]);
          }

          // @if MODULE_FORMAT='es'
          return import("html2canvas");
          // @endif

          // @if MODULE_FORMAT!='es'
          if (typeof exports === "object" && typeof module !== "undefined") {
            return new Promise(function(resolve, reject) {
              try {
                resolve(require("html2canvas"));
              } catch (e) {
                reject(e);
              }
            });
          }
          if (typeof define === "function" && define.amd) {
            return new Promise(function(resolve, reject) {
              try {
                require(["html2canvas"], resolve);
              } catch (e) {
                reject(e);
              }
            });
          }
          return Promise.reject(new Error("Could not load html2canvas"));
          // @endif
        })()
          .catch(function(e) {
            return Promise.reject(new Error("Could not load html2canvas: " + e));
          })
          .then(function(html2canvas) {
            return html2canvas.default ? html2canvas.default : html2canvas;
          });
      }

      function loadDomPurify() {
        return (function() {
          if (globalObject["DOMPurify"]) {
            return Promise.resolve(globalObject["DOMPurify"]);
          }

          // @if MODULE_FORMAT='es'
          return import("dompurify");
          // @endif

          // @if MODULE_FORMAT!='es'
          if (typeof exports === "object" && typeof module !== "undefined") {
            return new Promise(function(resolve, reject) {
              try {
                resolve(require("dompurify"));
              } catch (e) {
                reject(e);
              }
            });
          }
          if (typeof define === "function" && define.amd) {
            return new Promise(function(resolve, reject) {
              try {
                require(["dompurify"], resolve);
              } catch (e) {
                reject(e);
              }
            });
          }
          return Promise.reject(new Error("Could not load dompurify"));
          // @endif
        })()
          .catch(function(e) {
            return Promise.reject(new Error("Could not load dompurify: " + e));
          })
          .then(function(dompurify) {
            return dompurify.default ? dompurify.default : dompurify;
          });
      }

      /**
       * Determine the type of a variable/object.
       *
       * @private
       * @ignore
       */
      var objType = function(obj) {
        var type = typeof obj;
        if (type === "undefined") return "undefined";
        else if (type === "string" || obj instanceof String) return "string";
        else if (type === "number" || obj instanceof Number) return "number";
        else if (type === "function" || obj instanceof Function) return "function";
        else if (!!obj && obj.constructor === Array) return "array";
        else if (obj && obj.nodeType === 1) return "element";
        else if (type === "object") return "object";
        else return "unknown";
      };

      /**
       * Create an HTML element with optional className, innerHTML, and style.
       *
       * @private
       * @ignore
       */
      var createElement = function(tagName, opt) {
        var el = document.createElement(tagName);
        if (opt.className) el.className = opt.className;
        if (opt.innerHTML && opt.dompurify) {
          el.innerHTML = opt.dompurify.sanitize(opt.innerHTML);
        }
        for (var key in opt.style) {
          el.style[key] = opt.style[key];
        }
        return el;
      };

      /**
       * Deep-clone a node and preserve contents/properties.
       *
       * @private
       * @ignore
       */
      var cloneNode = function(node, javascriptEnabled) {
        // Recursively clone the node.
        var clone =
          node.nodeType === 3
            ? document.createTextNode(node.nodeValue)
            : node.cloneNode(false);
        for (var child = node.firstChild; child; child = child.nextSibling) {
          if (
            javascriptEnabled === true ||
            child.nodeType !== 1 ||
            child.nodeName !== "SCRIPT"
          ) {
            clone.appendChild(cloneNode(child, javascriptEnabled));
          }
        }

        if (node.nodeType === 1) {
          // Preserve contents/properties of special nodes.
          if (node.nodeName === "CANVAS") {
            clone.width = node.width;
            clone.height = node.height;
            clone.getContext("2d").drawImage(node, 0, 0);
          } else if (node.nodeName === "TEXTAREA" || node.nodeName === "SELECT") {
            clone.value = node.value;
          }

          // Preserve the node's scroll position when it loads.
          clone.addEventListener(
            "load",
            function() {
              clone.scrollTop = node.scrollTop;
              clone.scrollLeft = node.scrollLeft;
            },
            true
          );
        }

        // Return the cloned node.
        return clone;
      };

      /* ----- CONSTRUCTOR ----- */

      var Worker = function Worker(opt) {
        // Create the root parent for the proto chain, and the starting Worker.
        var root = Object.assign(
          Worker.convert(Promise.resolve()),
          JSON.parse(JSON.stringify(Worker.template))
        );
        var self = Worker.convert(Promise.resolve(), root);

        // Set progress, optional settings, and return.
        self = self.setProgress(1, Worker, 1, [Worker]);
        self = self.set(opt);
        return self;
      };

      // Boilerplate for subclassing Promise.
      Worker.prototype = Object.create(Promise.prototype);
      Worker.prototype.constructor = Worker;

      // Converts/casts promises into Workers.
      Worker.convert = function convert(promise, inherit) {
        // Uses prototypal inheritance to receive changes made to ancestors' properties.
        promise.__proto__ = inherit || Worker.prototype;
        return promise;
      };

      Worker.template = {
        prop: {
          src: null,
          container: null,
          overlay: null,
          canvas: null,
          img: null,
          pdf: null,
          pageSize: null,
          callback: function() {}
        },
        progress: {
          val: 0,
          state: null,
          n: 0,
          stack: []
        },
        opt: {
          filename: "file.pdf",
          margin: [0, 0, 0, 0],
          enableLinks: true,
          x: 0,
          y: 0,
          html2canvas: {},
          jsPDF: {},
          backgroundColor: "transparent"
        }
      };

      /* ----- FROM / TO ----- */

      Worker.prototype.from = function from(src, type) {
        function getType(src) {
          switch (objType(src)) {
            case "string":
              return "string";
            case "element":
              return src.nodeName.toLowerCase() === "canvas" ? "canvas" : "element";
            default:
              return "unknown";
          }
        }

        return this.then(function from_main() {
          type = type || getType(src);
          switch (type) {
            case "string":
              return this.then(loadDomPurify).then(function(dompurify) {
                return this.set({
                  src: createElement("div", {
                    innerHTML: src,
                    dompurify: dompurify
                  })
                });
              });
            case "element":
              return this.set({ src: src });
            case "canvas":
              return this.set({ canvas: src });
            case "img":
              return this.set({ img: src });
            default:
              return this.error("Unknown source type.");
          }
        });
      };

      Worker.prototype.to = function to(target) {
        // Route the 'to' request to the appropriate method.
        switch (target) {
          case "container":
            return this.toContainer();
          case "canvas":
            return this.toCanvas();
          case "img":
            return this.toImg();
          case "pdf":
            return this.toPdf();
          default:
            return this.error("Invalid target.");
        }
      };

      Worker.prototype.toContainer = function toContainer() {
        // Set up function prerequisites.
        var prereqs = [
          function checkSrc() {
            return (
              this.prop.src || this.error("Cannot duplicate - no source HTML.")
            );
          },
          function checkPageSize() {
            return this.prop.pageSize || this.setPageSize();
          }
        ];
        return this.thenList(prereqs).then(function toContainer_main() {
          // Define the CSS styles for the container and its overlay parent.
          var overlayCSS = {
            position: "fixed",
            overflow: "hidden",
            zIndex: 1000,
            left: "-100000px",
            right: 0,
            bottom: 0,
            top: 0
          };
          var containerCSS = {
            position: "relative",
            display: "inline-block",
            width:
              (typeof this.opt.width === "number" &&
              !isNaN(this.opt.width) &&
              typeof this.opt.windowWidth === "number" &&
              !isNaN(this.opt.windowWidth)
                ? this.opt.windowWidth
                : Math.max(
                    this.prop.src.clientWidth,
                    this.prop.src.scrollWidth,
                    this.prop.src.offsetWidth
                  )) + "px",
            left: 0,
            right: 0,
            top: 0,
            margin: "auto",
            backgroundColor: this.opt.backgroundColor
          }; // Set the overlay to hidden (could be changed in the future to provide a print preview).

          var source = cloneNode(
            this.prop.src,
            this.opt.html2canvas.javascriptEnabled
          );

          if (source.tagName === "BODY") {
            containerCSS.height =
              Math.max(
                document.body.scrollHeight,
                document.body.offsetHeight,
                document.documentElement.clientHeight,
                document.documentElement.scrollHeight,
                document.documentElement.offsetHeight
              ) + "px";
          }

          this.prop.overlay = createElement("div", {
            className: "html2pdf__overlay",
            style: overlayCSS
          });
          this.prop.container = createElement("div", {
            className: "html2pdf__container",
            style: containerCSS
          });
          this.prop.container.appendChild(source);
          this.prop.container.firstChild.appendChild(
            createElement("div", {
              style: {
                clear: "both",
                border: "0 none transparent",
                margin: 0,
                padding: 0,
                height: 0
              }
            })
          );
          this.prop.container.style.float = "none";
          this.prop.overlay.appendChild(this.prop.container);
          document.body.appendChild(this.prop.overlay);
          this.prop.container.firstChild.style.position = "relative";
          this.prop.container.height =
            Math.max(
              this.prop.container.firstChild.clientHeight,
              this.prop.container.firstChild.scrollHeight,
              this.prop.container.firstChild.offsetHeight
            ) + "px";
        });
      };

      Worker.prototype.toCanvas = function toCanvas() {
        // Set up function prerequisites.
        var prereqs = [
          function checkContainer() {
            return (
              document.body.contains(this.prop.container) || this.toContainer()
            );
          }
        ];

        // Fulfill prereqs then create the canvas.
        return this.thenList(prereqs)
          .then(loadHtml2Canvas)
          .then(function toCanvas_main(html2canvas) {
            // Handle old-fashioned 'onrendered' argument.
            var options = Object.assign({}, this.opt.html2canvas);
            delete options.onrendered;

            return html2canvas(this.prop.container, options);
          })
          .then(function toCanvas_post(canvas) {
            // Handle old-fashioned 'onrendered' argument.
            var onRendered = this.opt.html2canvas.onrendered || function() {};
            onRendered(canvas);

            this.prop.canvas = canvas;
            document.body.removeChild(this.prop.overlay);
          });
      };

      Worker.prototype.toContext2d = function toContext2d() {
        // Set up function prerequisites.
        var prereqs = [
          function checkContainer() {
            return (
              document.body.contains(this.prop.container) || this.toContainer()
            );
          }
        ];

        // Fulfill prereqs then create the canvas.
        return this.thenList(prereqs)
          .then(loadHtml2Canvas)
          .then(function toContext2d_main(html2canvas) {
            // Handle old-fashioned 'onrendered' argument.

            var pdf = this.opt.jsPDF;
            var fontFaces = this.opt.fontFaces;

            var scale =
              typeof this.opt.width === "number" &&
              !isNaN(this.opt.width) &&
              typeof this.opt.windowWidth === "number" &&
              !isNaN(this.opt.windowWidth)
                ? this.opt.width / this.opt.windowWidth
                : 1;

            var options = Object.assign(
              {
                async: true,
                allowTaint: true,
                scale: scale,
                scrollX: this.opt.scrollX || 0,
                scrollY: this.opt.scrollY || 0,
                backgroundColor: "#ffffff",
                imageTimeout: 15000,
                logging: true,
                proxy: null,
                removeContainer: true,
                foreignObjectRendering: false,
                useCORS: false
              },
              this.opt.html2canvas
            );
            delete options.onrendered;

            pdf.context2d.autoPaging =
              typeof this.opt.autoPaging === "undefined"
                ? true
                : this.opt.autoPaging;
            pdf.context2d.posX = this.opt.x;
            pdf.context2d.posY = this.opt.y;
            pdf.context2d.margin = this.opt.margin;
            pdf.context2d.fontFaces = fontFaces;

            if (fontFaces) {
              for (var i = 0; i < fontFaces.length; ++i) {
                var font = fontFaces[i];
                var src = font.src.find(function(src) {
                  return src.format === "truetype";
                });

                if (src) {
                  pdf.addFont(src.url, font.ref.name, font.ref.style);
                }
              }
            }

            options.windowHeight = options.windowHeight || 0;
            options.windowHeight =
              options.windowHeight == 0
                ? Math.max(
                    this.prop.container.clientHeight,
                    this.prop.container.scrollHeight,
                    this.prop.container.offsetHeight
                  )
                : options.windowHeight;

            pdf.context2d.save(true);
            return html2canvas(this.prop.container, options);
          })
          .then(function toContext2d_post(canvas) {
            this.opt.jsPDF.context2d.restore(true);

            // Handle old-fashioned 'onrendered' argument.
            var onRendered = this.opt.html2canvas.onrendered || function() {};
            onRendered(canvas);

            this.prop.canvas = canvas;
            document.body.removeChild(this.prop.overlay);
          });
      };

      Worker.prototype.toImg = function toImg() {
        // Set up function prerequisites.
        var prereqs = [
          function checkCanvas() {
            return this.prop.canvas || this.toCanvas();
          }
        ];

        // Fulfill prereqs then create the image.
        return this.thenList(prereqs).then(function toImg_main() {
          var imgData = this.prop.canvas.toDataURL(
            "image/" + this.opt.image.type,
            this.opt.image.quality
          );
          this.prop.img = document.createElement("img");
          this.prop.img.src = imgData;
        });
      };

      Worker.prototype.toPdf = function toPdf() {
        // Set up function prerequisites.
        var prereqs = [
          function checkContext2d() {
            return this.toContext2d();
          }
          //function checkCanvas() { return this.prop.canvas || this.toCanvas(); }
        ];

        // Fulfill prereqs then create the image.
        return this.thenList(prereqs).then(function toPdf_main() {
          // Create local copies of frequently used properties.
          this.prop.pdf = this.prop.pdf || this.opt.jsPDF;
        });
      };

      /* ----- OUTPUT / SAVE ----- */

      Worker.prototype.output = function output(type, options, src) {
        // Redirect requests to the correct function (outputPdf / outputImg).
        src = src || "pdf";
        if (src.toLowerCase() === "img" || src.toLowerCase() === "image") {
          return this.outputImg(type, options);
        } else {
          return this.outputPdf(type, options);
        }
      };

      Worker.prototype.outputPdf = function outputPdf(type, options) {
        // Set up function prerequisites.
        var prereqs = [
          function checkPdf() {
            return this.prop.pdf || this.toPdf();
          }
        ];

        // Fulfill prereqs then perform the appropriate output.
        return this.thenList(prereqs).then(function outputPdf_main() {
          /* Currently implemented output types:
           *    https://rawgit.com/MrRio/jsPDF/master/docs/jspdf.js.html#line992
           *  save(options), arraybuffer, blob, bloburi/bloburl,
           *  datauristring/dataurlstring, dataurlnewwindow, datauri/dataurl
           */
          return this.prop.pdf.output(type, options);
        });
      };

      Worker.prototype.outputImg = function outputImg(type) {
        // Set up function prerequisites.
        var prereqs = [
          function checkImg() {
            return this.prop.img || this.toImg();
          }
        ];

        // Fulfill prereqs then perform the appropriate output.
        return this.thenList(prereqs).then(function outputImg_main() {
          switch (type) {
            case undefined:
            case "img":
              return this.prop.img;
            case "datauristring":
            case "dataurlstring":
              return this.prop.img.src;
            case "datauri":
            case "dataurl":
              return (document.location.href = this.prop.img.src);
            default:
              throw 'Image output type "' + type + '" is not supported.';
          }
        });
      };

      Worker.prototype.save = function save(filename) {
        // Set up function prerequisites.
        var prereqs = [
          function checkPdf() {
            return this.prop.pdf || this.toPdf();
          }
        ];

        // Fulfill prereqs, update the filename (if provided), and save the PDF.
        return this.thenList(prereqs)
          .set(filename ? { filename: filename } : null)
          .then(function save_main() {
            this.prop.pdf.save(this.opt.filename);
          });
      };

      Worker.prototype.doCallback = function doCallback() {
        // Set up function prerequisites.
        var prereqs = [
          function checkPdf() {
            return this.prop.pdf || this.toPdf();
          }
        ];

        // Fulfill prereqs, update the filename (if provided), and save the PDF.
        return this.thenList(prereqs).then(function doCallback_main() {
          this.prop.callback(this.prop.pdf);
        });
      };

      /* ----- SET / GET ----- */

      Worker.prototype.set = function set(opt) {
        // TODO: Implement ordered pairs?

        // Silently ignore invalid or empty input.
        if (objType(opt) !== "object") {
          return this;
        }

        // Build an array of setter functions to queue.
        var fns = Object.keys(opt || {}).map(function(key) {
          if (key in Worker.template.prop) {
            // Set pre-defined properties.
            return function set_prop() {
              this.prop[key] = opt[key];
            };
          } else {
            switch (key) {
              case "margin":
                return this.setMargin.bind(this, opt.margin);
              case "jsPDF":
                return function set_jsPDF() {
                  this.opt.jsPDF = opt.jsPDF;
                  return this.setPageSize();
                };
              case "pageSize":
                return this.setPageSize.bind(this, opt.pageSize);
              default:
                // Set any other properties in opt.
                return function set_opt() {
                  this.opt[key] = opt[key];
                };
            }
          }
        }, this);

        // Set properties within the promise chain.
        return this.then(function set_main() {
          return this.thenList(fns);
        });
      };

      Worker.prototype.get = function get(key, cbk) {
        return this.then(function get_main() {
          // Fetch the requested property, either as a predefined prop or in opt.
          var val = key in Worker.template.prop ? this.prop[key] : this.opt[key];
          return cbk ? cbk(val) : val;
        });
      };

      Worker.prototype.setMargin = function setMargin(margin) {
        return this.then(function setMargin_main() {
          // Parse the margin property.
          switch (objType(margin)) {
            case "number":
              margin = [margin, margin, margin, margin];
            // eslint-disable-next-line no-fallthrough
            case "array":
              if (margin.length === 2) {
                margin = [margin[0], margin[1], margin[0], margin[1]];
              }
              if (margin.length === 4) {
                break;
              }
            // eslint-disable-next-line no-fallthrough
            default:
              return this.error("Invalid margin array.");
          }

          // Set the margin property, then update pageSize.
          this.opt.margin = margin;
        }).then(this.setPageSize);
      };

      Worker.prototype.setPageSize = function setPageSize(pageSize) {
        function toPx(val, k) {
          return Math.floor(((val * k) / 72) * 96);
        }

        return this.then(function setPageSize_main() {
          // Retrieve page-size based on jsPDF settings, if not explicitly provided.
          pageSize = pageSize || jsPDF.getPageSize(this.opt.jsPDF);

          // Add 'inner' field if not present.
          if (!pageSize.hasOwnProperty("inner")) {
            pageSize.inner = {
              width: pageSize.width - this.opt.margin[1] - this.opt.margin[3],
              height: pageSize.height - this.opt.margin[0] - this.opt.margin[2]
            };
            pageSize.inner.px = {
              width: toPx(pageSize.inner.width, pageSize.k),
              height: toPx(pageSize.inner.height, pageSize.k)
            };
            pageSize.inner.ratio = pageSize.inner.height / pageSize.inner.width;
          }

          // Attach pageSize to this.
          this.prop.pageSize = pageSize;
        });
      };

      Worker.prototype.setProgress = function setProgress(val, state, n, stack) {
        // Immediately update all progress values.
        if (val != null) this.progress.val = val;
        if (state != null) this.progress.state = state;
        if (n != null) this.progress.n = n;
        if (stack != null) this.progress.stack = stack;
        this.progress.ratio = this.progress.val / this.progress.state;

        // Return this for command chaining.
        return this;
      };

      Worker.prototype.updateProgress = function updateProgress(
        val,
        state,
        n,
        stack
      ) {
        // Immediately update all progress values, using setProgress.
        return this.setProgress(
          val ? this.progress.val + val : null,
          state ? state : null,
          n ? this.progress.n + n : null,
          stack ? this.progress.stack.concat(stack) : null
        );
      };

      /* ----- PROMISE MAPPING ----- */

      Worker.prototype.then = function then(onFulfilled, onRejected) {
        // Wrap `this` for encapsulation.
        var self = this;

        return this.thenCore(onFulfilled, onRejected, function then_main(
          onFulfilled,
          onRejected
        ) {
          // Update progress while queuing, calling, and resolving `then`.
          self.updateProgress(null, null, 1, [onFulfilled]);
          return Promise.prototype.then
            .call(this, function then_pre(val) {
              self.updateProgress(null, onFulfilled);
              return val;
            })
            .then(onFulfilled, onRejected)
            .then(function then_post(val) {
              self.updateProgress(1);
              return val;
            });
        });
      };

      Worker.prototype.thenCore = function thenCore(
        onFulfilled,
        onRejected,
        thenBase
      ) {
        // Handle optional thenBase parameter.
        thenBase = thenBase || Promise.prototype.then;

        // Wrap `this` for encapsulation and bind it to the promise handlers.
        var self = this;
        if (onFulfilled) {
          onFulfilled = onFulfilled.bind(self);
        }
        if (onRejected) {
          onRejected = onRejected.bind(self);
        }

        // Cast self into a Promise to avoid polyfills recursively defining `then`.
        var isNative =
          Promise.toString().indexOf("[native code]") !== -1 &&
          Promise.name === "Promise";
        var selfPromise = isNative
          ? self
          : Worker.convert(Object.assign({}, self), Promise.prototype);

        // Return the promise, after casting it into a Worker and preserving props.
        var returnVal = thenBase.call(selfPromise, onFulfilled, onRejected);
        return Worker.convert(returnVal, self.__proto__);
      };

      Worker.prototype.thenExternal = function thenExternal(
        onFulfilled,
        onRejected
      ) {
        // Call `then` and return a standard promise (exits the Worker chain).
        return Promise.prototype.then.call(this, onFulfilled, onRejected);
      };

      Worker.prototype.thenList = function thenList(fns) {
        // Queue a series of promise 'factories' into the promise chain.
        var self = this;
        fns.forEach(function thenList_forEach(fn) {
          self = self.thenCore(fn);
        });
        return self;
      };

      Worker.prototype["catch"] = function(onRejected) {
        // Bind `this` to the promise handler, call `catch`, and return a Worker.
        if (onRejected) {
          onRejected = onRejected.bind(this);
        }
        var returnVal = Promise.prototype["catch"].call(this, onRejected);
        return Worker.convert(returnVal, this);
      };

      Worker.prototype.catchExternal = function catchExternal(onRejected) {
        // Call `catch` and return a standard promise (exits the Worker chain).
        return Promise.prototype["catch"].call(this, onRejected);
      };

      Worker.prototype.error = function error(msg) {
        // Throw the error in the Promise chain.
        return this.then(function error_main() {
          throw new Error(msg);
        });
      };

      /* ----- ALIASES ----- */

      Worker.prototype.using = Worker.prototype.set;
      Worker.prototype.saveAs = Worker.prototype.save;
      Worker.prototype.export = Worker.prototype.output;
      Worker.prototype.run = Worker.prototype.then;

      // Get dimensions of a PDF page, as determined by jsPDF.
      jsPDF.getPageSize = function(orientation, unit, format) {
        // Decode options object
        if (typeof orientation === "object") {
          var options = orientation;
          orientation = options.orientation;
          unit = options.unit || unit;
          format = options.format || format;
        }

        // Default options
        unit = unit || "mm";
        format = format || "a4";
        orientation = ("" + (orientation || "P")).toLowerCase();
        var format_as_string = ("" + format).toLowerCase();

        // Size in pt of various paper formats
        var pageFormats = {
          a0: [2383.94, 3370.39],
          a1: [1683.78, 2383.94],
          a2: [1190.55, 1683.78],
          a3: [841.89, 1190.55],
          a4: [595.28, 841.89],
          a5: [419.53, 595.28],
          a6: [297.64, 419.53],
          a7: [209.76, 297.64],
          a8: [147.4, 209.76],
          a9: [104.88, 147.4],
          a10: [73.7, 104.88],
          b0: [2834.65, 4008.19],
          b1: [2004.09, 2834.65],
          b2: [1417.32, 2004.09],
          b3: [1000.63, 1417.32],
          b4: [708.66, 1000.63],
          b5: [498.9, 708.66],
          b6: [354.33, 498.9],
          b7: [249.45, 354.33],
          b8: [175.75, 249.45],
          b9: [124.72, 175.75],
          b10: [87.87, 124.72],
          c0: [2599.37, 3676.54],
          c1: [1836.85, 2599.37],
          c2: [1298.27, 1836.85],
          c3: [918.43, 1298.27],
          c4: [649.13, 918.43],
          c5: [459.21, 649.13],
          c6: [323.15, 459.21],
          c7: [229.61, 323.15],
          c8: [161.57, 229.61],
          c9: [113.39, 161.57],
          c10: [79.37, 113.39],
          dl: [311.81, 623.62],
          letter: [612, 792],
          "government-letter": [576, 756],
          legal: [612, 1008],
          "junior-legal": [576, 360],
          ledger: [1224, 792],
          tabloid: [792, 1224],
          "credit-card": [153, 243]
        };

        var k;
        // Unit conversion
        switch (unit) {
          case "pt":
            k = 1;
            break;
          case "mm":
            k = 72 / 25.4;
            break;
          case "cm":
            k = 72 / 2.54;
            break;
          case "in":
            k = 72;
            break;
          case "px":
            k = 72 / 96;
            break;
          case "pc":
            k = 12;
            break;
          case "em":
            k = 12;
            break;
          case "ex":
            k = 6;
            break;
          default:
            throw "Invalid unit: " + unit;
        }
        var pageHeight = 0;
        var pageWidth = 0;

        // Dimensions are stored as user units and converted to points on output
        if (pageFormats.hasOwnProperty(format_as_string)) {
          pageHeight = pageFormats[format_as_string][1] / k;
          pageWidth = pageFormats[format_as_string][0] / k;
        } else {
          try {
            pageHeight = format[1];
            pageWidth = format[0];
          } catch (err) {
            throw new Error("Invalid format: " + format);
          }
        }

        var tmp;
        // Handle page orientation
        if (orientation === "p" || orientation === "portrait") {
          orientation = "p";
          if (pageWidth > pageHeight) {
            tmp = pageWidth;
            pageWidth = pageHeight;
            pageHeight = tmp;
          }
        } else if (orientation === "l" || orientation === "landscape") {
          orientation = "l";
          if (pageHeight > pageWidth) {
            tmp = pageWidth;
            pageWidth = pageHeight;
            pageHeight = tmp;
          }
        } else {
          throw "Invalid orientation: " + orientation;
        }

        // Return information (k is the unit conversion ratio from pts)
        var info = {
          width: pageWidth,
          height: pageHeight,
          unit: unit,
          k: k,
          orientation: orientation
        };
        return info;
      };

      /**
       * @typedef FontFace
       *
       * The font-face type implements an interface similar to that of the font-face CSS rule,
       * and is used by jsPDF to match fonts when the font property of CanvasRenderingContext2D
       * is updated.
       *
       * All properties expect values similar to those in the font-face CSS rule. A difference
       * is the font-family, which do not need to be enclosed in double-quotes when containing
       * spaces like in CSS.
       *
       * @property {string} family The name of the font-family.
       * @property {string|undefined} style The style that this font-face defines, e.g. 'italic'.
       * @property {string|number|undefined} weight The weight of the font, either as a string or a number (400, 500, 600, e.g.)
       * @property {string|undefined} stretch The stretch of the font, e.g. condensed, normal, expanded.
       * @property {Object[]} src A list of URLs from where fonts of various formats can be fetched.
       * @property {string} [src] url A URL to a font of a specific format.
       * @property {string} [src] format Format of the font referenced by the URL.
       */

      /**
       * Generate a PDF from an HTML element or string using.
       *
       * @name html
       * @function
       * @param {HTMLElement|string} source The source HTMLElement or a string containing HTML.
       * @param {Object} [options] Collection of settings
       * @param {function} [options.callback] The mandatory callback-function gets as first parameter the current jsPDF instance
       * @param {(number|number[])=} [options.margin] Page margins [top, right, bottom, left]. Default is 0.
       * @param {(boolean|'slice'|'text')=} [options.autoPaging] The auto paging mode.
       * <ul>
       * <li>
       *   <code>false</code>: Auto paging is disabled.
       * </li>
       * <li>
       *   <code>true</code> or <code>'slice'</code>: Will cut shapes or text chunks across page breaks. Will possibly
       *   slice text in half, making it difficult to read.
       * </li>
       * <li>
       *   <code>'text'</code>: Trys not to cut text in half across page breaks. Works best for documents consisting
       *   mostly of a single column of text.
       * </li>
       * </ul>
       * Default is <code>true</code>.
       * @param {string} [options.filename] name of the file
       * @param {HTMLOptionImage} [options.image] image settings when converting HTML to image
       * @param {Html2CanvasOptions} [options.html2canvas] html2canvas options
       * @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.
       * @param {jsPDF} [options.jsPDF] jsPDF instance
       * @param {number=} [options.x] x position on the PDF document in jsPDF units.
       * @param {number=} [options.y] y position on the PDF document in jsPDF units.
       * @param {number=} [options.width] The target width in the PDF document in jsPDF units. The rendered element will be
       * scaled such that it fits into the specified width. Has no effect if either the <code>html2canvas.scale<code> is
       * specified or the <code>windowWidth</code> option is NOT specified.
       * @param {number=} [options.windowWidth] The window width in CSS pixels. In contrast to the
       * <code>html2canvas.windowWidth</code> option, this option affects the actual container size while rendering and
       * does NOT affect CSS media queries. This option only has an effect, if the <code>width<code> option is also specified.
       *
       * @example
       * var doc = new jsPDF();
       *
       * doc.html(document.body, {
       *    callback: function (doc) {
       *      doc.save();
       *    },
       *    x: 10,
       *    y: 10
       * });
       */
      jsPDFAPI.html = function(src, options) {
        "use strict";

        options = options || {};
        options.callback = options.callback || function() {};
        options.html2canvas = options.html2canvas || {};
        options.html2canvas.canvas = options.html2canvas.canvas || this.canvas;
        options.jsPDF = options.jsPDF || this;
        options.fontFaces = options.fontFaces
          ? options.fontFaces.map(normalizeFontFace)
          : null;

        // Create a new worker with the given options.
        var worker = new Worker(options);

        if (!options.worker) {
          // If worker is not set to true, perform the traditional 'simple' operation.
          return worker.from(src).doCallback();
        } else {
          // Otherwise, return the worker for new Promise-based operation.
          return worker;
        }
      };
    })(jsPDF.API);