# jspdf.js

    /* 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=2] 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.
      * @returns {jsPDF} jsPDF-instance
      * @description
      * ```
      * {
      *  orientation: 'p',
      *  unit: 'mm',
      *  format: 'a4',
      *  putOnlyUsedFonts:true
      * }
      * ```
      *
      * @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;

        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;
          precision = options.precision;
        }

        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.40, 209.76],
          'a9': [104.88, 147.40],
          'a10': [73.70, 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.90, 708.66],
          'b6': [354.33, 498.90],
          '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 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');
          }
          if (precision >= 16) {
            return number.toFixed(precision).replace(/0+$/, "");
          } else {
            return number.toFixed(tmpPrecision);
          }
        };

        var scale = API.scale = API.__private__.scale = function (number) {
          if (isNaN(number)) {
            throw new Error('Invalid argument passed to jsPDF.scale');
          }
          return number * scaleFactor;
        };

        var 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 transformY = function (y) {
          return y;
        };

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

        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) {
          return (new Uint8Array(data.split(''))).buffer;
        };

        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) {
          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 () {
          return activeFontSize;
        };

        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) {
          var round = function (number) {
            if (precision >= 16) {
              return number;
            } else {
              return Math.round(number * 100000) / 100000
            }
          };

          var _matrix = [];

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

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

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

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

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

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

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

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

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

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

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

          Object.defineProperty(this, 'f', {
            get: function () {
              return _matrix[5];
            },
            set: function (value) {
              _matrix[5] = round(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]).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) {
          var tmpPrecision = precision || parmPrecision || 5
          var round = function (number) {
            if (precision >= 16) {
              return hpf(number);
            } else {
              return Math.round(number * Math.pow(10, tmpPrecision)) / Math.pow(10, tmpPrecision)
            }
          };

          return [round(this.sx), round(this.shy), round(this.shx), round(this.sy), round(this.tx), round(this.ty)].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
         */
        API.matrixMult = function (m1, m2) {
          return m1.multiply(m2);
        };

        /**
         * 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 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.000', '1.000', '1.000', 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(f2(page.mediaBox.bottomLeftX)) + ' ' + parseFloat(f2(page.mediaBox.bottomLeftY)) + ' ' + f2(page.mediaBox.topRightX) + ' ' + f2(page.mediaBox.topRightY) + ']');
          if (page.cropBox !== null) {
            out('/CropBox [' + f2(page.cropBox.bottomLeftX) + ' ' + f2(page.cropBox.bottomLeftY) + ' ' + f2(page.cropBox.topRightX) + ' ' + f2(page.cropBox.topRightY) + ']');
          }

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

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

          if (page.artBox !== null) {
            out('/ArtBox [' + f2(page.artBox.bottomLeftX) + ' ' + f2(page.artBox.bottomLeftY) + ' ' + f2(page.artBox.topRightX) + ' ' + f2(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');
          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();
          out("<<");
          out("/Type /XObject");
          out("/Subtype /Form");
          out(
            "/BBox [" +
            [hpf(xObject.x), hpf(xObject.y), hpf(xObject.x + xObject.width), hpf(xObject.y + xObject.height)].join(
              " "
            ) +
            "]"
          );
          out("/Matrix [" + xObject.matrix.toString() + "]");
          // TODO: /Resources

          var p = xObject.pages[1].join("\n");
          out("/Length " + p.length);

          out(">>");
          putStream(p);
          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.
           */
          out("/Shading <<");

          // 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);
          out("<< /FunctionType 0");
          out("/Domain [0.0 1.0]");
          out("/Size [" + numberSamples + "]");
          out("/BitsPerSample 8");
          out("/Range [0.0 1.0 0.0 1.0 0.0 1.0]");
          out("/Decode [0.0 1.0 0.0 1.0 0.0 1.0]");
          out("/Length " + stream.length);
          // The stream is Hex encoded
          out("/Filter /ASCIIHexDecode");
          out(">>");
          putStream(stream);
          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");
          out(">>");
        };

        var putTilingPattern = function (pattern) {
          var resourcesObjectNumber = newObject();
          putResourceDictionary();
          out("endobj");
          pattern.objectNumber = newObject();
          out("<< /Type /Pattern");
          out("/PatternType 1"); // tiling pattern
          out("/PaintType 1"); // colored tiling pattern
          out("/TilingType 1"); // constant spacing
          out("/BBox [" + pattern.boundingBox.map(hpf).join(" ") + "]");
          out("/XStep " + hpf(pattern.xStep));
          out("/YStep " + hpf(pattern.yStep));
          out("/Length " + pattern.stream.length);
          out("/Resources " + resourcesObjectNumber + " 0 R"); // TODO: resources
          pattern.matrix && out("/Matrix [" + pattern.matrix.toString() + "]");

          out(">>");

          putStream(pattern.stream);
          out("endobj");
        };

        var putPatterns = function () {
          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]);
              }
            }
          }
        };

        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 () {
          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
              ) {
                out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
              }
            }
            events.publish("putTilingPatternDict");
          }
        };

        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 () {
          out('<<');
          out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
          putFontDict();
          putShadingPatternDict();
          putTilingPatternDict();
          putGStatesDict();
          putXobjectDict();
          out('>>');
        };

        var putResources = function () {
          putFonts();
          putGStates();
          putXObjects();
          putPatterns();
          events.publish('putResources');
          newObjectDeferredBegin(resourceDictionaryObjId, true);
          putResourceDictionary();
          out('endobj');
          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 (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;
          }

          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 () {
          beginPage.apply(this, arguments);
          // 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.
         * @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 {
            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;
            }

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

          //charSpace

          charSpace = options.charSpace || activeCharSpace;

          if (typeof charSpace !== 'undefined') {
            xtra += f3(charSpace * scaleFactor) + " 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 = (prevWidth - lineWidths[i]) * scaleFactor;
                  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 = (prevWidth - lineWidths[j]) / 2 * scaleFactor;
                  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(f2((maxWidth - lineWidths[l]) / (da[l].split(" ").length - 1) * scaleFactor));
                }
                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) {
              parmTransformationMatrix.tx = parseFloat(f2(parmPosX));
              parmTransformationMatrix.ty = parseFloat(f2(parmPosY));
              position = parmTransformationMatrix.join(" ") + " Tm\n";
            } else {
              position = f2(parmPosX) + " " + f2(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) + 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 += f2(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', 'F', 'DF', 'FD', 'f', 'f*', 'B', 'B*'];
          var result = false;
          if (validStyleVariants.indexOf(style) !== -1) {
            result = true;
          }
          return (result);
        };

        var getStyle = API.__private__.getStyle = API.getStyle = function (style) {

          // see path-painting operators in PDF spec
          var op = 'S'; // stroke
          if (style === 'F') {
            op = 'f'; // fill
          } else if (style === 'FD' || style === 'DF') {
            op = 'B'; // both
          } else if (style === 'f' || style === 'f*' || style === 'B' ||
            style === '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;
          }
          return op;
        };

        /**
         * Close the current path. The PDF "h" operator.
         * @name close
         * @function
         * @instance
         * @returns {jsPDF}
         * @memberof jsPDF#
         */
        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 || 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, this.internal.pageSize.getHeight());

            if (patternData.matrix) {
              matrix = (patternData.matrix || identityMatrix).multiply(matrix);
              // 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}
         */
        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}
         */
        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}
         */
        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) {
          style = style || 'S';
          if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2) || !isValidStyle(style)) {
            throw new Error('Invalid arguments passed to jsPDF.line');
          }
          return this.lines([
            [x2 - x1, y2 - y1]
          ], x1, y1);
        };

        /**
         * 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. 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.
         * @param {boolean} closed If true, the path is closed with a straight line from the end of the last curve to the starting point.
         * @function
         * @instance
         * @returns {jsPDF}
         * @memberof jsPDF#
         * @name lines
         */
        API.__private__.lines = API.lines = function (lines, x, y, scale, style, closed) {
          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
          out(f3(getHorizontalCoordinate(x)) + ' ' + f3(getVerticalCoordinate(y)) + ' m ');

          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
              out(f3(getHorizontalCoordinate(x4)) + ' ' + f3(getVerticalCoordinate(y4)) + ' l');
            } 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
              out(
                f3(getHorizontalCoordinate(x2)) + ' ' +
                f3(getVerticalCoordinate(y2)) + ' ' +
                f3(getHorizontalCoordinate(x3)) + ' ' +
                f3(getVerticalCoordinate(y3)) + ' ' +
                f3(getHorizontalCoordinate(x4)) + ' ' +
                f3(getVerticalCoordinate(y4)) + ' c');
            }
          }

          if (closed) {
            out(' h');
          }

          // stroking / filling / both the path
          if (style !== null) {
            out(getStyle(style));
          }
          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":
                this.moveTo(coords[0], coords[1]);
                break;
              case "l":
                this.lineTo(coords[0], coords[1]);
                break;
              case "c":
                this.curveTo.apply(this, coords);
                break;
              case "h":
                this.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. 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.
         * @function
         * @instance
         * @returns {jsPDF}
         * @memberof jsPDF#
         * @name rect
         */
        API.__private__.rect = API.rect = function (x, y, w, h, style) {
          if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h) || !isValidStyle(style)) {
            throw new Error('Invalid arguments passed to jsPDF.rect');
          }

          out([
            f2(getHorizontalCoordinate(x)),
            f2(getVerticalCoordinate(y)),
            f2(scale(w)),
            f2(scale(-h)),
            're'
          ].join(' '));

          if (style !== null) {
            out(getStyle(style));
          }

          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. 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.
         * @function
         * @instance
         * @returns {jsPDF}
         * @memberof jsPDF#
         * @name triangle
         */
        API.__private__.triangle = API.triangle = function (x1, y1, x2, y2, x3, y3, style) {
          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);
          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. 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.
         * @function
         * @instance
         * @returns {jsPDF}
         * @memberof jsPDF#
         * @name roundedRect
         */
        API.__private__.roundedRect = API.roundedRect = function (x, y, w, h, rx, ry, style) {
          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);
          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);
          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. 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.
         * @function
         * @instance
         * @returns {jsPDF}
         * @memberof jsPDF#
         * @name ellipse
         */
        API.__private__.ellipse = API.ellipse = function (x, y, rx, ry, style) {
          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;

          out([
            f2(getHorizontalCoordinate(x + rx)),
            f2(getVerticalCoordinate(y)),
            'm',
            f2(getHorizontalCoordinate(x + rx)),
            f2(getVerticalCoordinate(y - ly)),
            f2(getHorizontalCoordinate(x + lx)),
            f2(getVerticalCoordinate(y - ry)),
            f2(getHorizontalCoordinate(x)),
            f2(getVerticalCoordinate(y - ry)),
            'c'
          ].join(' '));
          out([
            f2(getHorizontalCoordinate(x - lx)),
            f2(getVerticalCoordinate(y - ry)),
            f2(getHorizontalCoordinate(x - rx)),
            f2(getVerticalCoordinate(y - ly)),
            f2(getHorizontalCoordinate(x - rx)),
            f2(getVerticalCoordinate(y)),
            'c'
          ].join(' '));
          out([
            f2(getHorizontalCoordinate(x - rx)),
            f2(getVerticalCoordinate(y + ly)),
            f2(getHorizontalCoordinate(x - lx)),
            f2(getVerticalCoordinate(y + ry)),
            f2(getHorizontalCoordinate(x)),
            f2(getVerticalCoordinate(y + ry)),
            'c'
          ].join(' '));
          out([
            f2(getHorizontalCoordinate(x + lx)),
            f2(getVerticalCoordinate(y + ry)),
            f2(getHorizontalCoordinate(x + rx)),
            f2(getVerticalCoordinate(y + ly)),
            f2(getHorizontalCoordinate(x + rx)),
            f2(getVerticalCoordinate(y)),
            'c'
          ].join(' '));

          if (style !== null) {
            out(getStyle(style));
          }

          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. 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.
         * @function
         * @instance
         * @returns {jsPDF}
         * @memberof jsPDF#
         * @name circle
         */
        API.__private__.circle = API.circle = function (x, y, r, style) {
          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);
        };

        /**
         * 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(f2(width * scaleFactor) + ' 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 f3(x * scaleFactor) }).join(' ');
          dashPhase = f3(dashPhase * scaleFactor);

          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) {
          return pagesContext[currentPage].mediaBox.topRightY - pagesContext[currentPage].mediaBox.bottomLeftY - scale(value);
        };

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

        var getVerticalCoordinateString = API.__private__.getVerticalCoordinateString = API.getVerticalCoordinateString = function (value) {
          return f2(pagesContext[currentPage].mediaBox.topRightY - pagesContext[currentPage].mediaBox.bottomLeftY - scale(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');
          }
          miterLimit = parseFloat(f2(length * scaleFactor));
          out(miterLimit + ' M');

          return this;
        };
        /**
        * GState
        */

        /**
         * An object representing a pdf graphics state.
         * @class GState
         */
        API.GState = function (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 pdf.
         * @param {String} text
         * @returns {jsPDF}
         * @memberof jsPDF#
         * @name comment
         */
        API.comment = function (text) {
          out("#" + text);
          return this;
        };

        /**
        * Matrix
        */

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

        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.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);
          }
        };

        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('jsPDF', 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