/* eslint-disable no-console */
/* global saveAs, define, RGBColor */
// eslint-disable-next-line no-unused-vars
var jsPDF = (function(global) {
  "use strict";

  /**
   * jsPDF's Internal PubSub Implementation.
   * Backward compatible rewritten on 2014 by
   * Diego Casorran, https://github.com/diegocr
   *
   * @class
   * @name PubSub
   * @ignore
   */
  function PubSub(context) {
    if (typeof context !== "object") {
      throw new Error(
        "Invalid Context passed to initialize PubSub (jsPDF-module)"
      );
    }
    var topics = {};

    this.subscribe = function(topic, callback, once) {
      once = once || false;
      if (
        typeof topic !== "string" ||
        typeof callback !== "function" ||
        typeof once !== "boolean"
      ) {
        throw new Error(
          "Invalid arguments passed to PubSub.subscribe (jsPDF-module)"
        );
      }

      if (!topics.hasOwnProperty(topic)) {
        topics[topic] = {};
      }

      var token = Math.random().toString(35);
      topics[topic][token] = [callback, !!once];

      return token;
    };

    this.unsubscribe = function(token) {
      for (var topic in topics) {
        if (topics[topic][token]) {
          delete topics[topic][token];
          if (Object.keys(topics[topic]).length === 0) {
            delete topics[topic];
          }
          return true;
        }
      }
      return false;
    };

    this.publish = function(topic) {
      if (topics.hasOwnProperty(topic)) {
        var args = Array.prototype.slice.call(arguments, 1),
          tokens = [];

        for (var token in topics[topic]) {
          var sub = topics[topic][token];
          try {
            sub[0].apply(context, args);
          } catch (ex) {
            if (global.console) {
              console.error("jsPDF PubSub Error", ex.message, ex);
            }
          }
          if (sub[1]) tokens.push(token);
        }
        if (tokens.length) tokens.forEach(this.unsubscribe);
      }
    };

    this.getTopics = function() {
      return topics;
    };
  }

  /**
   * Creates new jsPDF document object instance.
   * @name jsPDF
   * @class
   * @param {Object} [options] - Collection of settings initializing the jsPDF-instance
   * @param {string} [options.orientation=portrait] - Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l").<br />
   * @param {string} [options.unit=mm] Measurement unit (base unit) to be used when coordinates are specified.<br />
   * Possible values are "pt" (points), "mm", "cm", "m", "in" or "px".
   * @param {string/Array} [options.format=a4] The format of the first page. Can be:<ul><li>a0 - a10</li><li>b0 - b10</li><li>c0 - c10</li><li>dl</li><li>letter</li><li>government-letter</li><li>legal</li><li>junior-legal</li><li>ledger</li><li>tabloid</li><li>credit-card</li></ul><br />
   * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89]
   * @param {boolean} [options.putOnlyUsedFonts=false] Only put fonts into the PDF, which were used.
   * @param {boolean} [options.compress=false] Compress the generated PDF.
   * @param {number} [options.precision=16] Precision of the element-positions.
   * @param {number} [options.userUnit=1.0] Not to be confused with the base unit. Please inform yourself before you use it.
   * @param {number|"smart"} [options.floatPrecision=16]
   * @returns {jsPDF} jsPDF-instance
   * @description
   * ```
   * {
   *  orientation: 'p',
   *  unit: 'mm',
   *  format: 'a4',
   *  putOnlyUsedFonts:true,
   *  floatPrecision: 16 // or "smart", default is 16
   * }
   * ```
   *
   * @constructor
   */
  function jsPDF(options) {
    var orientation = typeof arguments[0] === "string" ? arguments[0] : "p";
    var unit = arguments[1];
    var format = arguments[2];
    var compressPdf = arguments[3];
    var filters = [];
    var userUnit = 1.0;
    var precision;
    var floatPrecision = 16;
    var defaultPathOperation = "S";

    options = options || {};

    if (typeof options === "object") {
      orientation = options.orientation;
      unit = options.unit || unit;
      format = options.format || format;
      compressPdf = options.compress || options.compressPdf || compressPdf;
      userUnit =
        typeof options.userUnit === "number" ? Math.abs(options.userUnit) : 1.0;
      if (typeof options.precision !== "undefined") {
        precision = options.precision;
      }
      if (typeof options.floatPrecision !== "undefined") {
        floatPrecision = options.floatPrecision;
      }
      defaultPathOperation = options.defaultPathOperation || "S";
    }

    filters =
      options.filters || (compressPdf === true ? ["FlateEncode"] : filters);

    unit = unit || "mm";
    orientation = ("" + (orientation || "P")).toLowerCase();
    var putOnlyUsedFonts = options.putOnlyUsedFonts || false;
    var usedFonts = {};

    var API = {
      internal: {},
      __private__: {}
    };

    API.__private__.PubSub = PubSub;

    var pdfVersion = "1.3";
    var getPdfVersion = (API.__private__.getPdfVersion = function() {
      return pdfVersion;
    });

    API.__private__.setPdfVersion = function(value) {
      pdfVersion = value;
    };

    // 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]
    };

    API.__private__.getPageFormats = function() {
      return pageFormats;
    };

    var getPageFormat = (API.__private__.getPageFormat = function(value) {
      return pageFormats[value];
    });

    format = format || "a4";

    var ApiMode = {
      COMPAT: "compat",
      ADVANCED: "advanced"
    };
    var apiMode = ApiMode.COMPAT;

    function advancedAPI() {
      // prepend global change of basis matrix
      // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix
      // that does this job for us (however, texts, images and similar objects must be drawn bottom up))
      this.saveGraphicsState();
      out(
        new Matrix(
          scaleFactor,
          0,
          0,
          -scaleFactor,
          0,
          getPageHeight() * scaleFactor
        ).toString() + " cm"
      );
      this.setFontSize(this.getFontSize() / scaleFactor);

      // The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation
      // was "n" (none). Although this has nothing to do with transforms, we should use the API switch here.
      defaultPathOperation = "n";

      apiMode = ApiMode.ADVANCED;
    }

    function compatAPI() {
      this.restoreGraphicsState();
      defaultPathOperation = "S";
      apiMode = ApiMode.COMPAT;
    }

    /**
     * @callback ApiSwitchBody
     * @param {jsPDF} pdf
     */

    /**
     * For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual
     * screen coordinates and the PDF coordinate system.
     *   - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms
     *   - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might
     *     not support this mode, though.
     * Initial mode is "compat".
     *
     * You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to
     * the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}.
     *
     * Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the
     * callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject}
     * or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode.
     *
     * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
     * The API mode will be switched back automatically afterwards.
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name advancedAPI
     */
    API.advancedAPI = function(body) {
      var doSwitch = apiMode === ApiMode.COMPAT;

      if (doSwitch) {
        advancedAPI.call(this);
      }

      if (typeof body !== "function") {
        return this;
      }

      body(this);

      if (doSwitch) {
        compatAPI.call(this);
      }

      return this;
    };

    /**
     * Switches to "compat" API mode. See {@link advancedAPI} for more details.
     *
     * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
     * The API mode will be switched back automatically afterwards.
     * @return {jsPDF}
     * @memberof jsPDF#
     * @name compatApi
     */
    API.compatAPI = function(body) {
      var doSwitch = apiMode === ApiMode.ADVANCED;

      if (doSwitch) {
        compatAPI.call(this);
      }

      if (typeof body !== "function") {
        return this;
      }

      body(this);

      if (doSwitch) {
        advancedAPI.call(this);
      }

      return this;
    };

    /**
     * @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}.
     * @memberof jsPDF#
     * @name isAdvancedAPI
     */
    API.isAdvancedAPI = function() {
      return apiMode === ApiMode.ADVANCED;
    };

    var advancedApiModeTrap = function(methodName) {
      if (apiMode !== ApiMode.ADVANCED) {
        throw new Error(
          methodName +
            " is only available in 'advanced' API mode. " +
            "You need to call advancedAPI() first."
        );
      }
    };

    var roundToPrecision = (API.roundToPrecision = API.__private__.roundToPrecision = function(
      number,
      parmPrecision
    ) {
      var tmpPrecision = precision || parmPrecision;
      if (isNaN(number) || isNaN(tmpPrecision)) {
        throw new Error("Invalid argument passed to jsPDF.roundToPrecision");
      }
      return number.toFixed(tmpPrecision).replace(/0+$/, "");
    });

    // high precision float
    var hpf;
    if (typeof floatPrecision === "number") {
      hpf = API.hpf = API.__private__.hpf = function(number) {
        if (isNaN(number)) {
          throw new Error("Invalid argument passed to jsPDF.hpf");
        }
        return roundToPrecision(number, floatPrecision);
      };
    } else if (floatPrecision === "smart") {
      hpf = API.hpf = API.__private__.hpf = function(number) {
        if (isNaN(number)) {
          throw new Error("Invalid argument passed to jsPDF.hpf");
        }
        if (number > -1 && number < 1) {
          return roundToPrecision(number, 16);
        } else {
          return roundToPrecision(number, 5);
        }
      };
    } else {
      hpf = API.hpf = API.__private__.hpf = function(number) {
        if (isNaN(number)) {
          throw new Error("Invalid argument passed to jsPDF.hpf");
        }
        return roundToPrecision(number, 16);
      };
    }
    var f2 = (API.f2 = API.__private__.f2 = function(number) {
      if (isNaN(number)) {
        throw new Error("Invalid argument passed to jsPDF.f2");
      }
      return roundToPrecision(number, 2);
    });

    var f3 = (API.__private__.f3 = function(number) {
      if (isNaN(number)) {
        throw new Error("Invalid argument passed to jsPDF.f3");
      }
      return roundToPrecision(number, 3);
    });

    var scale = (API.scale = API.__private__.scale = function(number) {
      if (isNaN(number)) {
        throw new Error("Invalid argument passed to jsPDF.scale");
      }
      if (apiMode === ApiMode.COMPAT) {
        return number * scaleFactor;
      } else if (apiMode === ApiMode.ADVANCED) {
        return number;
      }
    });

    var transformY = function(y) {
      if (apiMode === ApiMode.COMPAT) {
        return getPageHeight() - y;
      } else if (apiMode === ApiMode.ADVANCED) {
        return y;
      }
    };

    var transformScaleY = function(y) {
      return scale(transformY(y));
    };

    /**
     * @name setPrecision
     * @memberof jsPDF#
     * @function
     * @instance
     * @param {string} precision
     * @returns {jsPDF}
     */
    API.__private__.setPrecision = API.setPrecision = function(value) {
      if (typeof parseInt(value, 10) === "number") {
        precision = parseInt(value, 10);
      }
    };

    var fileId = "00000000000000000000000000000000";

    var getFileId = (API.__private__.getFileId = function() {
      return fileId;
    });

    var setFileId = (API.__private__.setFileId = function(value) {
      if (typeof value !== "undefined" && /^[a-fA-F0-9]{32}$/.test(value)) {
        fileId = value.toUpperCase();
      } else {
        fileId = fileId
          .split("")
          .map(function() {
            return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16));
          })
          .join("");
      }
      return fileId;
    });

    /**
     * @name setFileId
     * @memberof jsPDF#
     * @function
     * @instance
     * @param {string} value GUID.
     * @returns {jsPDF}
     */
    API.setFileId = function(value) {
      setFileId(value);
      return this;
    };

    /**
     * @name getFileId
     * @memberof jsPDF#
     * @function
     * @instance
     *
     * @returns {string} GUID.
     */
    API.getFileId = function() {
      return getFileId();
    };

    var creationDate;

    var convertDateToPDFDate = (API.__private__.convertDateToPDFDate = function(
      parmDate
    ) {
      var result = "";
      var tzoffset = parmDate.getTimezoneOffset(),
        tzsign = tzoffset < 0 ? "+" : "-",
        tzhour = Math.floor(Math.abs(tzoffset / 60)),
        tzmin = Math.abs(tzoffset % 60),
        timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(
          ""
        );

      result = [
        "D:",
        parmDate.getFullYear(),
        padd2(parmDate.getMonth() + 1),
        padd2(parmDate.getDate()),
        padd2(parmDate.getHours()),
        padd2(parmDate.getMinutes()),
        padd2(parmDate.getSeconds()),
        timeZoneString
      ].join("");
      return result;
    });

    var convertPDFDateToDate = (API.__private__.convertPDFDateToDate = function(
      parmPDFDate
    ) {
      var year = parseInt(parmPDFDate.substr(2, 4), 10);
      var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1;
      var date = parseInt(parmPDFDate.substr(8, 2), 10);
      var hour = parseInt(parmPDFDate.substr(10, 2), 10);
      var minutes = parseInt(parmPDFDate.substr(12, 2), 10);
      var seconds = parseInt(parmPDFDate.substr(14, 2), 10);
      // var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10);
      // var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10);

      var resultingDate = new Date(
        year,
        month,
        date,
        hour,
        minutes,
        seconds,
        0
      );
      return resultingDate;
    });

    var setCreationDate = (API.__private__.setCreationDate = function(date) {
      var tmpCreationDateString;
      var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/;
      if (typeof date === "undefined") {
        date = new Date();
      }

      if (date instanceof Date) {
        tmpCreationDateString = convertDateToPDFDate(date);
      } else if (regexPDFCreationDate.test(date)) {
        tmpCreationDateString = date;
      } else {
        throw new Error("Invalid argument passed to jsPDF.setCreationDate");
      }
      creationDate = tmpCreationDateString;
      return creationDate;
    });

    var getCreationDate = (API.__private__.getCreationDate = function(type) {
      var result = creationDate;
      if (type === "jsDate") {
        result = convertPDFDateToDate(creationDate);
      }
      return result;
    });

    /**
     * @name setCreationDate
     * @memberof jsPDF#
     * @function
     * @instance
     * @param {Object} date
     * @returns {jsPDF}
     */
    API.setCreationDate = function(date) {
      setCreationDate(date);
      return this;
    };

    /**
     * @name getCreationDate
     * @memberof jsPDF#
     * @function
     * @instance
     * @param {Object} type
     * @returns {Object}
     */
    API.getCreationDate = function(type) {
      return getCreationDate(type);
    };

    var padd2 = (API.__private__.padd2 = function(number) {
      return ("0" + parseInt(number)).slice(-2);
    });

    var padd2Hex = (API.__private__.padd2Hex = function(hexString) {
      hexString = hexString.toString();
      return ("00" + hexString).substr(hexString.length);
    });

    var objectNumber = 0; // 'n' Current object number
    var offsets = []; // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes.
    var content = [];
    var contentLength = 0;
    var additionalObjects = [];

    var pages = [];
    var currentPage;
    var hasCustomDestination = false;
    var outputDestination = content;

    var resetDocument = function() {
      //reset fields relevant for objectNumber generation and xref.
      objectNumber = 0;
      contentLength = 0;
      content = [];
      offsets = [];
      additionalObjects = [];

      rootDictionaryObjId = newObjectDeferred();
      resourceDictionaryObjId = newObjectDeferred();
    };

    API.__private__.setCustomOutputDestination = function(destination) {
      hasCustomDestination = true;
      outputDestination = destination;
    };
    var setOutputDestination = function(destination) {
      if (!hasCustomDestination) {
        outputDestination = destination;
      }
    };

    API.__private__.resetCustomOutputDestination = function() {
      hasCustomDestination = false;
      outputDestination = content;
    };

    var out = (API.__private__.out = function(string) {
      string = string.toString();
      contentLength += string.length + 1;
      outputDestination.push(string);

      return outputDestination;
    });

    var write = (API.__private__.write = function(value) {
      return out(
        arguments.length === 1
          ? value.toString()
          : Array.prototype.join.call(arguments, " ")
      );
    });

    var getArrayBuffer = (API.__private__.getArrayBuffer = function(data) {
      var len = data.length,
        ab = new ArrayBuffer(len),
        u8 = new Uint8Array(ab);

      while (len--) u8[len] = data.charCodeAt(len);
      return ab;
    });

    var standardFonts = [
      ["Helvetica", "helvetica", "normal", "WinAnsiEncoding"],
      ["Helvetica-Bold", "helvetica", "bold", "WinAnsiEncoding"],
      ["Helvetica-Oblique", "helvetica", "italic", "WinAnsiEncoding"],
      ["Helvetica-BoldOblique", "helvetica", "bolditalic", "WinAnsiEncoding"],
      ["Courier", "courier", "normal", "WinAnsiEncoding"],
      ["Courier-Bold", "courier", "bold", "WinAnsiEncoding"],
      ["Courier-Oblique", "courier", "italic", "WinAnsiEncoding"],
      ["Courier-BoldOblique", "courier", "bolditalic", "WinAnsiEncoding"],
      ["Times-Roman", "times", "normal", "WinAnsiEncoding"],
      ["Times-Bold", "times", "bold", "WinAnsiEncoding"],
      ["Times-Italic", "times", "italic", "WinAnsiEncoding"],
      ["Times-BoldItalic", "times", "bolditalic", "WinAnsiEncoding"],
      ["ZapfDingbats", "zapfdingbats", "normal", null],
      ["Symbol", "symbol", "normal", null]
    ];

    API.__private__.getStandardFonts = function() {
      return standardFonts;
    };

    var activeFontSize = options.fontSize || 16;

    /**
     * Sets font size for upcoming text elements.
     *
     * @param {number} size Font size in points.
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setFontSize
     */
    API.__private__.setFontSize = API.setFontSize = function(size) {
      if (apiMode === ApiMode.ADVANCED) {
        activeFontSize = size / scaleFactor;
      } else {
        activeFontSize = size;
      }
      return this;
    };

    /**
     * Gets the fontsize for upcoming text elements.
     *
     * @function
     * @instance
     * @returns {number}
     * @memberof jsPDF#
     * @name getFontSize
     */
    var getFontSize = (API.__private__.getFontSize = API.getFontSize = function() {
      if (apiMode === ApiMode.COMPAT) {
        return activeFontSize;
      } else {
        return activeFontSize * scaleFactor;
      }
    });

    var R2L = options.R2L || false;

    /**
     * Set value of R2L functionality.
     *
     * @param {boolean} value
     * @function
     * @instance
     * @returns {jsPDF} jsPDF-instance
     * @memberof jsPDF#
     * @name setR2L
     */
    API.__private__.setR2L = API.setR2L = function(value) {
      R2L = value;
      return this;
    };

    /**
     * Get value of R2L functionality.
     *
     * @function
     * @instance
     * @returns {boolean} jsPDF-instance
     * @memberof jsPDF#
     * @name getR2L
     */
    API.__private__.getR2L = API.getR2L = function() {
      return R2L;
    };

    var zoomMode; // default: 1;

    var setZoomMode = (API.__private__.setZoomMode = function(zoom) {
      var validZoomModes = [
        undefined,
        null,
        "fullwidth",
        "fullheight",
        "fullpage",
        "original"
      ];

      if (/^\d*\.?\d*%$/.test(zoom)) {
        zoomMode = zoom;
      } else if (!isNaN(zoom)) {
        zoomMode = parseInt(zoom, 10);
      } else if (validZoomModes.indexOf(zoom) !== -1) {
        zoomMode = zoom;
      } else {
        throw new Error(
          'zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "' +
            zoom +
            '" is not recognized.'
        );
      }
    });

    API.__private__.getZoomMode = function() {
      return zoomMode;
    };

    var pageMode; // default: 'UseOutlines';
    var setPageMode = (API.__private__.setPageMode = function(pmode) {
      var validPageModes = [
        undefined,
        null,
        "UseNone",
        "UseOutlines",
        "UseThumbs",
        "FullScreen"
      ];

      if (validPageModes.indexOf(pmode) == -1) {
        throw new Error(
          'Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' +
            pmode +
            '" is not recognized.'
        );
      }
      pageMode = pmode;
    });

    API.__private__.getPageMode = function() {
      return pageMode;
    };

    var layoutMode; // default: 'continuous';
    var setLayoutMode = (API.__private__.setLayoutMode = function(layout) {
      var validLayoutModes = [
        undefined,
        null,
        "continuous",
        "single",
        "twoleft",
        "tworight",
        "two"
      ];

      if (validLayoutModes.indexOf(layout) == -1) {
        throw new Error(
          'Layout mode must be one of continuous, single, twoleft, tworight. "' +
            layout +
            '" is not recognized.'
        );
      }
      layoutMode = layout;
    });

    API.__private__.getLayoutMode = function() {
      return layoutMode;
    };

    /**
     * Set the display mode options of the page like zoom and layout.
     *
     * @name setDisplayMode
     * @memberof jsPDF#
     * @function
     * @instance
     * @param {integer|String} zoom   You can pass an integer or percentage as
     * a string. 2 will scale the document up 2x, '200%' will scale up by the
     * same amount. You can also set it to 'fullwidth', 'fullheight',
     * 'fullpage', or 'original'.
     *
     * Only certain PDF readers support this, such as Adobe Acrobat.
     *
     * @param {string} layout Layout mode can be: 'continuous' - this is the
     * default continuous scroll. 'single' - the single page mode only shows one
     * page at a time. 'twoleft' - two column left mode, first page starts on
     * the left, and 'tworight' - pages are laid out in two columns, with the
     * first page on the right. This would be used for books.
     * @param {string} pmode 'UseOutlines' - it shows the
     * outline of the document on the left. 'UseThumbs' - shows thumbnails along
     * the left. 'FullScreen' - prompts the user to enter fullscreen mode.
     *
     * @returns {jsPDF}
     */
    API.__private__.setDisplayMode = API.setDisplayMode = function(
      zoom,
      layout,
      pmode
    ) {
      setZoomMode(zoom);
      setLayoutMode(layout);
      setPageMode(pmode);
      return this;
    };

    var documentProperties = {
      title: "",
      subject: "",
      author: "",
      keywords: "",
      creator: ""
    };

    API.__private__.getDocumentProperty = function(key) {
      if (Object.keys(documentProperties).indexOf(key) === -1) {
        throw new Error("Invalid argument passed to jsPDF.getDocumentProperty");
      }
      return documentProperties[key];
    };

    API.__private__.getDocumentProperties = function() {
      return documentProperties;
    };

    /**
     * Adds a properties to the PDF document.
     *
     * @param {Object} A property_name-to-property_value object structure.
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setDocumentProperties
     */
    API.__private__.setDocumentProperties = API.setProperties = API.setDocumentProperties = function(
      properties
    ) {
      // copying only those properties we can render.
      for (var property in documentProperties) {
        if (
          documentProperties.hasOwnProperty(property) &&
          properties[property]
        ) {
          documentProperties[property] = properties[property];
        }
      }
      return this;
    };

    API.__private__.setDocumentProperty = function(key, value) {
      if (Object.keys(documentProperties).indexOf(key) === -1) {
        throw new Error(
          "Invalid arguments passed to jsPDF.setDocumentProperty"
        );
      }
      return (documentProperties[key] = value);
    };

    var fonts = {}; // collection of font objects, where key is fontKey - a dynamically created label for a given font.
    var fontmap = {}; // mapping structure fontName > fontStyle > font key - performance layer. See addFont()
    var activeFontKey; // will be string representing the KEY of the font as combination of fontName + fontStyle
    var fontStateStack = []; //
    var patterns = {}; // collection of pattern objects
    var patternMap = {}; // see fonts
    var gStates = {}; // collection of graphic state objects
    var gStatesMap = {}; // see fonts
    var activeGState = null;
    var scaleFactor; // Scale factor
    var page = 0;
    var pagesContext = [];
    var events = new PubSub(API);
    var hotfixes = options.hotfixes || [];

    var renderTargets = {};
    var renderTargetMap = {};
    var renderTargetStack = [];
    var pageX;
    var pageY;
    var pageMatrix; // only used for FormObjects

    /**
     * A matrix object for 2D homogenous transformations: <br>
     * | a b 0 | <br>
     * | c d 0 | <br>
     * | e f 1 | <br>
     * pdf multiplies matrices righthand: v' = v x m1 x m2 x ...
     *
     * @class
     * @name Matrix
     * @param {number} sx
     * @param {number} shy
     * @param {number} shx
     * @param {number} sy
     * @param {number} tx
     * @param {number} ty
     * @constructor
     */
    var Matrix = function(sx, shy, shx, sy, tx, ty) {
      if (!(this instanceof Matrix)) {
        return new Matrix(sx, shy, shx, sy, tx, ty);
      }

      var _matrix = [];

      /**
       * @name sx
       * @memberof Matrix#
       */
      Object.defineProperty(this, "sx", {
        get: function() {
          return _matrix[0];
        },
        set: function(value) {
          _matrix[0] = value;
        }
      });

      /**
       * @name shy
       * @memberof Matrix#
       */
      Object.defineProperty(this, "shy", {
        get: function() {
          return _matrix[1];
        },
        set: function(value) {
          _matrix[1] = value;
        }
      });

      /**
       * @name shx
       * @memberof Matrix#
       */
      Object.defineProperty(this, "shx", {
        get: function() {
          return _matrix[2];
        },
        set: function(value) {
          _matrix[2] = value;
        }
      });

      /**
       * @name sy
       * @memberof Matrix#
       */
      Object.defineProperty(this, "sy", {
        get: function() {
          return _matrix[3];
        },
        set: function(value) {
          _matrix[3] = value;
        }
      });

      /**
       * @name tx
       * @memberof Matrix#
       */
      Object.defineProperty(this, "tx", {
        get: function() {
          return _matrix[4];
        },
        set: function(value) {
          _matrix[4] = value;
        }
      });

      /**
       * @name ty
       * @memberof Matrix#
       */
      Object.defineProperty(this, "ty", {
        get: function() {
          return _matrix[5];
        },
        set: function(value) {
          _matrix[5] = value;
        }
      });

      Object.defineProperty(this, "a", {
        get: function() {
          return _matrix[0];
        },
        set: function(value) {
          _matrix[0] = value;
        }
      });

      Object.defineProperty(this, "b", {
        get: function() {
          return _matrix[1];
        },
        set: function(value) {
          _matrix[1] = value;
        }
      });

      Object.defineProperty(this, "c", {
        get: function() {
          return _matrix[2];
        },
        set: function(value) {
          _matrix[2] = value;
        }
      });

      Object.defineProperty(this, "d", {
        get: function() {
          return _matrix[3];
        },
        set: function(value) {
          _matrix[3] = value;
        }
      });

      Object.defineProperty(this, "e", {
        get: function() {
          return _matrix[4];
        },
        set: function(value) {
          _matrix[4] = value;
        }
      });

      Object.defineProperty(this, "f", {
        get: function() {
          return _matrix[5];
        },
        set: function(value) {
          _matrix[5] = value;
        }
      });

      /**
       * @name rotation
       * @memberof Matrix#
       */
      Object.defineProperty(this, "rotation", {
        get: function() {
          return Math.atan2(this.shx, this.sx);
        }
      });

      /**
       * @name scaleX
       * @memberof Matrix#
       */
      Object.defineProperty(this, "scaleX", {
        get: function() {
          return this.decompose().scale.sx;
        }
      });

      /**
       * @name scaleY
       * @memberof Matrix#
       */
      Object.defineProperty(this, "scaleY", {
        get: function() {
          return this.decompose().scale.sy;
        }
      });

      /**
       * @name isIdentity
       * @memberof Matrix#
       */
      Object.defineProperty(this, "isIdentity", {
        get: function() {
          if (this.sx !== 1) {
            return false;
          }
          if (this.shy !== 0) {
            return false;
          }
          if (this.shx !== 0) {
            return false;
          }
          if (this.sy !== 1) {
            return false;
          }
          if (this.tx !== 0) {
            return false;
          }
          if (this.ty !== 0) {
            return false;
          }
          return true;
        }
      });

      this.sx = !isNaN(sx) ? sx : 1;
      this.shy = !isNaN(shy) ? shy : 0;
      this.shx = !isNaN(shx) ? shx : 0;
      this.sy = !isNaN(sy) ? sy : 1;
      this.tx = !isNaN(tx) ? tx : 0;
      this.ty = !isNaN(ty) ? ty : 0;

      return this;
    };

    /**
     * Join the Matrix Values to a String
     *
     * @function join
     * @param {string} separator Specifies a string to separate each pair of adjacent elements of the array. The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If separator is an empty string, all elements are joined without any characters in between them.
     * @returns {string} A string with all array elements joined.
     * @memberof Matrix#
     */
    Matrix.prototype.join = function(separator) {
      return [this.sx, this.shy, this.shx, this.sy, this.tx, this.ty]
        .map(hpf)
        .join(separator);
    };

    /**
     * Multiply the matrix with given Matrix
     *
     * @function multiply
     * @param matrix
     * @returns {Matrix}
     * @memberof Matrix#
     */
    Matrix.prototype.multiply = function(matrix) {
      var sx = matrix.sx * this.sx + matrix.shy * this.shx;
      var shy = matrix.sx * this.shy + matrix.shy * this.sy;
      var shx = matrix.shx * this.sx + matrix.sy * this.shx;
      var sy = matrix.shx * this.shy + matrix.sy * this.sy;
      var tx = matrix.tx * this.sx + matrix.ty * this.shx + this.tx;
      var ty = matrix.tx * this.shy + matrix.ty * this.sy + this.ty;

      return new Matrix(sx, shy, shx, sy, tx, ty);
    };

    /**
     * @function decompose
     * @memberof Matrix#
     */
    Matrix.prototype.decompose = function() {
      var a = this.sx;
      var b = this.shy;
      var c = this.shx;
      var d = this.sy;
      var e = this.tx;
      var f = this.ty;

      var scaleX = Math.sqrt(a * a + b * b);
      a /= scaleX;
      b /= scaleX;

      var shear = a * c + b * d;
      c -= a * shear;
      d -= b * shear;

      var scaleY = Math.sqrt(c * c + d * d);
      c /= scaleY;
      d /= scaleY;
      shear /= scaleY;

      if (a * d < b * c) {
        a = -a;
        b = -b;
        shear = -shear;
        scaleX = -scaleX;
      }

      return {
        scale: new Matrix(scaleX, 0, 0, scaleY, 0, 0),
        translate: new Matrix(1, 0, 0, 1, e, f),
        rotate: new Matrix(a, b, -b, a, 0, 0),
        skew: new Matrix(1, 0, shear, 1, 0, 0)
      };
    };

    /**
     * @function toString
     * @memberof Matrix#
     */
    Matrix.prototype.toString = function(parmPrecision) {
      return this.join(" ");
    };

    /**
     * @function inversed
     * @memberof Matrix#
     */
    Matrix.prototype.inversed = function() {
      var a = this.sx,
        b = this.shy,
        c = this.shx,
        d = this.sy,
        e = this.tx,
        f = this.ty;

      var quot = 1 / (a * d - b * c);

      var aInv = d * quot;
      var bInv = -b * quot;
      var cInv = -c * quot;
      var dInv = a * quot;
      var eInv = -aInv * e - cInv * f;
      var fInv = -bInv * e - dInv * f;

      return new Matrix(aInv, bInv, cInv, dInv, eInv, fInv);
    };

    /**
     * @function applyToPoint
     * @memberof Matrix#
     */
    Matrix.prototype.applyToPoint = function(pt) {
      var x = pt.x * this.sx + pt.y * this.shx + this.tx;
      var y = pt.x * this.shy + pt.y * this.sy + this.ty;
      return new Point(x, y);
    };

    /**
     * @function applyToRectangle
     * @memberof Matrix#
     */
    Matrix.prototype.applyToRectangle = function(rect) {
      var pt1 = this.applyToPoint(rect);
      var pt2 = this.applyToPoint(new Point(rect.x + rect.w, rect.y + rect.h));
      return new Rectangle(pt1.x, pt1.y, pt2.x - pt1.x, pt2.y - pt1.y);
    };

    /**
     * Clone the Matrix
     *
     * @function clone
     * @memberof Matrix#
     * @name clone
     * @instance
     */
    Matrix.prototype.clone = function() {
      var sx = this.sx;
      var shy = this.shy;
      var shx = this.shx;
      var sy = this.sy;
      var tx = this.tx;
      var ty = this.ty;

      return new Matrix(sx, shy, shx, sy, tx, ty);
    };

    API.Matrix = Matrix;

    /**
     * Multiplies two matrices. (see {@link Matrix})
     * @param {Matrix} m1
     * @param {Matrix} m2
     * @memberof jsPDF#
     * @name matrixMult
     */
    var matrixMult = (API.matrixMult = function(m1, m2) {
      return m2.multiply(m1);
    });

    /**
     * The identity matrix (equivalent to new Matrix(1, 0, 0, 1, 0, 0)).
     * @type {Matrix}
     * @memberof! jsPDF#
     * @name identityMatrix
     */
    var identityMatrix = new Matrix(1, 0, 0, 1, 0, 0);
    API.unitMatrix = API.identityMatrix = identityMatrix;

    var Pattern = function(gState, matrix) {
      this.gState = gState;
      this.matrix = matrix;

      this.id = ""; // set by addPattern()
      this.objectNumber = -1; // will be set by putPattern()
    };

    /**
     * Adds a new pattern for later use.
     * @param {String} key The key by it can be referenced later. The keys must be unique!
     * @param {API.Pattern} pattern The pattern
     */
    var addPattern = function(key, pattern) {
      // only add it if it is not already present (the keys provided by the user must be unique!)
      if (patternMap[key]) return;

      var prefix = pattern instanceof API.ShadingPattern ? "Sh" : "P";
      var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10);
      pattern.id = patternKey;

      patternMap[key] = patternKey;
      patterns[patternKey] = pattern;

      events.publish("addPattern", pattern);
    };

    /**
     * A pattern describing a shading pattern.
     *
     * Only available in "advanced" API mode.
     *
     * @param {String} type One of "axial" or "radial"
     * @param {Array<Number>} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points
     * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle.
     * @param {Array<Object>} colors An array of objects with the fields "offset" and "color". "offset" describes
     * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255].
     * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional).
     * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
     * and the use coordinate system (optional).
     * @constructor
     * @extends API.Pattern
     */
    API.ShadingPattern = function ShadingPattern(
      type,
      coords,
      colors,
      gState,
      matrix
    ) {
      advancedApiModeTrap("ShadingPattern");

      if (!(this instanceof ShadingPattern)) {
        return new ShadingPattern(type, coords, colors, gState, matrix);
      }

      // see putPattern() for information how they are realized
      this.type = type === "axial" ? 2 : 3;
      this.coords = coords;
      this.colors = colors;

      Pattern.call(this, gState, matrix);
    };

    /**
     * A PDF Tiling pattern.
     *
     * Only available in "advanced" API mode.
     *
     * @param {Array.<Number>} boundingBox The bounding box at which one pattern cell gets clipped.
     * @param {Number} xStep Horizontal spacing between pattern cells.
     * @param {Number} yStep Vertical spacing between pattern cells.
     * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional).
     * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
     * and the use coordinate system (optional).
     * @constructor
     * @extends API.Pattern
     */
    API.TilingPattern = function TilingPattern(
      boundingBox,
      xStep,
      yStep,
      gState,
      matrix
    ) {
      advancedApiModeTrap("TilingPattern");

      if (!(this instanceof TilingPattern)) {
        return new TilingPattern(boundingBox, xStep, yStep, gState, matrix);
      }

      this.boundingBox = boundingBox;
      this.xStep = xStep;
      this.yStep = yStep;

      this.stream = ""; // set by endTilingPattern();

      this.cloneIndex = 0;

      Pattern.call(this, gState, matrix);
    };

    API.TilingPattern.prototype = {
      createClone: function(patternKey, boundingBox, xStep, yStep, matrix) {
        var clone = new API.TilingPattern(
          boundingBox || this.boundingBox,
          xStep || this.xStep,
          yStep || this.yStep,
          this.gState,
          matrix || this.matrix
        );
        clone.stream = this.stream;
        var key = patternKey + "$$" + this.cloneIndex++ + "$$";
        addPattern(key, clone);
        return clone;
      }
    };

    /**
     * Adds a new {@link API.ShadingPattern} for later use. Only available in "advanced" API mode.
     * @param {String} key
     * @param {Pattern} pattern
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name addPattern
     */
    API.addShadingPattern = function(key, pattern) {
      advancedApiModeTrap("addShadingPattern()");

      addPattern(key, pattern);
      return this;
    };

    /**
     * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern}
     * gets called. Only available in "advanced" API mode.
     * @param {API.Pattern} pattern
     * @memberof jsPDF#
     * @name beginTilingPattern
     */
    API.beginTilingPattern = function(pattern) {
      advancedApiModeTrap("beginTilingPattern()");

      beginNewRenderTarget(
        pattern.boundingBox[0],
        pattern.boundingBox[1],
        pattern.boundingBox[2] - pattern.boundingBox[0],
        pattern.boundingBox[3] - pattern.boundingBox[1],
        pattern.matrix
      );
    };

    /**
     * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called.
     *
     * Only available in "advanced" API mode.
     *
     * @param {string} key A unique key that is used to reference this pattern at later use.
     * @param {API.Pattern} pattern The pattern to end.
     * @memberof jsPDF#
     * @name endTilingPattern
     */
    API.endTilingPattern = function(key, pattern) {
      advancedApiModeTrap("endTilingPattern()");

      // retrieve the stream
      pattern.stream = pages[currentPage].join("\n");

      addPattern(key, pattern);

      events.publish("endTilingPattern", pattern);

      // restore state from stack
      renderTargetStack.pop().restore();
    };

    var newObject = (API.__private__.newObject = function() {
      var oid = newObjectDeferred();
      newObjectDeferredBegin(oid, true);
      return oid;
    });

    // Does not output the object.  The caller must call newObjectDeferredBegin(oid) before outputing any data
    var newObjectDeferred = (API.__private__.newObjectDeferred = function() {
      objectNumber++;
      offsets[objectNumber] = function() {
        return contentLength;
      };
      return objectNumber;
    });

    var newObjectDeferredBegin = function(oid, doOutput) {
      doOutput = typeof doOutput === "boolean" ? doOutput : false;
      offsets[oid] = contentLength;
      if (doOutput) {
        out(oid + " 0 obj");
      }
      return oid;
    };
    // Does not output the object until after the pages have been output.
    // Returns an object containing the objectId and content.
    // All pages have been added so the object ID can be estimated to start right after.
    // This does not modify the current objectNumber;  It must be updated after the newObjects are output.
    var newAdditionalObject = (API.__private__.newAdditionalObject = function() {
      var objId = newObjectDeferred();
      var obj = {
        objId: objId,
        content: ""
      };
      additionalObjects.push(obj);
      return obj;
    });

    var rootDictionaryObjId = newObjectDeferred();
    var resourceDictionaryObjId = newObjectDeferred();

    /////////////////////
    // Private functions
    /////////////////////

    var decodeColorString = (API.__private__.decodeColorString = function(
      color
    ) {
      var colorEncoded = color.split(" ");
      if (
        colorEncoded.length === 2 &&
        (colorEncoded[1] === "g" || colorEncoded[1] === "G")
      ) {
        // convert grayscale value to rgb so that it can be converted to hex for consistency
        var floatVal = parseFloat(colorEncoded[0]);
        colorEncoded = [floatVal, floatVal, floatVal, "r"];
      } else if (
        colorEncoded.length === 5 &&
        (colorEncoded[4] === "k" || colorEncoded[4] === "K")
      ) {
        // convert CMYK values to rbg so that it can be converted to hex for consistency
        var red = (1.0 - colorEncoded[0]) * (1.0 - colorEncoded[3]);
        var green = (1.0 - colorEncoded[1]) * (1.0 - colorEncoded[3]);
        var blue = (1.0 - colorEncoded[2]) * (1.0 - colorEncoded[3]);

        colorEncoded = [red, green, blue, "r"];
      }
      var colorAsRGB = "#";
      for (var i = 0; i < 3; i++) {
        colorAsRGB += (
          "0" + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16)
        ).slice(-2);
      }
      return colorAsRGB;
    });

    var encodeColorString = (API.__private__.encodeColorString = function(
      options
    ) {
      var color;

      if (typeof options === "string") {
        options = {
          ch1: options
        };
      }
      var ch1 = options.ch1;
      var ch2 = options.ch2;
      var ch3 = options.ch3;
      var ch4 = options.ch4;
      var letterArray =
        options.pdfColorType === "draw" ? ["G", "RG", "K"] : ["g", "rg", "k"];

      if (typeof ch1 === "string" && ch1.charAt(0) !== "#") {
        var rgbColor = new RGBColor(ch1);
        if (rgbColor.ok) {
          ch1 = rgbColor.toHex();
        } else if (!/^\d*\.?\d*$/.test(ch1)) {
          throw new Error(
            'Invalid color "' + ch1 + '" passed to jsPDF.encodeColorString.'
          );
        }
      }
      //convert short rgb to long form
      if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) {
        ch1 = "#" + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3];
      }

      if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) {
        var hex = parseInt(ch1.substr(1), 16);
        ch1 = (hex >> 16) & 255;
        ch2 = (hex >> 8) & 255;
        ch3 = hex & 255;
      }

      if (
        typeof ch2 === "undefined" ||
        (typeof ch4 === "undefined" && ch1 === ch2 && ch2 === ch3)
      ) {
        // Gray color space.
        if (typeof ch1 === "string") {
          color = ch1 + " " + letterArray[0];
        } else {
          switch (options.precision) {
            case 2:
              color = f2(ch1 / 255) + " " + letterArray[0];
              break;
            case 3:
            default:
              color = f3(ch1 / 255) + " " + letterArray[0];
          }
        }
      } else if (typeof ch4 === "undefined" || typeof ch4 === "object") {
        // assume RGBA
        if (ch4 && !isNaN(ch4.a)) {
          //TODO Implement transparency.
          //WORKAROUND use white for now, if transparent, otherwise handle as rgb
          if (ch4.a === 0) {
            color = ["1.", "1.", "1.", letterArray[1]].join(" ");
            return color;
          }
        }
        // assume RGB
        if (typeof ch1 === "string") {
          color = [ch1, ch2, ch3, letterArray[1]].join(" ");
        } else {
          switch (options.precision) {
            case 2:
              color = [
                f2(ch1 / 255),
                f2(ch2 / 255),
                f2(ch3 / 255),
                letterArray[1]
              ].join(" ");
              break;
            default:
            case 3:
              color = [
                f3(ch1 / 255),
                f3(ch2 / 255),
                f3(ch3 / 255),
                letterArray[1]
              ].join(" ");
          }
        }
      } else {
        // assume CMYK
        if (typeof ch1 === "string") {
          color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" ");
        } else {
          switch (options.precision) {
            case 2:
              color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join(
                " "
              );
              break;
            case 3:
            default:
              color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join(
                " "
              );
          }
        }
      }
      return color;
    });

    var getFilters = (API.__private__.getFilters = function() {
      return filters;
    });

    var putStream = (API.__private__.putStream = function(options) {
      options = options || {};
      var data = options.data || "";
      var filters = options.filters || getFilters();
      var alreadyAppliedFilters = options.alreadyAppliedFilters || [];
      var addLength1 = options.addLength1 || false;
      var valueOfLength1 = data.length;

      var processedData = {};
      if (filters === true) {
        filters = ["FlateEncode"];
      }
      var keyValues = options.additionalKeyValues || [];
      if (typeof jsPDF.API.processDataByFilters !== "undefined") {
        processedData = jsPDF.API.processDataByFilters(data, filters);
      } else {
        processedData = { data: data, reverseChain: [] };
      }
      var filterAsString =
        processedData.reverseChain +
        (Array.isArray(alreadyAppliedFilters)
          ? alreadyAppliedFilters.join(" ")
          : alreadyAppliedFilters.toString());

      if (processedData.data.length !== 0) {
        keyValues.push({
          key: "Length",
          value: processedData.data.length
        });
        if (addLength1 === true) {
          keyValues.push({
            key: "Length1",
            value: valueOfLength1
          });
        }
      }

      if (filterAsString.length != 0) {
        if (filterAsString.split("/").length - 1 === 1) {
          keyValues.push({
            key: "Filter",
            value: filterAsString
          });
        } else {
          keyValues.push({
            key: "Filter",
            value: "[" + filterAsString + "]"
          });

          for (var j = 0; j < keyValues.length; j += 1) {
            if (keyValues[j].key === "DecodeParms") {
              var decodeParmsArray = [];

              for (
                var i = 0;
                i < processedData.reverseChain.split("/").length - 1;
                i += 1
              ) {
                decodeParmsArray.push("null");
              }

              decodeParmsArray.push(keyValues[j].value);
              keyValues[j].value = "[" + decodeParmsArray.join(" ") + "]";
            }
          }
        }
      }

      out("<<");
      for (var k = 0; k < keyValues.length; k++) {
        out("/" + keyValues[k].key + " " + keyValues[k].value);
      }
      out(">>");
      if (processedData.data.length !== 0) {
        out("stream");
        out(processedData.data);
        out("endstream");
      }
    });

    var putPage = (API.__private__.putPage = function(page) {
      var pageNumber = page.number;
      var data = page.data;
      var pageObjectNumber = page.objId;
      var pageContentsObjId = page.contentsObjId;

      newObjectDeferredBegin(pageObjectNumber, true);
      out("<</Type /Page");
      out("/Parent " + page.rootDictionaryObjId + " 0 R");
      out("/Resources " + page.resourceDictionaryObjId + " 0 R");
      out(
        "/MediaBox [" +
          parseFloat(hpf(page.mediaBox.bottomLeftX)) +
          " " +
          parseFloat(hpf(page.mediaBox.bottomLeftY)) +
          " " +
          hpf(page.mediaBox.topRightX) +
          " " +
          hpf(page.mediaBox.topRightY) +
          "]"
      );
      if (page.cropBox !== null) {
        out(
          "/CropBox [" +
            hpf(page.cropBox.bottomLeftX) +
            " " +
            hpf(page.cropBox.bottomLeftY) +
            " " +
            hpf(page.cropBox.topRightX) +
            " " +
            hpf(page.cropBox.topRightY) +
            "]"
        );
      }

      if (page.bleedBox !== null) {
        out(
          "/BleedBox [" +
            hpf(page.bleedBox.bottomLeftX) +
            " " +
            hpf(page.bleedBox.bottomLeftY) +
            " " +
            hpf(page.bleedBox.topRightX) +
            " " +
            hpf(page.bleedBox.topRightY) +
            "]"
        );
      }

      if (page.trimBox !== null) {
        out(
          "/TrimBox [" +
            hpf(page.trimBox.bottomLeftX) +
            " " +
            hpf(page.trimBox.bottomLeftY) +
            " " +
            hpf(page.trimBox.topRightX) +
            " " +
            hpf(page.trimBox.topRightY) +
            "]"
        );
      }

      if (page.artBox !== null) {
        out(
          "/ArtBox [" +
            hpf(page.artBox.bottomLeftX) +
            " " +
            hpf(page.artBox.bottomLeftY) +
            " " +
            hpf(page.artBox.topRightX) +
            " " +
            hpf(page.artBox.topRightY) +
            "]"
        );
      }

      if (typeof page.userUnit === "number" && page.userUnit !== 1.0) {
        out("/UserUnit " + page.userUnit);
      }

      events.publish("putPage", {
        objId: pageObjectNumber,
        pageContext: pagesContext[pageNumber],
        pageNumber: pageNumber,
        page: data
      });
      out("/Contents " + pageContentsObjId + " 0 R");
      out(">>");
      out("endobj");
      // Page content
      var pageContent = data.join("\n");

      if (apiMode === ApiMode.ADVANCED) {
        // if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again
        pageContent += "\nQ";
      }

      newObjectDeferredBegin(pageContentsObjId, true);
      putStream({
        data: pageContent,
        filters: getFilters()
      });
      out("endobj");
      return pageObjectNumber;
    });

    var putPages = (API.__private__.putPages = function() {
      var n,
        i,
        pageObjectNumbers = [];

      for (n = 1; n <= page; n++) {
        pagesContext[n].objId = newObjectDeferred();
        pagesContext[n].contentsObjId = newObjectDeferred();
      }

      for (n = 1; n <= page; n++) {
        pageObjectNumbers.push(
          putPage({
            number: n,
            data: pages[n],
            objId: pagesContext[n].objId,
            contentsObjId: pagesContext[n].contentsObjId,
            mediaBox: pagesContext[n].mediaBox,
            cropBox: pagesContext[n].cropBox,
            bleedBox: pagesContext[n].bleedBox,
            trimBox: pagesContext[n].trimBox,
            artBox: pagesContext[n].artBox,
            userUnit: pagesContext[n].userUnit,
            rootDictionaryObjId: rootDictionaryObjId,
            resourceDictionaryObjId: resourceDictionaryObjId
          })
        );
      }
      newObjectDeferredBegin(rootDictionaryObjId, true);
      out("<</Type /Pages");
      var kids = "/Kids [";
      for (i = 0; i < page; i++) {
        kids += pageObjectNumbers[i] + " 0 R ";
      }
      out(kids + "]");
      out("/Count " + page);
      out(">>");
      out("endobj");
      events.publish("postPutPages");
    });

    var putFont = function(font) {
      var pdfEscapeWithNeededParanthesis = function(text, flags) {
        var addParanthesis = text.indexOf(" ") !== -1;
        return addParanthesis
          ? "(" + pdfEscape(text, flags) + ")"
          : pdfEscape(text, flags);
      };

      events.publish("putFont", {
        font: font,
        out: out,
        newObject: newObject,
        putStream: putStream,
        pdfEscapeWithNeededParanthesis: pdfEscapeWithNeededParanthesis
      });

      if (font.isAlreadyPutted !== true) {
        font.objectNumber = newObject();
        out("<<");
        out("/Type /Font");
        out(
          "/BaseFont /" + pdfEscapeWithNeededParanthesis(font.postScriptName)
        );
        out("/Subtype /Type1");
        if (typeof font.encoding === "string") {
          out("/Encoding /" + font.encoding);
        }
        out("/FirstChar 32");
        out("/LastChar 255");
        out(">>");
        out("endobj");
      }
    };

    var putFonts = function() {
      for (var fontKey in fonts) {
        if (fonts.hasOwnProperty(fontKey)) {
          if (
            putOnlyUsedFonts === false ||
            (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey))
          ) {
            putFont(fonts[fontKey]);
          }
        }
      }
    };

    var putXObject = function(xObject) {
      xObject.objectNumber = newObject();

      var options = [];
      options.push({ key: "Type", value: "/XObject" });
      options.push({ key: "Subtype", value: "/Form" });
      options.push({
        key: "BBox",
        value:
          "[" +
          [
            hpf(xObject.x),
            hpf(xObject.y),
            hpf(xObject.x + xObject.width),
            hpf(xObject.y + xObject.height)
          ].join(" ") +
          "]"
      });
      options.push({
        key: "Matrix",
        value: "[" + xObject.matrix.toString() + "]"
      });
      // TODO: /Resources

      var stream = xObject.pages[1].join("\n");
      putStream({
        data: stream,
        additionalKeyValues: options
      });
      out("endobj");
    };

    var putXObjects = function() {
      for (var xObjectKey in renderTargets) {
        if (renderTargets.hasOwnProperty(xObjectKey)) {
          putXObject(renderTargets[xObjectKey]);
        }
      }
    };

    var interpolateAndEncodeRGBStream = function(colors, numberSamples) {
      var tValues = [];
      var t;
      var dT = 1.0 / (numberSamples - 1);
      for (t = 0.0; t < 1.0; t += dT) {
        tValues.push(t);
      }
      tValues.push(1.0);
      // add first and last control point if not present
      if (colors[0].offset != 0.0) {
        var c0 = {
          offset: 0.0,
          color: colors[0].color
        };
        colors.unshift(c0);
      }
      if (colors[colors.length - 1].offset != 1.0) {
        var c1 = {
          offset: 1.0,
          color: colors[colors.length - 1].color
        };
        colors.push(c1);
      }
      var out = "";
      var index = 0;

      for (var i = 0; i < tValues.length; i++) {
        t = tValues[i];
        while (t > colors[index + 1].offset) index++;
        var a = colors[index].offset;
        var b = colors[index + 1].offset;
        var d = (t - a) / (b - a);

        var aColor = colors[index].color;
        var bColor = colors[index + 1].color;

        out +=
          padd2Hex(
            Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)
          ) +
          padd2Hex(
            Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)
          ) +
          padd2Hex(
            Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16)
          );
      }
      return out.trim();
    };

    var putShadingPattern = function(pattern, numberSamples) {
      /*
       Axial patterns shade between the two points specified in coords, radial patterns between the inner
       and outer circle.
       The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now
       interpolated to equidistant samples and written to pdf as a sample (type 0) function.
       */
      // The number of color samples that should be used to describe the shading.
      // The higher, the more accurate the gradient will be.
      numberSamples || (numberSamples = 21);
      var funcObjectNumber = newObject();
      var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples);

      var options = [];
      options.push({ key: "FunctionType", value: "0" });
      options.push({ key: "Domain", value: "[0.0 1.0]" });
      options.push({ key: "Size", value: "[" + numberSamples + "]" });
      options.push({ key: "BitsPerSample", value: "8" });
      options.push({ key: "Range", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" });
      options.push({ key: "Decode", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" });

      putStream({
        data: stream,
        additionalKeyValues: options,
        alreadyAppliedFilters: ["/ASCIIHexDecode"]
      });
      out("endobj");

      pattern.objectNumber = newObject();
      out("<< /ShadingType " + pattern.type);
      out("/ColorSpace /DeviceRGB");
      var coords =
        "/Coords [" +
        hpf(parseFloat(pattern.coords[0])) +
        " " + // x1
        hpf(parseFloat(pattern.coords[1])) +
        " "; // y1
      if (pattern.type === 2) {
        // axial
        coords +=
          hpf(parseFloat(pattern.coords[2])) +
          " " + // x2
          hpf(parseFloat(pattern.coords[3])); // y2
      } else {
        // radial
        coords +=
          hpf(parseFloat(pattern.coords[2])) +
          " " + // r1
          hpf(parseFloat(pattern.coords[3])) +
          " " + // x2
          hpf(parseFloat(pattern.coords[4])) +
          " " + // y2
          hpf(parseFloat(pattern.coords[5])); // r2
      }
      coords += "]";
      out(coords);

      if (pattern.matrix) {
        out("/Matrix [" + pattern.matrix.toString() + "]");
      }
      out("/Function " + funcObjectNumber + " 0 R");
      out("/Extend [true true]");
      out(">>");
      out("endobj");
    };

    var putTilingPattern = function(pattern, deferredResourceDictionaryIds) {
      var resourcesObjectId = newObjectDeferred();
      var patternObjectId = newObject();

      deferredResourceDictionaryIds.push({
        resourcesOid: resourcesObjectId,
        objectOid: patternObjectId
      });

      pattern.objectNumber = patternObjectId;
      var options = [];
      options.push({ key: "Type", value: "/Pattern" });
      options.push({ key: "PatternType", value: "1" }); // tiling pattern
      options.push({ key: "PaintType", value: "1" }); // colored tiling pattern
      options.push({ key: "TilingType", value: "1" }); // constant spacing
      options.push({
        key: "BBox",
        value: "[" + pattern.boundingBox.map(hpf).join(" ") + "]"
      });
      options.push({ key: "XStep", value: hpf(pattern.xStep) });
      options.push({ key: "YStep", value: hpf(pattern.yStep) });
      options.push({ key: "Resources", value: resourcesObjectId + " 0 R" });
      if (pattern.matrix) {
        options.push({
          key: "Matrix",
          value: "[" + pattern.matrix.toString() + "]"
        });
      }

      putStream({
        data: pattern.stream,
        additionalKeyValues: options
      });
      out("endobj");
    };

    var putPatterns = function(deferredResourceDictionaryIds) {
      var patternKey;
      for (patternKey in patterns) {
        if (patterns.hasOwnProperty(patternKey)) {
          if (patterns[patternKey] instanceof API.ShadingPattern) {
            putShadingPattern(patterns[patternKey]);
          } else if (patterns[patternKey] instanceof API.TilingPattern) {
            putTilingPattern(
              patterns[patternKey],
              deferredResourceDictionaryIds
            );
          }
        }
      }
    };

    var putGState = function(gState) {
      gState.objectNumber = newObject();
      out("<<");
      for (var p in gState) {
        switch (p) {
          case "opacity":
            out("/ca " + f2(gState[p]));
            break;
          case "stroke-opacity":
            out("/CA " + f2(gState[p]));
            break;
        }
      }
      out(">>");
      out("endobj");
    };

    var putGStates = function() {
      var gStateKey;
      for (gStateKey in gStates) {
        if (gStates.hasOwnProperty(gStateKey)) {
          putGState(gStates[gStateKey]);
        }
      }
    };

    var putXobjectDict = function() {
      out("/XObject <<");
      for (var xObjectKey in renderTargets) {
        if (
          renderTargets.hasOwnProperty(xObjectKey) &&
          renderTargets[xObjectKey].objectNumber >= 0
        ) {
          out(
            "/" +
              xObjectKey +
              " " +
              renderTargets[xObjectKey].objectNumber +
              " 0 R"
          );
        }
      }

      // Loop through images, or other data objects
      events.publish("putXobjectDict");
      out(">>");
    };

    var putFontDict = function() {
      out("/Font <<");

      for (var fontKey in fonts) {
        if (fonts.hasOwnProperty(fontKey)) {
          if (
            putOnlyUsedFonts === false ||
            (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey))
          ) {
            out("/" + fontKey + " " + fonts[fontKey].objectNumber + " 0 R");
          }
        }
      }
      out(">>");
    };

    var putShadingPatternDict = function() {
      if (Object.keys(patterns).length > 0) {
        out("/Shading <<");
        for (var patternKey in patterns) {
          if (
            patterns.hasOwnProperty(patternKey) &&
            patterns[patternKey] instanceof API.ShadingPattern &&
            patterns[patternKey].objectNumber >= 0
          ) {
            out(
              "/" +
                patternKey +
                " " +
                patterns[patternKey].objectNumber +
                " 0 R"
            );
          }
        }

        events.publish("putShadingPatternDict");
        out(">>");
      }
    };

    var putTilingPatternDict = function(objectOid) {
      if (Object.keys(patterns).length > 0) {
        out("/Pattern <<");
        for (var patternKey in patterns) {
          if (
            patterns.hasOwnProperty(patternKey) &&
            patterns[patternKey] instanceof API.TilingPattern &&
            patterns[patternKey].objectNumber >= 0 &&
            patterns[patternKey].objectNumber < objectOid // prevent cyclic dependencies
          ) {
            out(
              "/" +
                patternKey +
                " " +
                patterns[patternKey].objectNumber +
                " 0 R"
            );
          }
        }
        events.publish("putTilingPatternDict");
        out(">>");
      }
    };

    var putGStatesDict = function() {
      if (Object.keys(gStates).length > 0) {
        var gStateKey;
        out("/ExtGState <<");
        for (gStateKey in gStates) {
          if (
            gStates.hasOwnProperty(gStateKey) &&
            gStates[gStateKey].objectNumber >= 0
          ) {
            out(
              "/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R"
            );
          }
        }

        events.publish("putGStateDict");
        out(">>");
      }
    };

    var putResourceDictionary = function(objectIds) {
      newObjectDeferredBegin(objectIds.resourcesOid, true);
      out("<<");
      out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]");
      putFontDict();
      putShadingPatternDict();
      putTilingPatternDict(objectIds.objectOid);
      putGStatesDict();
      putXobjectDict();
      out(">>");
      out("endobj");
    };

    var putResources = function() {
      // FormObjects, Patterns etc. might use other FormObjects/Patterns/Images
      // which means their resource dictionaries must contain the already resolved
      // object ids. For this reason we defer the serialization of the resource
      // dicts until all objects have been serialized and have object ids.
      //
      // In order to prevent cyclic dependencies (which Adobe Reader doesn't like),
      // we only put all oids that are smaller than the oid of the object the
      // resource dict belongs to. This is correct behavior, since the streams
      // may only use other objects that have already been defined and thus appear
      // earlier in their respective collection.
      // Currently, this only affects tiling patterns, but a (more) correct
      // implementation of FormObjects would also define their own resource dicts.
      var deferredResourceDictionaryIds = [];

      putFonts();
      putGStates();
      putXObjects();
      putPatterns(deferredResourceDictionaryIds);

      events.publish("putResources");
      deferredResourceDictionaryIds.forEach(putResourceDictionary);
      putResourceDictionary({
        resourcesOid: resourceDictionaryObjId,
        objectOid: Number.MAX_SAFE_INTEGER // output all objects
      });
      events.publish("postPutResources");
    };

    var putAdditionalObjects = function() {
      events.publish("putAdditionalObjects");
      for (var i = 0; i < additionalObjects.length; i++) {
        var obj = additionalObjects[i];
        newObjectDeferredBegin(obj.objId, true);
        out(obj.content);
        out("endobj");
      }
      events.publish("postPutAdditionalObjects");
    };

    var addFontToFontDictionary = function(font) {
      fontmap[font.fontName] = fontmap[font.fontName] || {};
      fontmap[font.fontName][font.fontStyle] = font.id;
    };

    var addFont = function(
      postScriptName,
      fontName,
      fontStyle,
      encoding,
      isStandardFont
    ) {
      var font = {
        id: "F" + (Object.keys(fonts).length + 1).toString(10),
        postScriptName: postScriptName,
        fontName: fontName,
        fontStyle: fontStyle,
        encoding: encoding,
        isStandardFont: isStandardFont || false,
        metadata: {}
      };
      var instance = this;

      events.publish("addFont", {
        font: font,
        instance: instance
      });

      fonts[font.id] = font;
      addFontToFontDictionary(font);
      return font.id;
    };

    var addFonts = function(arrayOfFonts) {
      for (var i = 0, l = standardFonts.length; i < l; i++) {
        var fontKey = addFont(
          arrayOfFonts[i][0],
          arrayOfFonts[i][1],
          arrayOfFonts[i][2],
          standardFonts[i][3],
          true
        );

        if (putOnlyUsedFonts === false) {
          usedFonts[fontKey] = true;
        }
        // adding aliases for standard fonts, this time matching the capitalization
        var parts = arrayOfFonts[i][0].split("-");
        addFontToFontDictionary({
          id: fontKey,
          fontName: parts[0],
          fontStyle: parts[1] || ""
        });
      }
      events.publish("addFonts", {
        fonts: fonts,
        dictionary: fontmap
      });
    };

    var SAFE = function __safeCall(fn) {
      fn.foo = function __safeCallWrapper() {
        try {
          return fn.apply(this, arguments);
        } catch (e) {
          var stack = e.stack || "";
          if (~stack.indexOf(" at ")) stack = stack.split(" at ")[1];
          var m =
            "Error in function " +
            stack.split("\n")[0].split("<")[0] +
            ": " +
            e.message;
          if (global.console) {
            global.console.error(m, e);
            if (global.alert) alert(m);
          } else {
            throw new Error(m);
          }
        }
      };
      fn.foo.bar = fn;
      return fn.foo;
    };

    var to8bitStream = function(text, flags) {
      /**
       * PDF 1.3 spec:
       * "For text strings encoded in Unicode, the first two bytes must be 254 followed by
       * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts
       * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely
       * to be a meaningful beginning of a word or phrase.) The remainder of the
       * string consists of Unicode character codes, according to the UTF-16 encoding
       * specified in the Unicode standard, version 2.0. Commonly used Unicode values
       * are represented as 2 bytes per character, with the high-order byte appearing first
       * in the string."
       *
       * In other words, if there are chars in a string with char code above 255, we
       * recode the string to UCS2 BE - string doubles in length and BOM is prepended.
       *
       * HOWEVER!
       * Actual *content* (body) text (as opposed to strings used in document properties etc)
       * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID)
       *
       * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have
       * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could
       * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode
       * code page. There, however, all characters in the stream are treated as GIDs,
       * including BOM, which is the reason we need to skip BOM in content text (i.e. that
       * that is tied to a font).
       *
       * To signal this "special" PDFEscape / to8bitStream handling mode,
       * API.text() function sets (unless you overwrite it with manual values
       * given to API.text(.., flags) )
       * flags.autoencode = true
       * flags.noBOM = true
       *
       * ===================================================================================
       * `flags` properties relied upon:
       *   .sourceEncoding = string with encoding label.
       *                     "Unicode" by default. = encoding of the incoming text.
       *                     pass some non-existing encoding name
       *                     (ex: 'Do not touch my strings! I know what I am doing.')
       *                     to make encoding code skip the encoding step.
       *   .outputEncoding = Either valid PDF encoding name
       *                     (must be supported by jsPDF font metrics, otherwise no encoding)
       *                     or a JS object, where key = sourceCharCode, value = outputCharCode
       *                     missing keys will be treated as: sourceCharCode === outputCharCode
       *   .noBOM
       *       See comment higher above for explanation for why this is important
       *   .autoencode
       *       See comment higher above for explanation for why this is important
       */

      var i,
        l,
        sourceEncoding,
        encodingBlock,
        outputEncoding,
        newtext,
        isUnicode,
        ch,
        bch;

      flags = flags || {};
      sourceEncoding = flags.sourceEncoding || "Unicode";
      outputEncoding = flags.outputEncoding;

      // This 'encoding' section relies on font metrics format
      // attached to font objects by, among others,
      // "Willow Systems' standard_font_metrics plugin"
      // see jspdf.plugin.standard_font_metrics.js for format
      // of the font.metadata.encoding Object.
      // It should be something like
      //   .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}}
      //   .widths = {0:width, code:width, ..., 'fof':divisor}
      //   .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...}
      if (
        (flags.autoencode || outputEncoding) &&
        fonts[activeFontKey].metadata &&
        fonts[activeFontKey].metadata[sourceEncoding] &&
        fonts[activeFontKey].metadata[sourceEncoding].encoding
      ) {
        encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding;

        // each font has default encoding. Some have it clearly defined.
        if (!outputEncoding && fonts[activeFontKey].encoding) {
          outputEncoding = fonts[activeFontKey].encoding;
        }

        // Hmmm, the above did not work? Let's try again, in different place.
        if (!outputEncoding && encodingBlock.codePages) {
          outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default
        }

        if (typeof outputEncoding === "string") {
          outputEncoding = encodingBlock[outputEncoding];
        }
        // we want output encoding to be a JS Object, where
        // key = sourceEncoding's character code and
        // value = outputEncoding's character code.
        if (outputEncoding) {
          isUnicode = false;
          newtext = [];
          for (i = 0, l = text.length; i < l; i++) {
            ch = outputEncoding[text.charCodeAt(i)];
            if (ch) {
              newtext.push(String.fromCharCode(ch));
            } else {
              newtext.push(text[i]);
            }

            // since we are looping over chars anyway, might as well
            // check for residual unicodeness
            if (newtext[i].charCodeAt(0) >> 8) {
              /* more than 255 */
              isUnicode = true;
            }
          }
          text = newtext.join("");
        }
      }

      i = text.length;
      // isUnicode may be set to false above. Hence the triple-equal to undefined
      while (isUnicode === undefined && i !== 0) {
        if (text.charCodeAt(i - 1) >> 8) {
          /* more than 255 */
          isUnicode = true;
        }
        i--;
      }
      if (!isUnicode) {
        return text;
      }

      newtext = flags.noBOM ? [] : [254, 255];
      for (i = 0, l = text.length; i < l; i++) {
        ch = text.charCodeAt(i);
        bch = ch >> 8; // divide by 256
        if (bch >> 8) {
          /* something left after dividing by 256 second time */
          throw new Error(
            "Character at position " +
              i +
              " of string '" +
              text +
              "' exceeds 16bits. Cannot be encoded into UCS-2 BE"
          );
        }
        newtext.push(bch);
        newtext.push(ch - (bch << 8));
      }
      return String.fromCharCode.apply(undefined, newtext);
    };

    var pdfEscape = (API.__private__.pdfEscape = API.pdfEscape = function(
      text,
      flags
    ) {
      /**
       * Replace '/', '(', and ')' with pdf-safe versions
       *
       * Doing to8bitStream does NOT make this PDF display unicode text. For that
       * we also need to reference a unicode font and embed it - royal pain in the rear.
       *
       * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars,
       * which JavaScript Strings are happy to provide. So, while we still cannot display
       * 2-byte characters property, at least CONDITIONALLY converting (entire string containing)
       * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF
       * is still parseable.
       * This will allow immediate support for unicode in document properties strings.
       */
      return to8bitStream(text, flags)
        .replace(/\\/g, "\\\\")
        .replace(/\(/g, "\\(")
        .replace(/\)/g, "\\)");
    });

    var beginPage = (API.__private__.beginPage = function(format) {
      pages[++page] = [];
      pagesContext[page] = {
        objId: 0,
        contentsObjId: 0,
        userUnit: Number(userUnit),
        artBox: null,
        bleedBox: null,
        cropBox: null,
        trimBox: null,
        mediaBox: {
          bottomLeftX: 0,
          bottomLeftY: 0,
          topRightX: Number(format[0]),
          topRightY: Number(format[1])
        }
      };
      _setPage(page);
      setOutputDestination(pages[currentPage]);
    });

    var _addPage = function(parmFormat, parmOrientation) {
      var dimensions, width, height;

      orientation = parmOrientation || orientation;

      if (typeof parmFormat === "string") {
        dimensions = getPageFormat(parmFormat.toLowerCase());
        if (Array.isArray(dimensions)) {
          width = dimensions[0];
          height = dimensions[1];
        }
      }

      if (Array.isArray(parmFormat)) {
        width = parmFormat[0] * scaleFactor;
        height = parmFormat[1] * scaleFactor;
      }

      if (isNaN(width)) {
        width = format[0];
        height = format[1];
      }

      if (width > 14400 || height > 14400) {
        console.warn(
          "A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"
        );
        width = Math.min(14400, width);
        height = Math.min(14400, height);
      }

      format = [width, height];

      switch (orientation.substr(0, 1)) {
        case "l":
          if (height > width) {
            format = [height, width];
          }
          break;
        case "p":
          if (width > height) {
            format = [height, width];
          }
          break;
      }

      beginPage(format);

      // Set line width
      setLineWidth(lineWidth);
      // Set draw color
      out(strokeColor);
      // resurrecting non-default line caps, joins
      if (lineCapID !== 0) {
        out(lineCapID + " J");
      }
      if (lineJoinID !== 0) {
        out(lineJoinID + " j");
      }
      events.publish("addPage", {
        pageNumber: page
      });
    };

    var _deletePage = function(n) {
      if (n > 0 && n <= page) {
        pages.splice(n, 1);
        pagesContext.splice(n, 1);
        page--;
        if (currentPage > page) {
          currentPage = page;
        }
        this.setPage(currentPage);
      }
    };

    var _setPage = function(n) {
      if (n > 0 && n <= page) {
        currentPage = n;
      }
    };

    var getNumberOfPages = (API.__private__.getNumberOfPages = API.getNumberOfPages = function() {
      return pages.length - 1;
    });

    /**
     * Returns a document-specific font key - a label assigned to a
     * font name + font type combination at the time the font was added
     * to the font inventory.
     *
     * Font key is used as label for the desired font for a block of text
     * to be added to the PDF document stream.
     * @private
     * @function
     * @param fontName {string} can be undefined on "falthy" to indicate "use current"
     * @param fontStyle {string} can be undefined on "falthy" to indicate "use current"
     * @returns {string} Font key.
     * @ignore
     */
    var getFont = function(fontName, fontStyle, options) {
      var key = undefined,
        fontNameLowerCase;
      options = options || {};

      fontName =
        fontName !== undefined ? fontName : fonts[activeFontKey].fontName;
      fontStyle =
        fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle;
      fontNameLowerCase = fontName.toLowerCase();

      if (
        fontmap[fontNameLowerCase] !== undefined &&
        fontmap[fontNameLowerCase][fontStyle] !== undefined
      ) {
        key = fontmap[fontNameLowerCase][fontStyle];
      } else if (
        fontmap[fontName] !== undefined &&
        fontmap[fontName][fontStyle] !== undefined
      ) {
        key = fontmap[fontName][fontStyle];
      } else {
        if (options.disableWarning === false) {
          console.warn(
            "Unable to look up font label for font '" +
              fontName +
              "', '" +
              fontStyle +
              "'. Refer to getFontList() for available fonts."
          );
        }
      }

      if (!key && !options.noFallback) {
        key = fontmap["times"][fontStyle];
        if (key == null) {
          key = fontmap["times"]["normal"];
        }
      }
      return key;
    };

    var putInfo = (API.__private__.putInfo = function() {
      newObject();
      out("<<");
      out("/Producer (jsPDF " + jsPDF.version + ")");
      for (var key in documentProperties) {
        if (documentProperties.hasOwnProperty(key) && documentProperties[key]) {
          out(
            "/" +
              key.substr(0, 1).toUpperCase() +
              key.substr(1) +
              " (" +
              pdfEscape(documentProperties[key]) +
              ")"
          );
        }
      }
      out("/CreationDate (" + creationDate + ")");
      out(">>");
      out("endobj");
    });

    var putCatalog = (API.__private__.putCatalog = function(options) {
      options = options || {};
      var tmpRootDictionaryObjId =
        options.rootDictionaryObjId || rootDictionaryObjId;
      newObject();
      out("<<");
      out("/Type /Catalog");
      out("/Pages " + tmpRootDictionaryObjId + " 0 R");
      // PDF13ref Section 7.2.1
      if (!zoomMode) zoomMode = "fullwidth";
      switch (zoomMode) {
        case "fullwidth":
          out("/OpenAction [3 0 R /FitH null]");
          break;
        case "fullheight":
          out("/OpenAction [3 0 R /FitV null]");
          break;
        case "fullpage":
          out("/OpenAction [3 0 R /Fit]");
          break;
        case "original":
          out("/OpenAction [3 0 R /XYZ null null 1]");
          break;
        default:
          var pcn = "" + zoomMode;
          if (pcn.substr(pcn.length - 1) === "%")
            zoomMode = parseInt(zoomMode) / 100;
          if (typeof zoomMode === "number") {
            out("/OpenAction [3 0 R /XYZ null null " + f2(zoomMode) + "]");
          }
      }
      if (!layoutMode) layoutMode = "continuous";
      switch (layoutMode) {
        case "continuous":
          out("/PageLayout /OneColumn");
          break;
        case "single":
          out("/PageLayout /SinglePage");
          break;
        case "two":
        case "twoleft":
          out("/PageLayout /TwoColumnLeft");
          break;
        case "tworight":
          out("/PageLayout /TwoColumnRight");
          break;
      }
      if (pageMode) {
        /**
         * A name object specifying how the document should be displayed when opened:
         * UseNone      : Neither document outline nor thumbnail images visible -- DEFAULT
         * UseOutlines  : Document outline visible
         * UseThumbs    : Thumbnail images visible
         * FullScreen   : Full-screen mode, with no menu bar, window controls, or any other window visible
         */
        out("/PageMode /" + pageMode);
      }
      events.publish("putCatalog");
      out(">>");
      out("endobj");
    });

    var putTrailer = (API.__private__.putTrailer = function() {
      out("trailer");
      out("<<");
      out("/Size " + (objectNumber + 1));
      out("/Root " + objectNumber + " 0 R");
      out("/Info " + (objectNumber - 1) + " 0 R");
      out("/ID [ <" + fileId + "> <" + fileId + "> ]");
      out(">>");
    });

    var putHeader = (API.__private__.putHeader = function() {
      out("%PDF-" + pdfVersion);
      out("%\xBA\xDF\xAC\xE0");
    });

    var putXRef = (API.__private__.putXRef = function() {
      var p = "0000000000";

      out("xref");
      out("0 " + (objectNumber + 1));
      out("0000000000 65535 f");
      for (var i = 1; i <= objectNumber; i++) {
        var offset = offsets[i];
        if (typeof offset === "function") {
          out((p + offsets[i]()).slice(-10) + " 00000 n");
        } else {
          if (typeof offsets[i] !== "undefined") {
            out((p + offsets[i]).slice(-10) + " 00000 n");
          } else {
            out("0000000000 00000 n");
          }
        }
      }
    });

    var buildDocument = (API.__private__.buildDocument = function() {
      resetDocument();
      setOutputDestination(content);

      events.publish("buildDocument");

      putHeader();
      putPages();
      putAdditionalObjects();
      putResources();
      putInfo();
      putCatalog();

      var offsetOfXRef = contentLength;
      putXRef();
      putTrailer();
      out("startxref");
      out("" + offsetOfXRef);
      out("%%EOF");

      setOutputDestination(pages[currentPage]);

      return content.join("\n");
    });

    var getBlob = (API.__private__.getBlob = function(data) {
      return new Blob([getArrayBuffer(data)], {
        type: "application/pdf"
      });
    });

    /**
     * Generates the PDF document.
     *
     * If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
     *
     * @param {string} type A string identifying one of the possible output types. Possible values are 'arraybuffer', 'blob', 'bloburi'/'bloburl', 'datauristring'/'dataurlstring', 'datauri'/'dataurl', 'dataurlnewwindow', 'pdfobjectnewwindow', 'pdfjsnewwindow'.
     * @param {Object} options An object providing some additional signalling to PDF generator. Possible options are 'filename'.
     *
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name output
     */
    var output = (API.output = API.__private__.output = SAFE(function output(
      type,
      options
    ) {
      options = options || {};

      if (typeof options === "string") {
        options = {
          filename: options
        };
      } else {
        options.filename = options.filename || "generated.pdf";
      }

      switch (type) {
        case undefined:
          return buildDocument();
        case "save":
          API.save(options.filename);
          break;
        case "arraybuffer":
          return getArrayBuffer(buildDocument());
        case "blob":
          return getBlob(buildDocument());
        case "bloburi":
        case "bloburl":
          // Developer is responsible of calling revokeObjectURL
          if (
            typeof global.URL !== "undefined" &&
            typeof global.URL.createObjectURL === "function"
          ) {
            return (
              (global.URL &&
                global.URL.createObjectURL(getBlob(buildDocument()))) ||
              void 0
            );
          } else {
            console.warn(
              "bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser."
            );
          }
          break;
        case "datauristring":
        case "dataurlstring":
          var dataURI = "";
          var pdfDocument = buildDocument();
          try {
            dataURI = btoa(pdfDocument);
          } catch (e) {
            dataURI = btoa(unescape(encodeURIComponent(pdfDocument)));
          }
          return (
            "data:application/pdf;filename=" +
            options.filename +
            ";base64," +
            dataURI
          );
        case "pdfobjectnewwindow":
          if (Object.prototype.toString.call(global) === "[object Window]") {
            var pdfObjectUrl =
              options.pdfObjectUrl ||
              "https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js";
            var htmlForNewWindow =
              "<html>" +
              '<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;}  </style><body><script src="' +
              pdfObjectUrl +
              '"></script><script >PDFObject.embed("' +
              this.output("dataurlstring") +
              '", ' +
              JSON.stringify(options) +
              ");</script></body></html>";
            var nW = global.open();

            if (nW !== null) {
              nW.document.write(htmlForNewWindow);
            }
            return nW;
          } else {
            throw new Error(
              "The option pdfobjectnewwindow just works in a browser-environment."
            );
          }
        case "pdfjsnewwindow":
          if (Object.prototype.toString.call(global) === "[object Window]") {
            var pdfJsUrl =
              options.pdfJsUrl || "examples/PDF.js/web/viewer.html";
            var htmlForPDFjsNewWindow =
              "<html>" +
              "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;}  </style>" +
              '<body><iframe id="pdfViewer" src="' +
              pdfJsUrl +
              '?file=" width="500px" height="400px" />' +
              "</body></html>";
            var PDFjsNewWindow = global.open();

            if (PDFjsNewWindow !== null) {
              PDFjsNewWindow.document.write(htmlForPDFjsNewWindow);
              var scope = this;
              PDFjsNewWindow.document.documentElement.querySelector(
                "#pdfViewer"
              ).onload = function() {
                PDFjsNewWindow.document.documentElement
                  .querySelector("#pdfViewer")
                  .contentWindow.PDFViewerApplication.open(
                    scope.output("bloburl")
                  );
              };
            }
            return PDFjsNewWindow;
          } else {
            throw new Error(
              "The option pdfjsnewwindow just works in a browser-environment."
            );
          }
        case "dataurlnewwindow":
          if (Object.prototype.toString.call(global) === "[object Window]") {
            var htmlForDataURLNewWindow =
              "<html>" +
              "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;}  </style>" +
              "<body>" +
              '<iframe src="' +
              this.output("datauristring", options) +
              '"></iframe>' +
              "</body></html>";
            var dataURLNewWindow = global.open();
            if (dataURLNewWindow !== null) {
              dataURLNewWindow.document.write(htmlForDataURLNewWindow);
            }
            if (dataURLNewWindow || typeof safari === "undefined")
              return dataURLNewWindow;
          } else {
            throw new Error(
              "The option dataurlnewwindow just works in a browser-environment."
            );
          }
          break;
        case "datauri":
        case "dataurl":
          return (global.document.location.href = this.output(
            "datauristring",
            options
          ));
        default:
          return null;
      }
    }));

    /**
     * Used to see if a supplied hotfix was requested when the pdf instance was created.
     * @param {string} hotfixName - The name of the hotfix to check.
     * @returns {boolean}
     */
    var hasHotfix = function(hotfixName) {
      return (
        Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1
      );
    };

    switch (unit) {
      case "pt":
        scaleFactor = 1;
        break;
      case "mm":
        scaleFactor = 72 / 25.4;
        break;
      case "cm":
        scaleFactor = 72 / 2.54;
        break;
      case "in":
        scaleFactor = 72;
        break;
      case "px":
        if (hasHotfix("px_scaling") == true) {
          scaleFactor = 72 / 96;
        } else {
          scaleFactor = 96 / 72;
        }
        break;
      case "pc":
        scaleFactor = 12;
        break;
      case "em":
        scaleFactor = 12;
        break;
      case "ex":
        scaleFactor = 6;
        break;
      default:
        throw new Error("Invalid unit: " + unit);
    }

    setCreationDate();
    setFileId();

    //---------------------------------------
    // Public API

    var getPageInfo = (API.__private__.getPageInfo = API.getPageInfo = function(
      pageNumberOneBased
    ) {
      if (isNaN(pageNumberOneBased) || pageNumberOneBased % 1 !== 0) {
        throw new Error("Invalid argument passed to jsPDF.getPageInfo");
      }
      var objId = pagesContext[pageNumberOneBased].objId;
      return {
        objId: objId,
        pageNumber: pageNumberOneBased,
        pageContext: pagesContext[pageNumberOneBased]
      };
    });

    var getPageInfoByObjId = (API.__private__.getPageInfoByObjId = function(
      objId
    ) {
      if (isNaN(objId) || objId % 1 !== 0) {
        throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId");
      }
      for (var pageNumber in pagesContext) {
        if (pagesContext[pageNumber].objId === objId) {
          break;
        }
      }
      return getPageInfo(pageNumber);
    });

    var getCurrentPageInfo = (API.__private__.getCurrentPageInfo = API.getCurrentPageInfo = function() {
      return {
        objId: pagesContext[currentPage].objId,
        pageNumber: currentPage,
        pageContext: pagesContext[currentPage]
      };
    });

    /**
     * Adds (and transfers the focus to) new page to the PDF document.
     * @param format {String/Array} The format of the new page. Can be: <ul><li>a0 - a10</li><li>b0 - b10</li><li>c0 - c10</li><li>dl</li><li>letter</li><li>government-letter</li><li>legal</li><li>junior-legal</li><li>ledger</li><li>tabloid</li><li>credit-card</li></ul><br />
     * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89]
     * @param orientation {string} Orientation of the new page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l").
     * @function
     * @instance
     * @returns {jsPDF}
     *
     * @memberof jsPDF#
     * @name addPage
     */
    API.addPage = function() {
      _addPage.apply(this, arguments);
      return this;
    };
    /**
     * Adds (and transfers the focus to) new page to the PDF document.
     * @function
     * @instance
     * @returns {jsPDF}
     *
     * @memberof jsPDF#
     * @name setPage
     * @param {number} page Switch the active page to the page number specified (indexed starting at 1).
     * @example
     * doc = jsPDF()
     * doc.addPage()
     * doc.addPage()
     * doc.text('I am on page 3', 10, 10)
     * doc.setPage(1)
     * doc.text('I am on page 1', 10, 10)
     */
    API.setPage = function() {
      _setPage.apply(this, arguments);
      setOutputDestination.call(this, pages[currentPage]);
      return this;
    };

    /**
     * @name insertPage
     * @memberof jsPDF#
     *
     * @function
     * @instance
     * @param {Object} beforePage
     * @returns {jsPDF}
     */
    API.insertPage = function(beforePage) {
      this.addPage();
      this.movePage(currentPage, beforePage);
      return this;
    };

    /**
     * @name movePage
     * @memberof jsPDF#
     * @function
     * @instance
     * @param {number} targetPage
     * @param {number} beforePage
     * @returns {jsPDF}
     */
    API.movePage = function(targetPage, beforePage) {
      var tmpPages, tmpPagesContext;
      if (targetPage > beforePage) {
        tmpPages = pages[targetPage];
        tmpPagesContext = pagesContext[targetPage];
        for (var i = targetPage; i > beforePage; i--) {
          pages[i] = pages[i - 1];
          pagesContext[i] = pagesContext[i - 1];
        }
        pages[beforePage] = tmpPages;
        pagesContext[beforePage] = tmpPagesContext;
        this.setPage(beforePage);
      } else if (targetPage < beforePage) {
        tmpPages = pages[targetPage];
        tmpPagesContext = pagesContext[targetPage];
        for (var j = targetPage; j < beforePage; j++) {
          pages[j] = pages[j + 1];
          pagesContext[j] = pagesContext[j + 1];
        }
        pages[beforePage] = tmpPages;
        pagesContext[beforePage] = tmpPagesContext;
        this.setPage(beforePage);
      }
      return this;
    };

    /**
     * Deletes a page from the PDF.
     * @name deletePage
     * @memberof jsPDF#
     * @function
     * @param {number} targetPage
     * @instance
     * @returns {jsPDF}
     */
    API.deletePage = function() {
      _deletePage.apply(this, arguments);
      return this;
    };

    /**
     * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings.
     *
     * @function
     * @instance
     * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down per font, spacing settings declared before this call.
     * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page.
     * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page.
     * @param {Object} [options] - Collection of settings signaling how the text must be encoded.
     * @param {string} [options.align=left] - The alignment of the text, possible values: left, center, right, justify.
     * @param {string} [options.baseline=alphabetic] - Sets text baseline used when drawing the text, possible values: alphabetic, ideographic, bottom, top, middle, hanging
     * @param {string} [options.angle=0] - Rotate the text clockwise or counterclockwise. Expects the angle in degree.
     * @param {string} [options.rotationDirection=1] - Direction of the rotation. 0 = clockwise, 1 = counterclockwise.
     * @param {string} [options.charSpace=0] - The space between each letter.
     * @param {string} [options.lineHeightFactor=1.15] - The lineheight of each line.
     * @param {string} [options.flags] - Flags for to8bitStream.
     * @param {string} [options.flags.noBOM=true] - Don't add BOM to Unicode-text.
     * @param {string} [options.flags.autoencode=true] - Autoencode the Text.
     * @param {string} [options.maxWidth=0] - Split the text by given width, 0 = no split.
     * @param {string} [options.renderingMode=fill] - Set how the text should be rendered, possible values: fill, stroke, fillThenStroke, invisible, fillAndAddForClipping, strokeAndAddPathForClipping, fillThenStrokeAndAddToPathForClipping, addToPathForClipping.
     * @param {boolean} [options.isInputVisual] - Option for the BidiEngine
     * @param {boolean} [options.isOutputVisual] - Option for the BidiEngine
     * @param {boolean} [options.isInputRtl] - Option for the BidiEngine
     * @param {boolean} [options.isOutputRtl] - Option for the BidiEngine
     * @param {boolean} [options.isSymmetricSwapping] - Option for the BidiEngine
     * @param {number|Matrix} transform If transform is a number the text will be rotated by this value around the anchor set by x and y.
     *
     * If it is a Matrix, this matrix gets directly applied to the text, which allows shearing
     * effects etc.; the x and y offsets are then applied AFTER the coordinate system has been established by this
     * matrix. This means passing a rotation matrix that is equivalent to some rotation angle will in general yield a
     * DIFFERENT result. A matrix is only allowed in "advanced" API mode.
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name text
     */
    API.__private__.text = API.text = function(text, x, y, options, transform) {
      /*
       * Inserts something like this into PDF
       *   BT
       *    /F1 16 Tf  % Font name + size
       *    16 TL % How many units down for next line in multiline text
       *    0 g % color
       *    28.35 813.54 Td % position
       *    (line one) Tj
       *    T* (line two) Tj
       *    T* (line three) Tj
       *   ET
       */
      options = options || {};
      var scope = options.scope || this;
      var payload, da, angle, align, charSpace, maxWidth, flags;

      // Pre-August-2012 the order of arguments was function(x, y, text, flags)
      // in effort to make all calls have similar signature like
      //   function(data, coordinates... , miscellaneous)
      // this method had its args flipped.
      // code below allows backward compatibility with old arg order.
      if (
        typeof text === "number" &&
        typeof x === "number" &&
        (typeof y === "string" || Array.isArray(y))
      ) {
        var tmp = y;
        y = x;
        x = text;
        text = tmp;
      }

      var transformationMatrix;

      if (arguments[3] instanceof Matrix === false) {
        flags = arguments[3];
        angle = arguments[4];
        align = arguments[5];

        if (typeof flags !== "object" || flags === null) {
          if (typeof angle === "string") {
            align = angle;
            angle = null;
          }
          if (typeof flags === "string") {
            align = flags;
            flags = null;
          }
          if (typeof flags === "number") {
            angle = flags;
            flags = null;
          }
          options = {
            flags: flags,
            angle: angle,
            align: align
          };
        }
      } else {
        advancedApiModeTrap(
          "The transform parameter of text() with a Matrix value"
        );
        transformationMatrix = transform;
      }

      if (
        isNaN(x) ||
        isNaN(y) ||
        typeof text === "undefined" ||
        text === null
      ) {
        throw new Error("Invalid arguments passed to jsPDF.text");
      }

      if (text.length === 0) {
        return scope;
      }

      var xtra = "";
      var isHex = false;
      var lineHeight =
        typeof options.lineHeightFactor === "number"
          ? options.lineHeightFactor
          : lineHeightFactor;
      var scaleFactor = scope.internal.scaleFactor;

      function ESC(s) {
        s = s.split("\t").join(Array(options.TabLen || 9).join(" "));
        return pdfEscape(s, flags);
      }

      function transformTextToSpecialArray(text) {
        //we don't want to destroy original text array, so cloning it
        var sa = text.concat();
        var da = [];
        var len = sa.length;
        var curDa;
        //we do array.join('text that must not be PDFescaped")
        //thus, pdfEscape each component separately
        while (len--) {
          curDa = sa.shift();
          if (typeof curDa === "string") {
            da.push(curDa);
          } else {
            if (
              Array.isArray(text) &&
              (curDa.length === 1 ||
                (curDa[1] === undefined && curDa[2] === undefined))
            ) {
              da.push(curDa[0]);
            } else {
              da.push([curDa[0], curDa[1], curDa[2]]);
            }
          }
        }
        return da;
      }

      function processTextByFunction(text, processingFunction) {
        var result;
        if (typeof text === "string") {
          result = processingFunction(text)[0];
        } else if (Array.isArray(text)) {
          //we don't want to destroy original text array, so cloning it
          var sa = text.concat();
          var da = [];
          var len = sa.length;
          var curDa;
          var tmpResult;
          //we do array.join('text that must not be PDFescaped")
          //thus, pdfEscape each component separately
          while (len--) {
            curDa = sa.shift();
            if (typeof curDa === "string") {
              da.push(processingFunction(curDa)[0]);
            } else if (Array.isArray(curDa) && typeof curDa[0] === "string") {
              tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]);
              da.push([tmpResult[0], tmpResult[1], tmpResult[2]]);
            }
          }
          result = da;
        }
        return result;
      }

      //Check if text is of type String
      var textIsOfTypeString = false;
      var tmpTextIsOfTypeString = true;

      if (typeof text === "string") {
        textIsOfTypeString = true;
      } else if (Array.isArray(text)) {
        //we don't want to destroy original text array, so cloning it
        var sa = text.concat();
        da = [];
        var len = sa.length;
        var curDa;
        //we do array.join('text that must not be PDFescaped")
        //thus, pdfEscape each component separately
        while (len--) {
          curDa = sa.shift();
          if (
            typeof curDa !== "string" ||
            (Array.isArray(curDa) && typeof curDa[0] !== "string")
          ) {
            tmpTextIsOfTypeString = false;
          }
        }
        textIsOfTypeString = tmpTextIsOfTypeString;
      }
      if (textIsOfTypeString === false) {
        throw new Error(
          'Type of text must be string or Array. "' +
            text +
            '" is not recognized.'
        );
      }

      //If there are any newlines in text, we assume
      //the user wanted to print multiple lines, so break the
      //text up into an array. If the text is already an array,
      //we assume the user knows what they are doing.
      //Convert text into an array anyway to simplify
      //later code.

      if (typeof text === "string") {
        if (text.match(/[\r?\n]/)) {
          text = text.split(/\r\n|\r|\n/g);
        } else {
          text = [text];
        }
      }

      //baseline
      var height = activeFontSize / scope.internal.scaleFactor;
      var descent = height * (lineHeightFactor - 1);
      switch (options.baseline) {
        case "bottom":
          y -= descent;
          break;
        case "top":
          y += height - descent;
          break;
        case "hanging":
          y += height - 2 * descent;
          break;
        case "middle":
          y += height / 2 - descent;
          break;
        case "ideographic":
        case "alphabetic":
        default:
          // do nothing, everything is fine
          break;
      }

      //multiline
      maxWidth = options.maxWidth || 0;

      if (maxWidth > 0) {
        if (typeof text === "string") {
          text = scope.splitTextToSize(text, maxWidth);
        } else if (Object.prototype.toString.call(text) === "[object Array]") {
          text = scope.splitTextToSize(text.join(" "), maxWidth);
        }
      }

      //creating Payload-Object to make text byRef
      payload = {
        text: text,
        x: x,
        y: y,
        options: options,
        mutex: {
          pdfEscape: pdfEscape,
          activeFontKey: activeFontKey,
          fonts: fonts,
          activeFontSize: activeFontSize
        }
      };
      events.publish("preProcessText", payload);

      text = payload.text;
      options = payload.options;

      //angle
      angle = options.angle;

      if (
        transformationMatrix instanceof Matrix === false &&
        angle &&
        typeof angle === "number"
      ) {
        angle *= Math.PI / 180;

        if (options.rotationDirection === 0) {
          angle = -angle;
        }

        if (apiMode === ApiMode.ADVANCED) {
          angle = -angle;
        }

        var c = Math.cos(angle);
        var s = Math.sin(angle);
        transformationMatrix = new Matrix(c, s, -s, c, 0, 0);
      } else if (angle && angle instanceof Matrix) {
        transformationMatrix = angle;
      }

      if (apiMode === ApiMode.ADVANCED && !transformationMatrix) {
        transformationMatrix = identityMatrix;
      }

      //charSpace

      charSpace = options.charSpace || activeCharSpace;

      if (typeof charSpace !== "undefined") {
        xtra += hpf(scale(charSpace)) + " Tc\n";
        this.setCharSpace(this.getCharSpace() || 0);
      }

      //lang

      var lang = options.lang;

      if (lang) {
        //    xtra += "/Lang (" + lang +")\n";
      }

      //renderingMode
      var renderingMode = -1;
      var parmRenderingMode =
        typeof options.renderingMode !== "undefined"
          ? options.renderingMode
          : options.stroke;
      var pageContext = scope.internal.getCurrentPageInfo().pageContext;

      switch (parmRenderingMode) {
        case 0:
        case false:
        case "fill":
          renderingMode = 0;
          break;
        case 1:
        case true:
        case "stroke":
          renderingMode = 1;
          break;
        case 2:
        case "fillThenStroke":
          renderingMode = 2;
          break;
        case 3:
        case "invisible":
          renderingMode = 3;
          break;
        case 4:
        case "fillAndAddForClipping":
          renderingMode = 4;
          break;
        case 5:
        case "strokeAndAddPathForClipping":
          renderingMode = 5;
          break;
        case 6:
        case "fillThenStrokeAndAddToPathForClipping":
          renderingMode = 6;
          break;
        case 7:
        case "addToPathForClipping":
          renderingMode = 7;
          break;
      }

      var usedRenderingMode =
        typeof pageContext.usedRenderingMode !== "undefined"
          ? pageContext.usedRenderingMode
          : -1;

      //if the coder wrote it explicitly to use a specific
      //renderingMode, then use it
      if (renderingMode !== -1) {
        xtra += renderingMode + " Tr\n";
        //otherwise check if we used the rendering Mode already
        //if so then set the rendering Mode...
      } else if (usedRenderingMode !== -1) {
        xtra += "0 Tr\n";
      }

      if (renderingMode !== -1) {
        pageContext.usedRenderingMode = renderingMode;
      }

      //align
      align = options.align || "left";
      var leading = activeFontSize * lineHeight;
      var pageWidth = scope.internal.pageSize.getWidth();
      var activeFont = fonts[activeFontKey];
      charSpace = options.charSpace || activeCharSpace;
      maxWidth = options.maxWidth || 0;

      var lineWidths;
      flags = {};
      var wordSpacingPerLine = [];

      if (Object.prototype.toString.call(text) === "[object Array]") {
        da = transformTextToSpecialArray(text);
        var newY;
        if (align !== "left") {
          lineWidths = da.map(function(v) {
            return (
              (scope.getStringUnitWidth(v, {
                font: activeFont,
                charSpace: charSpace,
                fontSize: activeFontSize,
                doKerning: false
              }) *
                activeFontSize) /
              scaleFactor
            );
          });
        }
        //The first line uses the "main" Td setting,
        //and the subsequent lines are offset by the
        //previous line's x coordinate.
        var prevWidth = 0;
        var newX;
        if (align === "right") {
          //The passed in x coordinate defines the
          //rightmost point of the text.
          x -= lineWidths[0];
          text = [];
          len = da.length;
          for (var i = 0; i < len; i++) {
            if (i === 0) {
              newX = getHorizontalCoordinate(x);
              newY = getVerticalCoordinate(y);
            } else {
              newX = scale(prevWidth - lineWidths[i]);
              newY = -leading;
            }
            text.push([da[i], newX, newY]);
            prevWidth = lineWidths[i];
          }
        } else if (align === "center") {
          //The passed in x coordinate defines
          //the center point.
          x -= lineWidths[0] / 2;
          text = [];
          len = da.length;
          for (var j = 0; j < len; j++) {
            if (j === 0) {
              newX = getHorizontalCoordinate(x);
              newY = getVerticalCoordinate(y);
            } else {
              newX = scale((prevWidth - lineWidths[j]) / 2);
              newY = -leading;
            }
            text.push([da[j], newX, newY]);
            prevWidth = lineWidths[j];
          }
        } else if (align === "left") {
          text = [];
          len = da.length;
          for (var h = 0; h < len; h++) {
            text.push(da[h]);
          }
        } else if (align === "justify") {
          text = [];
          len = da.length;
          maxWidth = maxWidth !== 0 ? maxWidth : pageWidth;

          for (var l = 0; l < len; l++) {
            newY = l === 0 ? getVerticalCoordinate(y) : -leading;
            newX = l === 0 ? getHorizontalCoordinate(x) : 0;
            if (l < len - 1) {
              wordSpacingPerLine.push(
                hpf(
                  scale(
                    (maxWidth - lineWidths[l]) / (da[l].split(" ").length - 1)
                  )
                )
              );
            }
            text.push([da[l], newX, newY]);
          }
        } else {
          throw new Error(
            'Unrecognized alignment option, use "left", "center", "right" or "justify".'
          );
        }
      }

      //R2L
      var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L;
      if (doReversing === true) {
        text = processTextByFunction(text, function(text, posX, posY) {
          return [
            text
              .split("")
              .reverse()
              .join(""),
            posX,
            posY
          ];
        });
      }

      //creating Payload-Object to make text byRef
      payload = {
        text: text,
        x: x,
        y: y,
        options: options,
        mutex: {
          pdfEscape: pdfEscape,
          activeFontKey: activeFontKey,
          fonts: fonts,
          activeFontSize: activeFontSize
        }
      };
      events.publish("postProcessText", payload);

      text = payload.text;
      isHex = payload.mutex.isHex || false;

      //Escaping
      var activeFontEncoding = fonts[activeFontKey].encoding;

      if (
        activeFontEncoding === "WinAnsiEncoding" ||
        activeFontEncoding === "StandardEncoding"
      ) {
        text = processTextByFunction(text, function(text, posX, posY) {
          return [ESC(text), posX, posY];
        });
      }

      da = transformTextToSpecialArray(text);

      text = [];
      var STRING = 0;
      var ARRAY = 1;
      var variant = Array.isArray(da[0]) ? ARRAY : STRING;
      var posX;
      var posY;
      var content;
      var wordSpacing = "";

      var generatePosition = function(
        parmPosX,
        parmPosY,
        parmTransformationMatrix
      ) {
        var position = "";
        if (parmTransformationMatrix instanceof Matrix) {
          // It is kind of more intuitive to apply a plain rotation around the text anchor set by x and y
          // but when the user supplies an arbitrary transformation matrix, the x and y offsets should be applied
          // in the coordinate system established by this matrix
          if (typeof options.angle === "number") {
            parmTransformationMatrix = matrixMult(
              parmTransformationMatrix,
              new Matrix(1, 0, 0, 1, parmPosX, parmPosY)
            );
          } else {
            parmTransformationMatrix = matrixMult(
              new Matrix(1, 0, 0, 1, parmPosX, parmPosY),
              parmTransformationMatrix
            );
          }

          if (apiMode === ApiMode.ADVANCED) {
            parmTransformationMatrix = matrixMult(
              new Matrix(1, 0, 0, -1, 0, 0),
              parmTransformationMatrix
            );
          }

          position = parmTransformationMatrix.join(" ") + " Tm\n";
        } else {
          position = hpf(parmPosX) + " " + hpf(parmPosY) + " Td\n";
        }
        return position;
      };

      for (var lineIndex = 0; lineIndex < da.length; lineIndex++) {
        wordSpacing = "";

        switch (variant) {
          case ARRAY:
            content =
              (isHex ? "<" : "(") + da[lineIndex][0] + (isHex ? ">" : ")");
            posX = parseFloat(da[lineIndex][1]);
            posY = parseFloat(da[lineIndex][2]);
            break;
          case STRING:
            content = (isHex ? "<" : "(") + da[lineIndex] + (isHex ? ">" : ")");
            posX = getHorizontalCoordinate(x);
            posY = getVerticalCoordinate(y);
            break;
        }

        if (
          typeof wordSpacingPerLine !== "undefined" &&
          typeof wordSpacingPerLine[lineIndex] !== "undefined"
        ) {
          wordSpacing = wordSpacingPerLine[lineIndex] + " Tw\n";
        }

        if (lineIndex === 0) {
          text.push(
            wordSpacing +
              generatePosition(posX, posY, transformationMatrix) +
              content
          );
        } else if (variant === STRING) {
          text.push(wordSpacing + content);
        } else if (variant === ARRAY) {
          text.push(
            wordSpacing +
              generatePosition(posX, posY, transformationMatrix) +
              content
          );
        }
      }

      text = variant === STRING ? text.join(" Tj\nT* ") : text.join(" Tj\n");
      text += " Tj\n";

      var result = "BT\n/";
      result += activeFontKey + " " + activeFontSize + " Tf\n"; // font face, style, size
      result += hpf(activeFontSize * lineHeight) + " TL\n"; // line spacing
      result += textColor + "\n";
      result += xtra;
      result += text;
      result += "ET";

      out(result);
      usedFonts[activeFontKey] = true;
      return scope;
    };

    /**
     * Letter spacing method to print text with gaps
     *
     * @function
     * @instance
     * @param {String|Array} text String to be added to the page.
     * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} spacing Spacing (in units declared at inception)
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name lstext
     * @deprecated We'll be removing this function. It doesn't take character width into account.
     */
    API.__private__.lstext = API.lstext = function(text, x, y, charSpace) {
      return this.text(text, x, y, {
        charSpace: charSpace
      });
    };

    // PDF supports these path painting and clip path operators:
    //
    // S - stroke
    // s - close/stroke
    // f (F) - fill non-zero
    // f* - fill evenodd
    // B - fill stroke nonzero
    // B* - fill stroke evenodd
    // b - close fill stroke nonzero
    // b* - close fill stroke evenodd
    // n - nothing (consume path)
    // W - clip nonzero
    // W* - clip evenodd
    //
    // In order to keep the API small, we omit the close-and-fill/stroke operators and provide a separate close()
    // method.
    /**
     *
     * @name clip
     * @function
     * @instance
     * @param {string} rule Only possible value is 'evenodd'
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @description All .clip() after calling drawing ops with a style argument of null.
     */
    var clip = (API.__private__.clip = API.clip = function(rule) {
      // Call .clip() after calling drawing ops with a style argument of null
      // W is the PDF clipping op
      if ("evenodd" === rule) {
        out("W*");
      } else {
        out("W");
      }
      return this;
    });

    /**
     * @name clipEvenOdd
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @description Modify the current clip path by intersecting it with the current path using the even-odd rule. Note
     * that this will NOT consume the current path. In order to only use this path for clipping call
     * {@link API.discardPath} afterwards.
     */
    API.clipEvenOdd = function() {
      return clip("evenodd");
    };

    /**
     * This fixes the previous function clip(). Perhaps the 'stroke path' hack was due to the missing 'n' instruction?
     * We introduce the fixed version so as to not break API.
     * @param fillRule
     * @deprecated
     * @ignore
     */
    API.__private__.clip_fixed = API.clip_fixed = function(rule) {
      return API.clip(rule);
    };

    /**
     * Consumes the current path without any effect. Mainly used in combination with {@link clip} or
     * {@link clipEvenOdd}. The PDF "n" operator.
     * @name discardPath
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    API.__private__.discardPath = API.discardPath = function() {
      out("n");
      return this;
    };

    var isValidStyle = (API.__private__.isValidStyle = function(style) {
      var validStyleVariants = [
        undefined,
        null,
        "S",
        "D",
        "F",
        "DF",
        "FD",
        "f",
        "f*",
        "B",
        "B*",
        "n"
      ];
      var result = false;
      if (validStyleVariants.indexOf(style) !== -1) {
        result = true;
      }
      return result;
    });

    API.__private__.setDefaultPathOperation = API.setDefaultPathOperation = function(
      operator
    ) {
      if (isValidStyle(operator)) {
        defaultPathOperation = operator;
      }
      return this;
    };

    var getStyle = (API.__private__.getStyle = API.getStyle = function(style) {
      // see path-painting operators in PDF spec
      var op = defaultPathOperation; // stroke

      switch (style) {
        case "D":
        case "S":
          op = "S"; // stroke
          break;
        case "F":
          op = "f"; // fill
          break;
        case "FD":
        case "DF":
          op = "B";
          break;
        case "f":
        case "f*":
        case "B":
        case "B*":
          /*
               Allow direct use of these PDF path-painting operators:
               - f    fill using nonzero winding number rule
               - f*    fill using even-odd rule
               - B    fill then stroke with fill using non-zero winding number rule
               - B*    fill then stroke with fill using even-odd rule
               */
          op = style;
          break;
      }
      return op;
    });

    /**
     * Close the current path. The PDF "h" operator.
     * @name close
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    var close = (API.close = function() {
      out("h");
      return this;
    });

    /**
     * Stroke the path. The PDF "S" operator.
     * @name stroke
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    API.stroke = function() {
      out("S");
      return this;
    };

    /**
     * Fill the current path using the nonzero winding number rule. If a pattern is provided, the path will be filled
     * with this pattern, otherwise with the current fill color. Equivalent to the PDF "f" operator.
     * @name fill
     * @function
     * @instance
     * @param {PatternData=} pattern If provided the path will be filled with this pattern
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    API.fill = function(pattern) {
      fillWithOptionalPattern("f", pattern);
      return this;
    };

    /**
     * Fill the current path using the even-odd rule. The PDF f* operator.
     * @see API.fill
     * @name fillEvenOdd
     * @function
     * @instance
     * @param {PatternData=} pattern If provided the path will be filled with this pattern
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    API.fillEvenOdd = function(pattern) {
      fillWithOptionalPattern("f*", pattern);
      return this;
    };

    /**
     * Fill using the nonzero winding number rule and then stroke the current Path. The PDF "B" operator.
     * @see API.fill
     * @name fillStroke
     * @function
     * @instance
     * @param {PatternData=} pattern If provided the path will be stroked with this pattern
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    API.fillStroke = function(pattern) {
      fillWithOptionalPattern("B", pattern);
      return this;
    };

    /**
     * Fill using the even-odd rule and then stroke the current Path. The PDF "B" operator.
     * @see API.fill
     * @name fillStrokeEvenOdd
     * @function
     * @instance
     * @param {PatternData=} pattern If provided the path will be fill-stroked with this pattern
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    API.fillStrokeEvenOdd = function(pattern) {
      fillWithOptionalPattern("B*", pattern);
      return this;
    };

    var fillWithOptionalPattern = function(style, pattern) {
      if (typeof pattern === "object") {
        fillWithPattern(pattern, style);
      } else {
        out(style);
      }
    };

    var putStyle = function(style, patternKey, patternData) {
      if (
        style === null ||
        (apiMode === ApiMode.ADVANCED && style === undefined)
      ) {
        return;
      }

      style = getStyle(style);

      // stroking / filling / both the path
      if (!patternKey) {
        out(style);
        return;
      }

      if (!patternData) {
        patternData = { matrix: identityMatrix };
      }

      if (patternData instanceof Matrix) {
        patternData = { matrix: patternData };
      }

      patternData.key = patternKey;

      patternData || (patternData = identityMatrix);

      fillWithPattern(patternData, style);
    };

    var fillWithPattern = function(patternData, style) {
      var patternId = patternMap[patternData.key];
      var pattern = patterns[patternId];

      if (pattern instanceof API.ShadingPattern) {
        out("q");

        out(clipRuleFromStyle(style));

        if (pattern.gState) {
          API.setGState(pattern.gState);
        }
        out(patternData.matrix.toString() + " cm");
        out("/" + patternId + " sh");
        out("Q");
      } else if (pattern instanceof API.TilingPattern) {
        // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation,
        // so we must flip them
        var matrix = new Matrix(1, 0, 0, -1, 0, getPageHeight());

        if (patternData.matrix) {
          matrix = matrix.multiply(patternData.matrix || identityMatrix);
          // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances
          // for each use
          patternId = pattern.createClone(
            patternData.key,
            patternData.boundingBox,
            patternData.xStep,
            patternData.yStep,
            matrix
          ).id;
        }

        out("q");
        out("/Pattern cs");
        out("/" + patternId + " scn");

        if (pattern.gState) {
          API.setGState(pattern.gState);
        }

        out(style);
        out("Q");
      }
    };

    var clipRuleFromStyle = function(style) {
      switch (style) {
        case "f":
        case "F":
          return "W n";
        case "f*":
          return "W* n";
        case "B":
          return "W S";
        case "B*":
          return "W* S";

        // these two are for compatibility reasons (in the past, calling any primitive method with a shading pattern
        // and "n"/"S" as style would still fill/fill and stroke the path)
        case "S":
          return "W S";
        case "n":
          return "W n";
      }
    };

    /**
     * Begin a new subpath by moving the current point to coordinates (x, y). The PDF "m" operator.
     * @param {number} x
     * @param {number} y
     * @name moveTo
     * @function
     * @instance
     * @memberof jsPDF#
     * @returns {jsPDF}
     */
    var moveTo = (API.moveTo = function(x, y) {
      out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " m");
      return this;
    });

    /**
     * Append a straight line segment from the current point to the point (x, y). The PDF "l" operator.
     * @param {number} x
     * @param {number} y
     * @memberof jsPDF#
     * @name lineTo
     * @function
     * @instance
     * @memberof jsPDF#
     * @returns {jsPDF}
     */
    var lineTo = (API.lineTo = function(x, y) {
      out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " l");
      return this;
    });

    /**
     * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point
     * (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. The new current point shall be (x3, x3).
     * @param {number} x1
     * @param {number} y1
     * @param {number} x2
     * @param {number} y2
     * @param {number} x3
     * @param {number} y3
     * @memberof jsPDF#
     * @name curveTo
     * @function
     * @instance
     * @memberof jsPDF#
     * @returns {jsPDF}
     */
    var curveTo = (API.curveTo = function(x1, y1, x2, y2, x3, y3) {
      out(
        [
          hpf(scale(x1)),
          hpf(transformScaleY(y1)),
          hpf(scale(x2)),
          hpf(transformScaleY(y2)),
          hpf(scale(x3)),
          hpf(transformScaleY(y3)),
          "c"
        ].join(" ")
      );
      return this;
    });

    /**
     * Draw a line on the current page.
     *
     * @name line
     * @function
     * @instance
     * @param {number} x1
     * @param {number} y1
     * @param {number} x2
     * @param {number} y2
     * @param {string} style A string specifying the painting style or null.  Valid styles include: 'S' [default] - stroke, 'F' - fill,  and 'DF' (or 'FD') -  fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. default: 'S'
     * @returns {jsPDF}
     * @memberof jsPDF#
     */
    API.__private__.line = API.line = function(x1, y1, x2, y2, style) {
      if (
        isNaN(x1) ||
        isNaN(y1) ||
        isNaN(x2) ||
        isNaN(y2) ||
        !isValidStyle(style)
      ) {
        throw new Error("Invalid arguments passed to jsPDF.line");
      }
      if (apiMode === ApiMode.COMPAT) {
        return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], style || "S");
      } else {
        return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1]).stroke();
      }
    };

    /**
     * @typedef {Object} PatternData
     * {Matrix|undefined} matrix
     * {Number|undefined} xStep
     * {Number|undefined} yStep
     * {Array.<Number>|undefined} boundingBox
     */

    /**
     * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates.
     * All data points in `lines` are relative to last line origin.
     * `x`, `y` become x1,y1 for first line / curve in the set.
     * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point.
     * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1.
     *
     * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, [1,1], 'F', false) // line, line, bezier curve, line
     * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves).
     * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction.
     * @param {string=} style A string specifying the painting style or null. Valid styles include:
     * 'S' [default] - stroke,
     * 'F' - fill,
     * and 'DF' (or 'FD') -  fill then stroke.
     * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
     * method calls. The last drawing method call used to define the shape should not have a null style argument.
     *
     * In "advanced" API mode this parameter is deprecated.
     * @param {Boolean=} closed If true, the path is closed with a straight line from the end of the last curve to the starting point.
     * @param {String=} patternKey The pattern key for the pattern that should be used to fill the path. Deprecated!
     * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
     * will modify the pattern on use. Deprecated!
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name lines
     */
    API.__private__.lines = API.lines = function(
      lines,
      x,
      y,
      scale,
      style,
      closed,
      patternKey,
      patternData
    ) {
      var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4, tmp;

      // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style)
      // in effort to make all calls have similar signature like
      //   function(content, coordinateX, coordinateY , miscellaneous)
      // this method had its args flipped.
      // code below allows backward compatibility with old arg order.
      if (typeof lines === "number") {
        tmp = y;
        y = x;
        x = lines;
        lines = tmp;
      }

      scale = scale || [1, 1];
      closed = closed || false;

      if (
        isNaN(x) ||
        isNaN(y) ||
        !Array.isArray(lines) ||
        !Array.isArray(scale) ||
        !isValidStyle(style) ||
        typeof closed !== "boolean"
      ) {
        throw new Error("Invalid arguments passed to jsPDF.lines");
      }

      // starting point
      moveTo(x, y);

      scalex = scale[0];
      scaley = scale[1];
      l = lines.length;
      //, x2, y2 // bezier only. In page default measurement "units", *after* scaling
      //, x3, y3 // bezier only. In page default measurement "units", *after* scaling
      // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling
      x4 = x; // last / ending point = starting point for first item.
      y4 = y; // last / ending point = starting point for first item.

      for (i = 0; i < l; i++) {
        leg = lines[i];
        if (leg.length === 2) {
          // simple line
          x4 = leg[0] * scalex + x4; // here last x4 was prior ending point
          y4 = leg[1] * scaley + y4; // here last y4 was prior ending point
          lineTo(x4, y4);
        } else {
          // bezier curve
          x2 = leg[0] * scalex + x4; // here last x4 is prior ending point
          y2 = leg[1] * scaley + y4; // here last y4 is prior ending point
          x3 = leg[2] * scalex + x4; // here last x4 is prior ending point
          y3 = leg[3] * scaley + y4; // here last y4 is prior ending point
          x4 = leg[4] * scalex + x4; // here last x4 was prior ending point
          y4 = leg[5] * scaley + y4; // here last y4 was prior ending point
          curveTo(x2, y2, x3, y3, x4, y4);
        }
      }

      if (closed) {
        close();
      }

      putStyle(style, patternKey, patternData);
      return this;
    };

    /**
     * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative.
     * @param {Array<Object>} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to)
     * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c"
     * six and "h" an empty array (or undefined).
     * @param {String=} style  The style. Deprecated!
     * @param {String=} patternKey The pattern key for the pattern that should be used to fill the path. Deprecated!
     * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
     * will modify the pattern on use. Deprecated!
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name path
     */
    API.path = function(lines, style, patternKey, patternData) {
      for (var i = 0; i < lines.length; i++) {
        var leg = lines[i];
        var coords = leg.c;
        switch (leg.op) {
          case "m":
            moveTo(coords[0], coords[1]);
            break;
          case "l":
            lineTo(coords[0], coords[1]);
            break;
          case "c":
            curveTo.apply(this, coords);
            break;
          case "h":
            close();
            break;
        }
      }

      putStyle(style, patternKey, patternData);
      return this;
    };

    /**
     * Adds a rectangle to PDF.
     *
     * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} w Width (in units declared at inception of PDF document)
     * @param {number} h Height (in units declared at inception of PDF document)
     * @param {string=} style A string specifying the painting style or null. Valid styles include:
     * 'S' [default] - stroke,
     * 'F' - fill,
     * and 'DF' (or 'FD') -  fill then stroke.
     * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
     * method calls. The last drawing method call used to define the shape should not have a null style argument.
     *
     * In "advanced" API mode this parameter is deprecated.
     * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
     * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
     * will modify the pattern on use. Deprecated!
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name rect
     */
    API.__private__.rect = API.rect = function(
      x,
      y,
      w,
      h,
      style,
      patternKey,
      patternData
    ) {
      if (
        isNaN(x) ||
        isNaN(y) ||
        isNaN(w) ||
        isNaN(h) ||
        !isValidStyle(style)
      ) {
        throw new Error("Invalid arguments passed to jsPDF.rect");
      }
      if (apiMode === ApiMode.COMPAT) {
        h = -h;
      }

      out(
        [
          hpf(scale(x)),
          hpf(transformScaleY(y)),
          hpf(scale(w)),
          hpf(scale(h)),
          "re"
        ].join(" ")
      );

      putStyle(style, patternKey, patternData);
      return this;
    };

    /**
     * Adds a triangle to PDF.
     *
     * @param {number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {string=} style A string specifying the painting style or null. Valid styles include:
     * 'S' [default] - stroke,
     * 'F' - fill,
     * and 'DF' (or 'FD') -  fill then stroke.
     * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
     * method calls. The last drawing method call used to define the shape should not have a null style argument.
     *
     * In "advanced" API mode this parameter is deprecated.
     * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
     * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
     * will modify the pattern on use. Deprecated!
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name triangle
     */
    API.__private__.triangle = API.triangle = function(
      x1,
      y1,
      x2,
      y2,
      x3,
      y3,
      style,
      patternKey,
      patternData
    ) {
      if (
        isNaN(x1) ||
        isNaN(y1) ||
        isNaN(x2) ||
        isNaN(y2) ||
        isNaN(x3) ||
        isNaN(y3) ||
        !isValidStyle(style)
      ) {
        throw new Error("Invalid arguments passed to jsPDF.triangle");
      }
      this.lines(
        [
          [x2 - x1, y2 - y1], // vector to point 2
          [x3 - x2, y3 - y2], // vector to point 3
          [x1 - x3, y1 - y3] // closing vector back to point 1
        ],
        x1,
        y1, // start of path
        [1, 1],
        style,
        true,
        patternKey,
        patternData
      );
      return this;
    };

    /**
     * Adds a rectangle with rounded corners to PDF.
     *
     * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} w Width (in units declared at inception of PDF document)
     * @param {number} h Height (in units declared at inception of PDF document)
     * @param {number} rx Radius along x axis (in units declared at inception of PDF document)
     * @param {number} ry Radius along y axis (in units declared at inception of PDF document)
     * @param {string=} style A string specifying the painting style or null. Valid styles include:
     * 'S' [default] - stroke,
     * 'F' - fill,
     * and 'DF' (or 'FD') -  fill then stroke.
     * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
     * method calls. The last drawing method call used to define the shape should not have a null style argument.
     *
     * In "advanced" API mode this parameter is deprecated.
     * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
     * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
     * will modify the pattern on use. Deprecated!
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name roundedRect
     */
    API.__private__.roundedRect = API.roundedRect = function(
      x,
      y,
      w,
      h,
      rx,
      ry,
      style,
      patternKey,
      patternData
    ) {
      if (
        isNaN(x) ||
        isNaN(y) ||
        isNaN(w) ||
        isNaN(h) ||
        isNaN(rx) ||
        isNaN(ry) ||
        !isValidStyle(style)
      ) {
        throw new Error("Invalid arguments passed to jsPDF.roundedRect");
      }
      var MyArc = (4 / 3) * (Math.SQRT2 - 1);

      rx = Math.min(rx, w * 0.5);
      ry = Math.min(ry, h * 0.5);

      this.lines(
        [
          [w - 2 * rx, 0],
          [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry],
          [0, h - 2 * ry],
          [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry],
          [-w + 2 * rx, 0],
          [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry],
          [0, -h + 2 * ry],
          [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry]
        ],
        x + rx,
        y, // start of path
        [1, 1],
        style,
        true,
        patternKey,
        patternData
      );
      return this;
    };

    /**
     * Adds an ellipse to PDF.
     *
     * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} rx Radius along x axis (in units declared at inception of PDF document)
     * @param {number} ry Radius along y axis (in units declared at inception of PDF document)
     * @param {string=} style A string specifying the painting style or null. Valid styles include:
     * 'S' [default] - stroke,
     * 'F' - fill,
     * and 'DF' (or 'FD') -  fill then stroke.
     * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
     * method calls. The last drawing method call used to define the shape should not have a null style argument.
     *
     * In "advanced" API mode this parameter is deprecated.
     * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
     * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
     * will modify the pattern on use. Deprecated!
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name ellipse
     */
    API.__private__.ellipse = API.ellipse = function(
      x,
      y,
      rx,
      ry,
      style,
      patternKey,
      patternData
    ) {
      if (
        isNaN(x) ||
        isNaN(y) ||
        isNaN(rx) ||
        isNaN(ry) ||
        !isValidStyle(style)
      ) {
        throw new Error("Invalid arguments passed to jsPDF.ellipse");
      }
      var lx = (4 / 3) * (Math.SQRT2 - 1) * rx,
        ly = (4 / 3) * (Math.SQRT2 - 1) * ry;

      moveTo(x + rx, y);
      curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry);
      curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y);
      curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry);
      curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y);

      putStyle(style, patternKey, patternData);
      return this;
    };

    /**
     * Adds an circle to PDF.
     *
     * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
     * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
     * @param {number} r Radius (in units declared at inception of PDF document)
     * @param {string=} style A string specifying the painting style or null. Valid styles include:
     * 'S' [default] - stroke,
     * 'F' - fill,
     * and 'DF' (or 'FD') -  fill then stroke.
     * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
     * method calls. The last drawing method call used to define the shape should not have a null style argument.
     *
     * In "advanced" API mode this parameter is deprecated.
     * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
     * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
     * will modify the pattern on use. Deprecated!
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name circle
     */
    API.__private__.circle = API.circle = function(
      x,
      y,
      r,
      style,
      patternKey,
      patternData
    ) {
      if (isNaN(x) || isNaN(y) || isNaN(r) || !isValidStyle(style)) {
        throw new Error("Invalid arguments passed to jsPDF.circle");
      }
      return this.ellipse(x, y, r, r, style, patternKey, patternData);
    };

    /**
     * Sets text font face, variant for upcoming text elements.
     * See output of jsPDF.getFontList() for possible font names, styles.
     *
     * @param {string} fontName Font name or family. Example: "times".
     * @param {string} fontStyle Font style or variant. Example: "italic".
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setFont
     */
    API.setFont = function(fontName, fontStyle) {
      activeFontKey = getFont(fontName, fontStyle, {
        disableWarning: false
      });
      return this;
    };

    /**
     * Gets text font face, variant for upcoming text elements.
     *
     * @function
     * @instance
     * @returns {Object}
     * @memberof jsPDF#
     * @name getFont
     */
    var getFontEntry = (API.__private__.getFont = API.getFont = function() {
      return fonts[getFont.apply(API, arguments)];
    });

    /**
     * Switches font style or variant for upcoming text elements,
     * while keeping the font face or family same.
     * See output of jsPDF.getFontList() for possible font names, styles.
     *
     * @param {string} style Font style or variant. Example: "italic".
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @deprecated
     * @name setFontStyle
     */
    API.setFontStyle = API.setFontType = function(style) {
      activeFontKey = getFont(undefined, style);
      // if font is not found, the above line blows up and we never go further
      return this;
    };

    /**
     * Returns an object - a tree of fontName to fontStyle relationships available to
     * active PDF document.
     *
     * @public
     * @function
     * @instance
     * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... }
     * @memberof jsPDF#
     * @name getFontList
     */
    API.__private__.getFontList = API.getFontList = function() {
      var list = {},
        fontName,
        fontStyle;

      for (fontName in fontmap) {
        if (fontmap.hasOwnProperty(fontName)) {
          list[fontName] = [];
          for (fontStyle in fontmap[fontName]) {
            if (fontmap[fontName].hasOwnProperty(fontStyle)) {
              list[fontName].push(fontStyle);
            }
          }
        }
      }
      return list;
    };

    /**
     * Add a custom font to the current instance.
     *
     * @property {string} postScriptName PDF specification full name for the font.
     * @property {string} id PDF-document-instance-specific label assinged to the font.
     * @property {string} fontStyle Style of the Font.
     * @property {Object} encoding Encoding_name-to-Font_metrics_object mapping.
     * @function
     * @instance
     * @memberof jsPDF#
     * @name addFont
     * @returns {string} fontId
     */
    API.addFont = function(postScriptName, fontName, fontStyle, encoding) {
      encoding = encoding || "Identity-H";
      return addFont.call(this, postScriptName, fontName, fontStyle, encoding);
    };

    var lineWidth = options.lineWidth || 0.200025; // 2mm
    /**
     * Sets line width for upcoming lines.
     *
     * @param {number} width Line width (in units declared at inception of PDF document).
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setLineWidth
     */
    var setLineWidth = (API.__private__.setLineWidth = API.setLineWidth = function(
      width
    ) {
      out(hpf(scale(width)) + " w");
      return this;
    });

    /**
     * Sets the dash pattern for upcoming lines.
     *
     * To reset the settings simply call the method without any parameters.
     * @param {Array<number>} dashArray An array containing 0-2 numbers. The first number sets the length of the
     * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered
     * to be as long as the dashes. An empty array means solid, unbroken lines.
     * @param {number} dashPhase The phase lines start with.
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setLineDashPattern
     */
    API.__private__.setLineDash = jsPDF.API.setLineDash = jsPDF.API.setLineDashPattern = function(
      dashArray,
      dashPhase
    ) {
      dashArray = dashArray || [];
      dashPhase = dashPhase || 0;

      if (isNaN(dashPhase) || !Array.isArray(dashArray)) {
        throw new Error("Invalid arguments passed to jsPDF.setLineDash");
      }

      dashArray = dashArray
        .map(function(x) {
          return hpf(scale(x));
        })
        .join(" ");
      dashPhase = hpf(scale(dashPhase));

      out("[" + dashArray + "] " + dashPhase + " d");
      return this;
    };

    var lineHeightFactor;

    var getLineHeight = (API.__private__.getLineHeight = API.getLineHeight = function() {
      return activeFontSize * lineHeightFactor;
    });

    API.__private__.getLineHeight = API.getLineHeight = function() {
      return activeFontSize * lineHeightFactor;
    };

    /**
     * Sets the LineHeightFactor of proportion.
     *
     * @param {number} value LineHeightFactor value. Default: 1.15.
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setLineHeightFactor
     */
    var setLineHeightFactor = (API.__private__.setLineHeightFactor = API.setLineHeightFactor = function(
      value
    ) {
      value = value || 1.15;
      if (typeof value === "number") {
        lineHeightFactor = value;
      }
      return this;
    });

    /**
     * Gets the LineHeightFactor, default: 1.15.
     *
     * @function
     * @instance
     * @returns {number} lineHeightFactor
     * @memberof jsPDF#
     * @name getLineHeightFactor
     */
    var getLineHeightFactor = (API.__private__.getLineHeightFactor = API.getLineHeightFactor = function() {
      return lineHeightFactor;
    });

    setLineHeightFactor(options.lineHeight);

    var getHorizontalCoordinate = (API.__private__.getHorizontalCoordinate = function(
      value
    ) {
      return scale(value);
    });

    var getVerticalCoordinate = (API.__private__.getVerticalCoordinate = function(
      value
    ) {
      if (apiMode === ApiMode.ADVANCED) {
        return value;
      } else {
        var pageHeight =
          pagesContext[currentPage].mediaBox.topRightY -
          pagesContext[currentPage].mediaBox.bottomLeftY;
        return pageHeight - scale(value);
      }
    });

    var getHorizontalCoordinateString = (API.__private__.getHorizontalCoordinateString = API.getHorizontalCoordinateString = function(
      value
    ) {
      return hpf(getHorizontalCoordinate(value));
    });

    var getVerticalCoordinateString = (API.__private__.getVerticalCoordinateString = API.getVerticalCoordinateString = function(
      value
    ) {
      return hpf(getVerticalCoordinate(value));
    });

    var strokeColor = options.strokeColor || "0 G";

    /**
     *  Gets the stroke color for upcoming elements.
     *
     * @function
     * @instance
     * @returns {string} colorAsHex
     * @memberof jsPDF#
     * @name getDrawColor
     */
    API.__private__.getStrokeColor = API.getDrawColor = function() {
      return decodeColorString(strokeColor);
    };

    /**
     * Sets the stroke color for upcoming elements.
     *
     * Depending on the number of arguments given, Gray, RGB, or CMYK
     * color space is implied.
     *
     * When only ch1 is given, "Gray" color space is implied and it
     * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
     * if values are communicated as String types, or in range from 0 (black)
     * to 255 (white) if communicated as Number type.
     * The RGB-like 0-255 range is provided for backward compatibility.
     *
     * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
     * value must be in the range from 0.00 (minimum intensity) to to 1.00
     * (max intensity) if values are communicated as String types, or
     * from 0 (min intensity) to to 255 (max intensity) if values are communicated
     * as Number types.
     * The RGB-like 0-255 range is provided for backward compatibility.
     *
     * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
     * value must be a in the range from 0.00 (0% concentration) to to
     * 1.00 (100% concentration)
     *
     * Because JavaScript treats fixed point numbers badly (rounds to
     * floating point nearest to binary representation) it is highly advised to
     * communicate the fractional numbers as String types, not JavaScript Number type.
     *
     * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'.
     * @param {Number} ch2 Color channel value.
     * @param {Number} ch3 Color channel value.
     * @param {Number} ch4 Color channel value.
     *
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setDrawColor
     */
    API.__private__.setStrokeColor = API.setDrawColor = function(
      ch1,
      ch2,
      ch3,
      ch4
    ) {
      var options = {
        ch1: ch1,
        ch2: ch2,
        ch3: ch3,
        ch4: ch4,
        pdfColorType: "draw",
        precision: 2
      };

      strokeColor = encodeColorString(options);
      out(strokeColor);
      return this;
    };

    var fillColor = options.fillColor || "0 g";

    /**
     * Gets the fill color for upcoming elements.
     *
     * @function
     * @instance
     * @returns {string} colorAsHex
     * @memberof jsPDF#
     * @name getFillColor
     */
    API.__private__.getFillColor = API.getFillColor = function() {
      return decodeColorString(fillColor);
    };

    /**
     * Sets the fill color for upcoming elements.
     *
     * Depending on the number of arguments given, Gray, RGB, or CMYK
     * color space is implied.
     *
     * When only ch1 is given, "Gray" color space is implied and it
     * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
     * if values are communicated as String types, or in range from 0 (black)
     * to 255 (white) if communicated as Number type.
     * The RGB-like 0-255 range is provided for backward compatibility.
     *
     * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
     * value must be in the range from 0.00 (minimum intensity) to to 1.00
     * (max intensity) if values are communicated as String types, or
     * from 0 (min intensity) to to 255 (max intensity) if values are communicated
     * as Number types.
     * The RGB-like 0-255 range is provided for backward compatibility.
     *
     * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
     * value must be a in the range from 0.00 (0% concentration) to to
     * 1.00 (100% concentration)
     *
     * Because JavaScript treats fixed point numbers badly (rounds to
     * floating point nearest to binary representation) it is highly advised to
     * communicate the fractional numbers as String types, not JavaScript Number type.
     *
     * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'.
     * @param {Number} ch2 Color channel value.
     * @param {Number} ch3 Color channel value.
     * @param {Number} ch4 Color channel value.
     *
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setFillColor
     */
    API.__private__.setFillColor = API.setFillColor = function(
      ch1,
      ch2,
      ch3,
      ch4
    ) {
      var options = {
        ch1: ch1,
        ch2: ch2,
        ch3: ch3,
        ch4: ch4,
        pdfColorType: "fill",
        precision: 2
      };

      fillColor = encodeColorString(options);
      out(fillColor);
      return this;
    };

    var textColor = options.textColor || "0 g";
    /**
     * Gets the text color for upcoming elements.
     *
     * @function
     * @instance
     * @returns {string} colorAsHex
     * @memberof jsPDF#
     * @name getTextColor
     */
    var getTextColor = (API.__private__.getTextColor = API.getTextColor = function() {
      return decodeColorString(textColor);
    });
    /**
     * Sets the text color for upcoming elements.
     *
     * Depending on the number of arguments given, Gray, RGB, or CMYK
     * color space is implied.
     *
     * When only ch1 is given, "Gray" color space is implied and it
     * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
     * if values are communicated as String types, or in range from 0 (black)
     * to 255 (white) if communicated as Number type.
     * The RGB-like 0-255 range is provided for backward compatibility.
     *
     * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
     * value must be in the range from 0.00 (minimum intensity) to to 1.00
     * (max intensity) if values are communicated as String types, or
     * from 0 (min intensity) to to 255 (max intensity) if values are communicated
     * as Number types.
     * The RGB-like 0-255 range is provided for backward compatibility.
     *
     * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
     * value must be a in the range from 0.00 (0% concentration) to to
     * 1.00 (100% concentration)
     *
     * Because JavaScript treats fixed point numbers badly (rounds to
     * floating point nearest to binary representation) it is highly advised to
     * communicate the fractional numbers as String types, not JavaScript Number type.
     *
     * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'.
     * @param {Number} ch2 Color channel value.
     * @param {Number} ch3 Color channel value.
     * @param {Number} ch4 Color channel value.
     *
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setTextColor
     */
    API.__private__.setTextColor = API.setTextColor = function(
      ch1,
      ch2,
      ch3,
      ch4
    ) {
      var options = {
        ch1: ch1,
        ch2: ch2,
        ch3: ch3,
        ch4: ch4,
        pdfColorType: "text",
        precision: 3
      };
      textColor = encodeColorString(options);

      return this;
    };

    var activeCharSpace = options.charSpace;

    /**
     * Get global value of CharSpace.
     *
     * @function
     * @instance
     * @returns {number} charSpace
     * @memberof jsPDF#
     * @name getCharSpace
     */
    var getCharSpace = (API.__private__.getCharSpace = API.getCharSpace = function() {
      return parseFloat(activeCharSpace || 0);
    });

    /**
     * Set global value of CharSpace.
     *
     * @param {number} charSpace
     * @function
     * @instance
     * @returns {jsPDF} jsPDF-instance
     * @memberof jsPDF#
     * @name setCharSpace
     */
    API.__private__.setCharSpace = API.setCharSpace = function(charSpace) {
      if (isNaN(charSpace)) {
        throw new Error("Invalid argument passed to jsPDF.setCharSpace");
      }
      activeCharSpace = charSpace;
      return this;
    };

    var lineCapID = 0;
    /**
     * Is an Object providing a mapping from human-readable to
     * integer flag values designating the varieties of line cap
     * and join styles.
     *
     * @memberof jsPDF#
     * @name CapJoinStyles
     */
    API.CapJoinStyles = {
      0: 0,
      butt: 0,
      but: 0,
      miter: 0,
      1: 1,
      round: 1,
      rounded: 1,
      circle: 1,
      2: 2,
      projecting: 2,
      project: 2,
      square: 2,
      bevel: 2
    };

    /**
     * Sets the line cap styles.
     * See {jsPDF.CapJoinStyles} for variants.
     *
     * @param {String|Number} style A string or number identifying the type of line cap.
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setLineCap
     */
    API.__private__.setLineCap = API.setLineCap = function(style) {
      var id = API.CapJoinStyles[style];
      if (id === undefined) {
        throw new Error(
          "Line cap style of '" +
            style +
            "' is not recognized. See or extend .CapJoinStyles property for valid styles"
        );
      }
      lineCapID = id;
      out(id + " J");

      return this;
    };

    var lineJoinID = 0;
    /**
     * Sets the line join styles.
     * See {jsPDF.CapJoinStyles} for variants.
     *
     * @param {String|Number} style A string or number identifying the type of line join.
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setLineJoin
     */
    API.__private__.setLineJoin = API.setLineJoin = function(style) {
      var id = API.CapJoinStyles[style];
      if (id === undefined) {
        throw new Error(
          "Line join style of '" +
            style +
            "' is not recognized. See or extend .CapJoinStyles property for valid styles"
        );
      }
      lineJoinID = id;
      out(id + " j");

      return this;
    };

    var miterLimit;
    /**
     * Sets the miterLimit property, which effects the maximum miter length.
     *
     * @param {number} length The length of the miter
     * @function
     * @instance
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setLineMiterLimit
     */
    API.__private__.setLineMiterLimit = API.__private__.setMiterLimit = API.setLineMiterLimit = API.setMiterLimit = function(
      length
    ) {
      length = length || 0;
      if (isNaN(length)) {
        throw new Error("Invalid argument passed to jsPDF.setLineMiterLimit");
      }
      out(hpf(scale(length)) + " M");

      return this;
    };

    /**
     * An object representing a pdf graphics state.
     * @class GState
     */

    /**
     *
     * @param parameters A parameter object that contains all properties this graphics state wants to set.
     * Supported are: opacity, stroke-opacity
     * @constructor
     */
    API.GState = function GState(parameters) {
      if (!(this instanceof GState)) {
        return new GState(parameters);
      }

      /**
       * @name GState#opacity
       * @type {any}
       */
      /**
       * @name GState#stroke-opacity
       * @type {any}
       */
      var supported = "opacity,stroke-opacity".split(",");
      for (var p in parameters) {
        if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) {
          this[p] = parameters[p];
        }
      }
      /**
       * @name GState#id
       * @type {string}
       */
      this.id = ""; // set by addGState()
      /**
       * @name GState#objectNumber
       * @type {number}
       */
      this.objectNumber = -1; // will be set by putGState()
    };

    API.GState.prototype.equals = function equals(other) {
      var ignore = "id,objectNumber,equals";
      var p;
      if (!other || typeof other !== typeof this) return false;
      var count = 0;
      for (p in this) {
        if (ignore.indexOf(p) >= 0) continue;
        if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false;
        if (this[p] !== other[p]) return false;
        count++;
      }
      for (p in other) {
        if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--;
      }
      return count === 0;
    };

    /**
     * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}.
     * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState
     * it will be added before use.
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setGState
     */
    API.setGState = function(gState) {
      if (typeof gState === "string") {
        gState = gStates[gStatesMap[gState]];
      } else {
        gState = addGState(null, gState);
      }

      if (!gState.equals(activeGState)) {
        out("/" + gState.id + " gs");
        activeGState = gState;
      }
    };

    /**
     * Adds a new Graphics State. Duplicates are automatically eliminated.
     * @param {String} key Might also be null, if no later reference to this gState is needed
     * @param {Object} gState The gState object
     */
    var addGState = function(key, gState) {
      // only add it if it is not already present (the keys provided by the user must be unique!)
      if (key && gStatesMap[key]) return;
      var duplicate = false;
      for (var s in gStates) {
        if (gStates.hasOwnProperty(s)) {
          if (gStates[s].equals(gState)) {
            duplicate = true;
            break;
          }
        }
      }

      if (duplicate) {
        gState = gStates[s];
      } else {
        var gStateKey = "GS" + (Object.keys(gStates).length + 1).toString(10);
        gStates[gStateKey] = gState;
        gState.id = gStateKey;
      }

      // several user keys may point to the same GState object
      key && (gStatesMap[key] = gState.id);

      events.publish("addGState", gState);

      return gState;
    };

    /**
     * Adds a new {@link GState} for later use. See {@link setGState}.
     * @param {String} key
     * @param {GState} gState
     * @function
     * @instance
     * @returns {jsPDF}
     *
     * @memberof jsPDF#
     * @name addGState
     */
    API.addGState = function(key, gState) {
      addGState(key, gState);
      return this;
    };

    /**
     * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState}
     * later. Here, the general pdf graphics state is meant, also including the current transformation matrix,
     * fill and stroke colors etc.
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name saveGraphicsState
     */
    API.saveGraphicsState = function() {
      out("q");
      // as we cannot set font key and size independently we must keep track of both
      fontStateStack.push({
        key: activeFontKey,
        size: activeFontSize,
        color: textColor
      });
      return this;
    };

    /**
     * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack").
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name restoreGraphicsState
     */
    API.restoreGraphicsState = function() {
      out("Q");

      // restore previous font state
      var fontState = fontStateStack.pop();
      activeFontKey = fontState.key;
      activeFontSize = fontState.size;
      textColor = fontState.color;

      activeGState = null;

      return this;
    };

    /**
     * Appends this matrix to the left of all previously applied matrices.
     *
     * @param {Matrix} matrix
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name setCurrentTransformationMatrix
     */
    API.setCurrentTransformationMatrix = function(matrix) {
      out(matrix.toString() + " cm");
      return this;
    };

    /**
     * Inserts a debug comment into the generated pdf.
     * @function
     * @instance
     * @param {String} text
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name comment
     */
    API.comment = function(text) {
      out("#" + text);
      return this;
    };

    /**
     * Point
     */
    var Point = function(x, y) {
      var _x = x || 0;
      Object.defineProperty(this, "x", {
        enumerable: true,
        get: function() {
          return _x;
        },
        set: function(value) {
          if (!isNaN(value)) {
            _x = parseFloat(value);
          }
        }
      });

      var _y = y || 0;
      Object.defineProperty(this, "y", {
        enumerable: true,
        get: function() {
          return _y;
        },
        set: function(value) {
          if (!isNaN(value)) {
            _y = parseFloat(value);
          }
        }
      });

      var _type = "pt";
      Object.defineProperty(this, "type", {
        enumerable: true,
        get: function() {
          return _type;
        },
        set: function(value) {
          _type = value.toString();
        }
      });
      return this;
    };

    /**
     * Rectangle
     */
    var Rectangle = function(x, y, w, h) {
      Point.call(this, x, y);
      this.type = "rect";

      var _w = w || 0;
      Object.defineProperty(this, "w", {
        enumerable: true,
        get: function() {
          return _w;
        },
        set: function(value) {
          if (!isNaN(value)) {
            _w = parseFloat(value);
          }
        }
      });

      var _h = h || 0;
      Object.defineProperty(this, "h", {
        enumerable: true,
        get: function() {
          return _h;
        },
        set: function(value) {
          if (!isNaN(value)) {
            _h = parseFloat(value);
          }
        }
      });

      return this;
    };

    /**
     * FormObject/RenderTarget
     */

    var RenderTarget = function() {
      this.page = page;
      this.currentPage = currentPage;
      this.pages = pages.slice(0);
      this.pagesContext = pagesContext.slice(0);
      this.x = pageX;
      this.y = pageY;
      this.matrix = pageMatrix;
      this.width = getPageWidth(currentPage);
      this.height = getPageHeight(currentPage);
      this.outputDestination = outputDestination;

      this.id = ""; // set by endFormObject()
      this.objectNumber = -1; // will be set by putXObject()
    };

    RenderTarget.prototype.restore = function() {
      page = this.page;
      currentPage = this.currentPage;
      pagesContext = this.pagesContext;
      pages = this.pages;
      pageX = this.x;
      pageY = this.y;
      pageMatrix = this.matrix;
      setPageWidth(currentPage, this.width);
      setPageHeight(currentPage, this.height);
      outputDestination = this.outputDestination;
    };

    var beginNewRenderTarget = function(x, y, width, height, matrix) {
      // save current state
      renderTargetStack.push(new RenderTarget());

      // clear pages
      page = currentPage = 0;
      pages = [];
      pageX = x;
      pageY = y;

      pageMatrix = matrix;

      beginPage([width, height]);
    };

    var endFormObject = function(key) {
      // only add it if it is not already present (the keys provided by the user must be unique!)
      if (renderTargetMap[key]) return;

      // save the created xObject
      var newXObject = new RenderTarget();

      var xObjectId =
        "Xo" + (Object.keys(renderTargets).length + 1).toString(10);
      newXObject.id = xObjectId;

      renderTargetMap[key] = xObjectId;
      renderTargets[xObjectId] = newXObject;

      events.publish("addFormObject", newXObject);

      // restore state from stack
      renderTargetStack.pop().restore();
    };

    /**
     * Starts a new pdf form object, which means that all consequent draw calls target a new independent object
     * until {@link endFormObject} is called. The created object can be referenced and drawn later using
     * {@link doFormObject}. Nested form objects are possible.
     * x, y, width, height set the bounding box that is used to clip the content.
     *
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to
     * the parent's.
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name beginFormObject
     */
    API.beginFormObject = function(x, y, width, height, matrix) {
      // The user can set the output target to a new form object. Nested form objects are possible.
      // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as
      // the PDF-Spec states:
      // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which
      // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions,
      // form XObjects may be independent of the content streams in which they appear, and this is strongly
      // recommended although not required"
      beginNewRenderTarget(x, y, width, height, matrix);
      return this;
    };

    /**
     * Completes and saves the form object.
     * @param {String} key The key by which this form object can be referenced.
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name endFormObject
     */
    API.endFormObject = function(key) {
      endFormObject(key);
      return this;
    };

    /**
     * Draws the specified form object by referencing to the respective pdf XObject created with
     * {@link API.beginFormObject} and {@link endFormObject}.
     * The location is determined by matrix.
     *
     * @param {String} key The key to the form object.
     * @param {Matrix} matrix The matrix applied before drawing the form object.
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name doFormObject
     */
    API.doFormObject = function(key, matrix) {
      var xObject = renderTargets[renderTargetMap[key]];
      out("q");
      out(matrix.toString() + " cm");
      out("/" + xObject.id + " Do");
      out("Q");
      return this;
    };

    /**
     * Returns the form object specified by key.
     * @param key {String}
     * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}}
     * @function
     * @returns {jsPDF}
     * @memberof jsPDF#
     * @name getFormObject
     */
    API.getFormObject = function(key) {
      var xObject = renderTargets[renderTargetMap[key]];
      return {
        x: xObject.x,
        y: xObject.y,
        width: xObject.width,
        height: xObject.height,
        matrix: xObject.matrix
      };
    };

    /**
     * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf').
     * Uses FileSaver.js-method saveAs.
     *
     * @memberof jsPDF#
     * @name save
     * @function
     * @instance
     * @param  {string} filename The filename including extension.
     * @param  {Object} options An Object with additional options, possible options: 'returnPromise'.
     * @returns {jsPDF} jsPDF-instance     */
    API.save = function(filename, options) {
      filename = filename || "generated.pdf";

      options = options || {};
      options.returnPromise = options.returnPromise || false;

      if (options.returnPromise === false) {
        saveAs(getBlob(buildDocument()), filename);
        if (typeof saveAs.unload === "function") {
          if (global.setTimeout) {
            setTimeout(saveAs.unload, 911);
          }
        }
      } else {
        return new Promise(function(resolve, reject) {
          try {
            var result = saveAs(getBlob(buildDocument()), filename);
            if (typeof saveAs.unload === "function") {
              if (global.setTimeout) {
                setTimeout(saveAs.unload, 911);
              }
            }
            resolve(result);
          } catch (e) {
            reject(e.message);
          }
        });
      }
    };

    // applying plugins (more methods) ON TOP of built-in API.
    // this is intentional as we allow plugins to override
    // built-ins
    for (var plugin in jsPDF.API) {
      if (jsPDF.API.hasOwnProperty(plugin)) {
        if (plugin === "events" && jsPDF.API.events.length) {
          (function(events, newEvents) {
            // jsPDF.API.events is a JS Array of Arrays
            // where each Array is a pair of event name, handler
            // Events were added by plugins to the jsPDF instantiator.
            // These are always added to the new instance and some ran
            // during instantiation.
            var eventname, handler_and_args, i;

            for (i = newEvents.length - 1; i !== -1; i--) {
              // subscribe takes 3 args: 'topic', function, runonce_flag
              // if undefined, runonce is false.
              // users can attach callback directly,
              // or they can attach an array with [callback, runonce_flag]
              // that's what the "apply" magic is for below.
              eventname = newEvents[i][0];
              handler_and_args = newEvents[i][1];
              events.subscribe.apply(
                events,
                [eventname].concat(
                  typeof handler_and_args === "function"
                    ? [handler_and_args]
                    : handler_and_args
                )
              );
            }
          })(events, jsPDF.API.events);
        } else {
          API[plugin] = jsPDF.API[plugin];
        }
      }
    }

    var getPageWidth = (API.getPageWidth = function(pageNumber) {
      pageNumber = pageNumber || currentPage;
      return (
        (pagesContext[pageNumber].mediaBox.topRightX -
          pagesContext[pageNumber].mediaBox.bottomLeftX) /
        scaleFactor
      );
    });

    var setPageWidth = (API.setPageWidth = function(pageNumber, value) {
      pagesContext[pageNumber].mediaBox.topRightX =
        value * scaleFactor + pagesContext[pageNumber].mediaBox.bottomLeftX;
    });

    var getPageHeight = (API.getPageHeight = function(pageNumber) {
      pageNumber = pageNumber || currentPage;
      return (
        (pagesContext[pageNumber].mediaBox.topRightY -
          pagesContext[pageNumber].mediaBox.bottomLeftY) /
        scaleFactor
      );
    });

    var setPageHeight = (API.setPageHeight = function(pageNumber, value) {
      pagesContext[pageNumber].mediaBox.topRightY =
        value * scaleFactor + pagesContext[pageNumber].mediaBox.bottomLeftY;
    });

    /**
     * Object exposing internal API to plugins
     * @public
     * @ignore
     */
    API.internal = {
      pdfEscape: pdfEscape,
      getStyle: getStyle,
      getFont: getFontEntry,
      getFontSize: getFontSize,
      getCharSpace: getCharSpace,
      getTextColor: getTextColor,
      getLineHeight: getLineHeight,
      getLineHeightFactor: getLineHeightFactor,
      write: write,
      getHorizontalCoordinate: getHorizontalCoordinate,
      getVerticalCoordinate: getVerticalCoordinate,
      getCoordinateString: getHorizontalCoordinateString,
      getVerticalCoordinateString: getVerticalCoordinateString,
      collections: {},
      newObject: newObject,
      newAdditionalObject: newAdditionalObject,
      newObjectDeferred: newObjectDeferred,
      newObjectDeferredBegin: newObjectDeferredBegin,
      getFilters: getFilters,
      putStream: putStream,
      events: events,
      scaleFactor: scaleFactor,
      pageSize: {
        getWidth: function() {
          return getPageWidth(currentPage);
        },
        setWidth: function(value) {
          setPageWidth(currentPage, value);
        },
        getHeight: function() {
          return getPageHeight(currentPage);
        },
        setHeight: function(value) {
          setPageHeight(currentPage, value);
        }
      },
      output: output,
      getNumberOfPages: getNumberOfPages,
      pages: pages,
      out: out,
      f2: f2,
      f3: f3,
      getPageInfo: getPageInfo,
      getPageInfoByObjId: getPageInfoByObjId,
      getCurrentPageInfo: getCurrentPageInfo,
      getPDFVersion: getPdfVersion,
      Point: Point,
      Rectangle: Rectangle,
      Matrix: Matrix,
      hasHotfix: hasHotfix //Expose the hasHotfix check so plugins can also check them.
    };

    Object.defineProperty(API.internal.pageSize, "width", {
      get: function() {
        return getPageWidth(currentPage);
      },
      set: function(value) {
        setPageWidth(currentPage, value);
      },
      enumerable: true,
      configurable: true
    });
    Object.defineProperty(API.internal.pageSize, "height", {
      get: function() {
        return getPageHeight(currentPage);
      },
      set: function(value) {
        setPageHeight(currentPage, value);
      },
      enumerable: true,
      configurable: true
    });

    //////////////////////////////////////////////////////
    // continuing initialization of jsPDF Document object
    //////////////////////////////////////////////////////
    // Add the first page automatically
    addFonts(standardFonts);
    activeFontKey = "F1";
    _addPage(format, orientation);

    events.publish("initialized");
    return API;
  }

  /**
   * jsPDF.API is a STATIC property of jsPDF class.
   * jsPDF.API is an object you can add methods and properties to.
   * The methods / properties you add will show up in new jsPDF objects.
   *
   * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics,
   * callbacks to this object. These will be reassigned to all new instances of jsPDF.
   *
   * @static
   * @public
   * @memberof jsPDF#
   * @name API
   *
   * @example
   * jsPDF.API.mymethod = function(){
   *   // 'this' will be ref to internal API object. see jsPDF source
   *   // , so you can refer to built-in methods like so:
   *   //     this.line(....)
   *   //     this.text(....)
   * }
   * var pdfdoc = new jsPDF()
   * pdfdoc.mymethod() // <- !!!!!!
   */
  jsPDF.API = {
    events: []
  };
  /**
   * The version of jsPDF.
   * @name version
   * @type {string}
   * @memberof jsPDF#
   */
  jsPDF.version = "0.0.0";

  if (typeof define === "function" && define.amd) {
    define(function() {
      return jsPDF;
    });
  } else if (typeof module !== "undefined" && module.exports) {
    module.exports = jsPDF;
    module.exports.jsPDF = jsPDF;
  } else {
    global.jsPDF = jsPDF;
  }
  return jsPDF;
})(
  (typeof self !== "undefined" && self) ||
    (typeof window !== "undefined" && window) ||
    (typeof global !== "undefined" && global) ||
    Function('return typeof this === "object" && this.content')() ||
    Function("return this")()
);
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window
