UNPKG

jspdf

Version:

PDF Document creation from JavaScript

1,824 lines (1,648 loc) 966 kB
/** @license * * jsPDF - PDF Document creation from JavaScript * Version 2.0.0 Built on 2020-08-11T07:57:54.902Z * CommitID 00000000 * * Copyright (c) 2010-2020 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF * 2015-2020 yWorks GmbH, http://www.yworks.com * 2015-2020 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX * 2016-2018 Aras Abbasi <aras.abbasi@gmail.com> * 2010 Aaron Spike, https://github.com/acspike * 2012 Willow Systems Corporation, willow-systems.com * 2012 Pablo Hess, https://github.com/pablohess * 2012 Florian Jenett, https://github.com/fjenett * 2013 Warren Weckesser, https://github.com/warrenweckesser * 2013 Youssef Beddad, https://github.com/lifof * 2013 Lee Driscoll, https://github.com/lsdriscoll * 2013 Stefan Slonevskiy, https://github.com/stefslon * 2013 Jeremy Morel, https://github.com/jmorel * 2013 Christoph Hartmann, https://github.com/chris-rock * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria * 2014 James Makes, https://github.com/dollaruw * 2014 Diego Casorran, https://github.com/diegocr * 2014 Steven Spungin, https://github.com/Flamenco * 2014 Kenneth Glassey, https://github.com/Gavvers * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Contributor(s): * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, * kim3er, mfo, alnorth, Flamenco */ var globalObject = (function() { return "undefined" !== typeof window ? window : "undefined" !== typeof global ? global : "undefined" !== typeof self ? self : this; })(); function consoleLog() { if (globalObject.console && typeof globalObject.console.log === "function") { globalObject.console.log.apply(globalObject.console, arguments); } } function consoleWarn(str) { if (globalObject.console) { if (typeof globalObject.console.warn === "function") { globalObject.console.warn.apply(globalObject.console, arguments); } else { consoleLog.call(null, arguments); } } } function consoleError(str) { if (globalObject.console) { if (typeof globalObject.console.error === "function") { globalObject.console.error.apply(globalObject.console, arguments); } else { consoleLog(str); } } } var console = { log: consoleLog, warn: consoleWarn, error: consoleError }; /** * @license * FileSaver.js * A saveAs() FileSaver implementation. * * By Eli Grey, http://eligrey.com * * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) * source : http://purl.eligrey.com/github/FileSaver.js */ function bom(blob, opts) { if (typeof opts === "undefined") opts = { autoBom: false }; else if (typeof opts !== "object") { console.warn("Deprecated: Expected third argument to be a object"); opts = { autoBom: !opts }; } // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF if ( opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test( blob.type ) ) { return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type }); } return blob; } function download(url, name, opts) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "blob"; xhr.onload = function() { saveAs(xhr.response, name, opts); }; xhr.onerror = function() { console.error("could not download file"); }; xhr.send(); } function corsEnabled(url) { var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker xhr.open("HEAD", url, false); try { xhr.send(); } catch (e) {} return xhr.status >= 200 && xhr.status <= 299; } // `a.click()` doesn't work for all browsers (#465) function click(node) { try { node.dispatchEvent(new MouseEvent("click")); } catch (e) { var evt = document.createEvent("MouseEvents"); evt.initMouseEvent( "click", true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null ); node.dispatchEvent(evt); } } var saveAs = globalObject.saveAs || // probably in some web worker (typeof window !== "object" || window !== globalObject ? function saveAs() { /* noop */ } : // Use download attribute first if possible (#193 Lumia mobile) "download" in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) { var URL = globalObject.URL || globalObject.webkitURL; var a = document.createElement("a"); name = name || blob.name || "download"; a.download = name; a.rel = "noopener"; // tabnabbing // TODO: detect chrome extensions & packaged apps // a.target = '_blank' if (typeof blob === "string") { // Support regular links a.href = blob; if (a.origin !== location.origin) { corsEnabled(a.href) ? download(blob, name, opts) : click(a, (a.target = "_blank")); } else { click(a); } } else { // Support blobs a.href = URL.createObjectURL(blob); setTimeout(function() { URL.revokeObjectURL(a.href); }, 4e4); // 40s setTimeout(function() { click(a); }, 0); } } : // Use msSaveOrOpenBlob as a second approach "msSaveOrOpenBlob" in navigator ? function saveAs(blob, name, opts) { name = name || blob.name || "download"; if (typeof blob === "string") { if (corsEnabled(blob)) { download(blob, name, opts); } else { var a = document.createElement("a"); a.href = blob; a.target = "_blank"; setTimeout(function() { click(a); }); } } else { navigator.msSaveOrOpenBlob(bom(blob, opts), name); } } : // Fallback to using FileReader and a popup function saveAs(blob, name, opts, popup) { // Open a popup immediately do go around popup blocker // Mostly only available on user interaction and the fileReader is async so... popup = popup || open("", "_blank"); if (popup) { popup.document.title = popup.document.body.innerText = "downloading..."; } if (typeof blob === "string") return download(blob, name, opts); var force = blob.type === "application/octet-stream"; var isSafari = /constructor/i.test(globalObject.HTMLElement) || globalObject.safari; var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); if ( (isChromeIOS || (force && isSafari)) && typeof FileReader === "object" ) { // Safari doesn't allow downloading of blob URLs var reader = new FileReader(); reader.onloadend = function() { var url = reader.result; url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, "data:attachment/file;"); if (popup) popup.location.href = url; else location = url; popup = null; // reverse-tabnabbing #460 }; reader.readAsDataURL(blob); } else { var URL = globalObject.URL || globalObject.webkitURL; var url = URL.createObjectURL(blob); if (popup) popup.location = url; else location.href = url; popup = null; // reverse-tabnabbing #460 setTimeout(function() { URL.revokeObjectURL(url); }, 4e4); // 40s } }); /** * A class to parse color values * @author Stoyan Stefanov <sstoo@gmail.com> * {@link http://www.phpied.com/rgb-color-parser-in-javascript/} * @license Use it if you like it */ function RGBColor(color_string) { color_string = color_string || ""; this.ok = false; // strip any leading # if (color_string.charAt(0) == "#") { // remove # if any color_string = color_string.substr(1, 6); } color_string = color_string.replace(/ /g, ""); color_string = color_string.toLowerCase(); var channels; // before getting into regexps, try simple matches // and overwrite the input var simple_colors = { aliceblue: "f0f8ff", antiquewhite: "faebd7", aqua: "00ffff", aquamarine: "7fffd4", azure: "f0ffff", beige: "f5f5dc", bisque: "ffe4c4", black: "000000", blanchedalmond: "ffebcd", blue: "0000ff", blueviolet: "8a2be2", brown: "a52a2a", burlywood: "deb887", cadetblue: "5f9ea0", chartreuse: "7fff00", chocolate: "d2691e", coral: "ff7f50", cornflowerblue: "6495ed", cornsilk: "fff8dc", crimson: "dc143c", cyan: "00ffff", darkblue: "00008b", darkcyan: "008b8b", darkgoldenrod: "b8860b", darkgray: "a9a9a9", darkgreen: "006400", darkkhaki: "bdb76b", darkmagenta: "8b008b", darkolivegreen: "556b2f", darkorange: "ff8c00", darkorchid: "9932cc", darkred: "8b0000", darksalmon: "e9967a", darkseagreen: "8fbc8f", darkslateblue: "483d8b", darkslategray: "2f4f4f", darkturquoise: "00ced1", darkviolet: "9400d3", deeppink: "ff1493", deepskyblue: "00bfff", dimgray: "696969", dodgerblue: "1e90ff", feldspar: "d19275", firebrick: "b22222", floralwhite: "fffaf0", forestgreen: "228b22", fuchsia: "ff00ff", gainsboro: "dcdcdc", ghostwhite: "f8f8ff", gold: "ffd700", goldenrod: "daa520", gray: "808080", green: "008000", greenyellow: "adff2f", honeydew: "f0fff0", hotpink: "ff69b4", indianred: "cd5c5c", indigo: "4b0082", ivory: "fffff0", khaki: "f0e68c", lavender: "e6e6fa", lavenderblush: "fff0f5", lawngreen: "7cfc00", lemonchiffon: "fffacd", lightblue: "add8e6", lightcoral: "f08080", lightcyan: "e0ffff", lightgoldenrodyellow: "fafad2", lightgrey: "d3d3d3", lightgreen: "90ee90", lightpink: "ffb6c1", lightsalmon: "ffa07a", lightseagreen: "20b2aa", lightskyblue: "87cefa", lightslateblue: "8470ff", lightslategray: "778899", lightsteelblue: "b0c4de", lightyellow: "ffffe0", lime: "00ff00", limegreen: "32cd32", linen: "faf0e6", magenta: "ff00ff", maroon: "800000", mediumaquamarine: "66cdaa", mediumblue: "0000cd", mediumorchid: "ba55d3", mediumpurple: "9370d8", mediumseagreen: "3cb371", mediumslateblue: "7b68ee", mediumspringgreen: "00fa9a", mediumturquoise: "48d1cc", mediumvioletred: "c71585", midnightblue: "191970", mintcream: "f5fffa", mistyrose: "ffe4e1", moccasin: "ffe4b5", navajowhite: "ffdead", navy: "000080", oldlace: "fdf5e6", olive: "808000", olivedrab: "6b8e23", orange: "ffa500", orangered: "ff4500", orchid: "da70d6", palegoldenrod: "eee8aa", palegreen: "98fb98", paleturquoise: "afeeee", palevioletred: "d87093", papayawhip: "ffefd5", peachpuff: "ffdab9", peru: "cd853f", pink: "ffc0cb", plum: "dda0dd", powderblue: "b0e0e6", purple: "800080", red: "ff0000", rosybrown: "bc8f8f", royalblue: "4169e1", saddlebrown: "8b4513", salmon: "fa8072", sandybrown: "f4a460", seagreen: "2e8b57", seashell: "fff5ee", sienna: "a0522d", silver: "c0c0c0", skyblue: "87ceeb", slateblue: "6a5acd", slategray: "708090", snow: "fffafa", springgreen: "00ff7f", steelblue: "4682b4", tan: "d2b48c", teal: "008080", thistle: "d8bfd8", tomato: "ff6347", turquoise: "40e0d0", violet: "ee82ee", violetred: "d02090", wheat: "f5deb3", white: "ffffff", whitesmoke: "f5f5f5", yellow: "ffff00", yellowgreen: "9acd32" }; color_string = simple_colors[color_string] || color_string; // array of color definition objects var color_defs = [ { re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, example: ["rgb(123, 234, 45)", "rgb(255,234,245)"], process: function(bits) { return [parseInt(bits[1]), parseInt(bits[2]), parseInt(bits[3])]; } }, { re: /^(\w{2})(\w{2})(\w{2})$/, example: ["#00ff00", "336699"], process: function(bits) { return [ parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16) ]; } }, { re: /^(\w{1})(\w{1})(\w{1})$/, example: ["#fb0", "f0f"], process: function(bits) { return [ parseInt(bits[1] + bits[1], 16), parseInt(bits[2] + bits[2], 16), parseInt(bits[3] + bits[3], 16) ]; } } ]; // search through the definitions to find a match for (var i = 0; i < color_defs.length; i++) { var re = color_defs[i].re; var processor = color_defs[i].process; var bits = re.exec(color_string); if (bits) { channels = processor(bits); this.r = channels[0]; this.g = channels[1]; this.b = channels[2]; this.ok = true; } } // validate/cleanup values this.r = this.r < 0 || isNaN(this.r) ? 0 : this.r > 255 ? 255 : this.r; this.g = this.g < 0 || isNaN(this.g) ? 0 : this.g > 255 ? 255 : this.g; this.b = this.b < 0 || isNaN(this.b) ? 0 : this.b > 255 ? 255 : this.b; // some getters this.toRGB = function() { return "rgb(" + this.r + ", " + this.g + ", " + this.b + ")"; }; this.toHex = function() { var r = this.r.toString(16); var g = this.g.toString(16); var b = this.b.toString(16); if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; return "#" + r + g + b; }; } var atob, btoa; (function() { atob = globalObject.atob; btoa = globalObject.btoa; return; })(); /* eslint-disable no-console */ /** * 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 (globalObject.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; }; } function GState(parameters) { if (!(this instanceof GState)) { return new GState(parameters); } /** * @name GState#opacity * @type {any} */ /** * @name GState#stroke-opacity * @type {any} */ var supported = "opacity,stroke-opacity".split(","); for (var p in parameters) { if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) { this[p] = parameters[p]; } } /** * @name GState#id * @type {string} */ this.id = ""; // set by addGState() /** * @name GState#objectNumber * @type {number} */ this.objectNumber = -1; // will be set by putGState() } 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; }; function Pattern(gState, matrix) { this.gState = gState; this.matrix = matrix; this.id = ""; // set by addPattern() this.objectNumber = -1; // will be set by putPattern() } function ShadingPattern(type, coords, colors, gState, matrix) { if (!(this instanceof ShadingPattern)) { return new ShadingPattern(type, coords, colors, gState, matrix); } // see putPattern() for information how they are realized this.type = type === "axial" ? 2 : 3; this.coords = coords; this.colors = colors; Pattern.call(this, gState, matrix); } function TilingPattern(boundingBox, xStep, yStep, gState, matrix) { if (!(this instanceof TilingPattern)) { return new TilingPattern(boundingBox, xStep, yStep, gState, matrix); } this.boundingBox = boundingBox; this.xStep = xStep; this.yStep = yStep; this.stream = ""; // set by endTilingPattern(); this.cloneIndex = 0; Pattern.call(this, gState, matrix); } /** * Creates new jsPDF document object instance. * @name jsPDF * @class * @param {Object} [options] - Collection of settings initializing the jsPDF-instance * @param {string} [options.orientation=portrait] - Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l").<br /> * @param {string} [options.unit=mm] Measurement unit (base unit) to be used when coordinates are specified.<br /> * Possible values are "pt" (points), "mm", "cm", "m", "in" or "px". * @param {string/Array} [options.format=a4] The format of the first page. Can be:<ul><li>a0 - a10</li><li>b0 - b10</li><li>c0 - c10</li><li>dl</li><li>letter</li><li>government-letter</li><li>legal</li><li>junior-legal</li><li>ledger</li><li>tabloid</li><li>credit-card</li></ul><br /> * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89] * @param {boolean} [options.putOnlyUsedFonts=false] Only put fonts into the PDF, which were used. * @param {boolean} [options.compress=false] Compress the generated PDF. * @param {number} [options.precision=16] Precision of the element-positions. * @param {number} [options.userUnit=1.0] Not to be confused with the base unit. Please inform yourself before you use it. * @param {number|"smart"} [options.floatPrecision=16] * @returns {jsPDF} jsPDF-instance * @description * ``` * { * orientation: 'p', * unit: 'mm', * format: 'a4', * putOnlyUsedFonts:true, * floatPrecision: 16 // or "smart", default is 16 * } * ``` * * @constructor */ function jsPDF(options) { var orientation = typeof arguments[0] === "string" ? arguments[0] : "p"; var unit = arguments[1]; var format = arguments[2]; var compressPdf = arguments[3]; var filters = []; var userUnit = 1.0; var precision; var floatPrecision = 16; var defaultPathOperation = "S"; options = options || {}; if (typeof options === "object") { orientation = options.orientation; unit = options.unit || unit; format = options.format || format; compressPdf = options.compress || options.compressPdf || compressPdf; userUnit = typeof options.userUnit === "number" ? Math.abs(options.userUnit) : 1.0; if (typeof options.precision !== "undefined") { precision = options.precision; } if (typeof options.floatPrecision !== "undefined") { floatPrecision = options.floatPrecision; } defaultPathOperation = options.defaultPathOperation || "S"; } filters = options.filters || (compressPdf === true ? ["FlateEncode"] : filters); unit = unit || "mm"; orientation = ("" + (orientation || "P")).toLowerCase(); var putOnlyUsedFonts = options.putOnlyUsedFonts || false; var usedFonts = {}; var API = { internal: {}, __private__: {} }; API.__private__.PubSub = PubSub; var pdfVersion = "1.3"; var getPdfVersion = (API.__private__.getPdfVersion = function() { return pdfVersion; }); API.__private__.setPdfVersion = function(value) { pdfVersion = value; }; // Size in pt of various paper formats var pageFormats = { a0: [2383.94, 3370.39], a1: [1683.78, 2383.94], a2: [1190.55, 1683.78], a3: [841.89, 1190.55], a4: [595.28, 841.89], a5: [419.53, 595.28], a6: [297.64, 419.53], a7: [209.76, 297.64], a8: [147.4, 209.76], a9: [104.88, 147.4], a10: [73.7, 104.88], b0: [2834.65, 4008.19], b1: [2004.09, 2834.65], b2: [1417.32, 2004.09], b3: [1000.63, 1417.32], b4: [708.66, 1000.63], b5: [498.9, 708.66], b6: [354.33, 498.9], b7: [249.45, 354.33], b8: [175.75, 249.45], b9: [124.72, 175.75], b10: [87.87, 124.72], c0: [2599.37, 3676.54], c1: [1836.85, 2599.37], c2: [1298.27, 1836.85], c3: [918.43, 1298.27], c4: [649.13, 918.43], c5: [459.21, 649.13], c6: [323.15, 459.21], c7: [229.61, 323.15], c8: [161.57, 229.61], c9: [113.39, 161.57], c10: [79.37, 113.39], dl: [311.81, 623.62], letter: [612, 792], "government-letter": [576, 756], legal: [612, 1008], "junior-legal": [576, 360], ledger: [1224, 792], tabloid: [792, 1224], "credit-card": [153, 243] }; API.__private__.getPageFormats = function() { return pageFormats; }; var getPageFormat = (API.__private__.getPageFormat = function(value) { return pageFormats[value]; }); format = format || "a4"; var ApiMode = { COMPAT: "compat", ADVANCED: "advanced" }; var apiMode = ApiMode.COMPAT; function advancedAPI() { // prepend global change of basis matrix // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix // that does this job for us (however, texts, images and similar objects must be drawn bottom up)) this.saveGraphicsState(); out( new Matrix( scaleFactor, 0, 0, -scaleFactor, 0, getPageHeight() * scaleFactor ).toString() + " cm" ); this.setFontSize(this.getFontSize() / scaleFactor); // The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation // was "n" (none). Although this has nothing to do with transforms, we should use the API switch here. defaultPathOperation = "n"; apiMode = ApiMode.ADVANCED; } function compatAPI() { this.restoreGraphicsState(); defaultPathOperation = "S"; apiMode = ApiMode.COMPAT; } /** * @callback ApiSwitchBody * @param {jsPDF} pdf */ /** * For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual * screen coordinates and the PDF coordinate system. * - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms * - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might * not support this mode, though. * Initial mode is "compat". * * You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to * the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}. * * Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the * callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject} * or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode. * * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. * The API mode will be switched back automatically afterwards. * @returns {jsPDF} * @memberof jsPDF# * @name advancedAPI */ API.advancedAPI = function(body) { var doSwitch = apiMode === ApiMode.COMPAT; if (doSwitch) { advancedAPI.call(this); } if (typeof body !== "function") { return this; } body(this); if (doSwitch) { compatAPI.call(this); } return this; }; /** * Switches to "compat" API mode. See {@link advancedAPI} for more details. * * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. * The API mode will be switched back automatically afterwards. * @return {jsPDF} * @memberof jsPDF# * @name compatApi */ API.compatAPI = function(body) { var doSwitch = apiMode === ApiMode.ADVANCED; if (doSwitch) { compatAPI.call(this); } if (typeof body !== "function") { return this; } body(this); if (doSwitch) { advancedAPI.call(this); } return this; }; /** * @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}. * @memberof jsPDF# * @name isAdvancedAPI */ API.isAdvancedAPI = function() { return apiMode === ApiMode.ADVANCED; }; var advancedApiModeTrap = function(methodName) { if (apiMode !== ApiMode.ADVANCED) { throw new Error( methodName + " is only available in 'advanced' API mode. " + "You need to call advancedAPI() first." ); } }; var roundToPrecision = (API.roundToPrecision = API.__private__.roundToPrecision = function( number, parmPrecision ) { var tmpPrecision = precision || parmPrecision; if (isNaN(number) || isNaN(tmpPrecision)) { throw new Error("Invalid argument passed to jsPDF.roundToPrecision"); } return number.toFixed(tmpPrecision).replace(/0+$/, ""); }); // high precision float var hpf; if (typeof floatPrecision === "number") { hpf = API.hpf = API.__private__.hpf = function(number) { if (isNaN(number)) { throw new Error("Invalid argument passed to jsPDF.hpf"); } return roundToPrecision(number, floatPrecision); }; } else if (floatPrecision === "smart") { hpf = API.hpf = API.__private__.hpf = function(number) { if (isNaN(number)) { throw new Error("Invalid argument passed to jsPDF.hpf"); } if (number > -1 && number < 1) { return roundToPrecision(number, 16); } else { return roundToPrecision(number, 5); } }; } else { hpf = API.hpf = API.__private__.hpf = function(number) { if (isNaN(number)) { throw new Error("Invalid argument passed to jsPDF.hpf"); } return roundToPrecision(number, 16); }; } var f2 = (API.f2 = API.__private__.f2 = function(number) { if (isNaN(number)) { throw new Error("Invalid argument passed to jsPDF.f2"); } return roundToPrecision(number, 2); }); var f3 = (API.__private__.f3 = function(number) { if (isNaN(number)) { throw new Error("Invalid argument passed to jsPDF.f3"); } return roundToPrecision(number, 3); }); var scale = (API.scale = API.__private__.scale = function(number) { if (isNaN(number)) { throw new Error("Invalid argument passed to jsPDF.scale"); } if (apiMode === ApiMode.COMPAT) { return number * scaleFactor; } else if (apiMode === ApiMode.ADVANCED) { return number; } }); var transformY = function(y) { if (apiMode === ApiMode.COMPAT) { return getPageHeight() - y; } else if (apiMode === ApiMode.ADVANCED) { return y; } }; var transformScaleY = function(y) { return scale(transformY(y)); }; /** * @name setPrecision * @memberof jsPDF# * @function * @instance * @param {string} precision * @returns {jsPDF} */ API.__private__.setPrecision = API.setPrecision = function(value) { if (typeof parseInt(value, 10) === "number") { precision = parseInt(value, 10); } }; var fileId = "00000000000000000000000000000000"; var getFileId = (API.__private__.getFileId = function() { return fileId; }); var setFileId = (API.__private__.setFileId = function(value) { if (typeof value !== "undefined" && /^[a-fA-F0-9]{32}$/.test(value)) { fileId = value.toUpperCase(); } else { fileId = fileId .split("") .map(function() { return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16)); }) .join(""); } return fileId; }); /** * @name setFileId * @memberof jsPDF# * @function * @instance * @param {string} value GUID. * @returns {jsPDF} */ API.setFileId = function(value) { setFileId(value); return this; }; /** * @name getFileId * @memberof jsPDF# * @function * @instance * * @returns {string} GUID. */ API.getFileId = function() { return getFileId(); }; var creationDate; var convertDateToPDFDate = (API.__private__.convertDateToPDFDate = function( parmDate ) { var result = ""; var tzoffset = parmDate.getTimezoneOffset(), tzsign = tzoffset < 0 ? "+" : "-", tzhour = Math.floor(Math.abs(tzoffset / 60)), tzmin = Math.abs(tzoffset % 60), timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(""); result = [ "D:", parmDate.getFullYear(), padd2(parmDate.getMonth() + 1), padd2(parmDate.getDate()), padd2(parmDate.getHours()), padd2(parmDate.getMinutes()), padd2(parmDate.getSeconds()), timeZoneString ].join(""); return result; }); var convertPDFDateToDate = (API.__private__.convertPDFDateToDate = function( parmPDFDate ) { var year = parseInt(parmPDFDate.substr(2, 4), 10); var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1; var date = parseInt(parmPDFDate.substr(8, 2), 10); var hour = parseInt(parmPDFDate.substr(10, 2), 10); var minutes = parseInt(parmPDFDate.substr(12, 2), 10); var seconds = parseInt(parmPDFDate.substr(14, 2), 10); // var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10); // var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10); var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0); return resultingDate; }); var setCreationDate = (API.__private__.setCreationDate = function(date) { var tmpCreationDateString; var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/; if (typeof date === "undefined") { date = new Date(); } if (date instanceof Date) { tmpCreationDateString = convertDateToPDFDate(date); } else if (regexPDFCreationDate.test(date)) { tmpCreationDateString = date; } else { throw new Error("Invalid argument passed to jsPDF.setCreationDate"); } creationDate = tmpCreationDateString; return creationDate; }); var getCreationDate = (API.__private__.getCreationDate = function(type) { var result = creationDate; if (type === "jsDate") { result = convertPDFDateToDate(creationDate); } return result; }); /** * @name setCreationDate * @memberof jsPDF# * @function * @instance * @param {Object} date * @returns {jsPDF} */ API.setCreationDate = function(date) { setCreationDate(date); return this; }; /** * @name getCreationDate * @memberof jsPDF# * @function * @instance * @param {Object} type * @returns {Object} */ API.getCreationDate = function(type) { return getCreationDate(type); }; var padd2 = (API.__private__.padd2 = function(number) { return ("0" + parseInt(number)).slice(-2); }); var padd2Hex = (API.__private__.padd2Hex = function(hexString) { hexString = hexString.toString(); return ("00" + hexString).substr(hexString.length); }); var objectNumber = 0; // 'n' Current object number var offsets = []; // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. var content = []; var contentLength = 0; var additionalObjects = []; var pages = []; var currentPage; var hasCustomDestination = false; var outputDestination = content; var resetDocument = function() { //reset fields relevant for objectNumber generation and xref. objectNumber = 0; contentLength = 0; content = []; offsets = []; additionalObjects = []; rootDictionaryObjId = newObjectDeferred(); resourceDictionaryObjId = newObjectDeferred(); }; API.__private__.setCustomOutputDestination = function(destination) { hasCustomDestination = true; outputDestination = destination; }; var setOutputDestination = function(destination) { if (!hasCustomDestination) { outputDestination = destination; } }; API.__private__.resetCustomOutputDestination = function() { hasCustomDestination = false; outputDestination = content; }; var out = (API.__private__.out = function(string) { string = string.toString(); contentLength += string.length + 1; outputDestination.push(string); return outputDestination; }); var write = (API.__private__.write = function(value) { return out( arguments.length === 1 ? value.toString() : Array.prototype.join.call(arguments, " ") ); }); var getArrayBuffer = (API.__private__.getArrayBuffer = function(data) { var len = data.length, ab = new ArrayBuffer(len), u8 = new Uint8Array(ab); while (len--) u8[len] = data.charCodeAt(len); return ab; }); var standardFonts = [ ["Helvetica", "helvetica", "normal", "WinAnsiEncoding"], ["Helvetica-Bold", "helvetica", "bold", "WinAnsiEncoding"], ["Helvetica-Oblique", "helvetica", "italic", "WinAnsiEncoding"], ["Helvetica-BoldOblique", "helvetica", "bolditalic", "WinAnsiEncoding"], ["Courier", "courier", "normal", "WinAnsiEncoding"], ["Courier-Bold", "courier", "bold", "WinAnsiEncoding"], ["Courier-Oblique", "courier", "italic", "WinAnsiEncoding"], ["Courier-BoldOblique", "courier", "bolditalic", "WinAnsiEncoding"], ["Times-Roman", "times", "normal", "WinAnsiEncoding"], ["Times-Bold", "times", "bold", "WinAnsiEncoding"], ["Times-Italic", "times", "italic", "WinAnsiEncoding"], ["Times-BoldItalic", "times", "bolditalic", "WinAnsiEncoding"], ["ZapfDingbats", "zapfdingbats", "normal", null], ["Symbol", "symbol", "normal", null] ]; API.__private__.getStandardFonts = function() { return standardFonts; }; var activeFontSize = options.fontSize || 16; /** * Sets font size for upcoming text elements. * * @param {number} size Font size in points. * @function * @instance * @returns {jsPDF} * @memberof jsPDF# * @name setFontSize */ API.__private__.setFontSize = API.setFontSize = function(size) { if (apiMode === ApiMode.ADVANCED) { activeFontSize = size / scaleFactor; } else { activeFontSize = size; } return this; }; /** * Gets the fontsize for upcoming text elements. * * @function * @instance * @returns {number} * @memberof jsPDF# * @name getFontSize */ var getFontSize = (API.__private__.getFontSize = API.getFontSize = function() { if (apiMode === ApiMode.COMPAT) { return activeFontSize; } else { return activeFontSize * scaleFactor; } }); var R2L = options.R2L || false; /** * Set value of R2L functionality. * * @param {boolean} value * @function * @instance * @returns {jsPDF} jsPDF-instance * @memberof jsPDF# * @name setR2L */ API.__private__.setR2L = API.setR2L = function(value) { R2L = value; return this; }; /** * Get value of R2L functionality. * * @function * @instance * @returns {boolean} jsPDF-instance * @memberof jsPDF# * @name getR2L */ API.__private__.getR2L = API.getR2L = function() { return R2L; }; var zoomMode; // default: 1; var setZoomMode = (API.__private__.setZoomMode = function(zoom) { var validZoomModes = [ undefined, null, "fullwidth", "fullheight", "fullpage", "original" ]; if (/^\d*\.?\d*%$/.test(zoom)) { zoomMode = zoom; } else if (!isNaN(zoom)) { zoomMode = parseInt(zoom, 10); } else if (validZoomModes.indexOf(zoom) !== -1) { zoomMode = zoom; } else { throw new Error( 'zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "' + zoom + '" is not recognized.' ); } }); API.__private__.getZoomMode = function() { return zoomMode; }; var pageMode; // default: 'UseOutlines'; var setPageMode = (API.__private__.setPageMode = function(pmode) { var validPageModes = [ undefined, null, "UseNone", "UseOutlines", "UseThumbs", "FullScreen" ]; if (validPageModes.indexOf(pmode) == -1) { throw new Error( 'Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + pmode + '" is not recognized.' ); } pageMode = pmode; }); API.__private__.getPageMode = function() { return pageMode; }; var layoutMode; // default: 'continuous'; var setLayoutMode = (API.__private__.setLayoutMode = function(layout) { var validLayoutModes = [ undefined, null, "continuous", "single", "twoleft", "tworight", "two" ]; if (validLayoutModes.indexOf(layout) == -1) { throw new Error( 'Layout mode must be one of continuous, single, twoleft, tworight. "' + layout + '" is not recognized.' ); } layoutMode = layout; }); API.__private__.getLayoutMode = function() { return layoutMode; }; /** * Set the display mode options of the page like zoom and layout. * * @name setDisplayMode * @memberof jsPDF# * @function * @instance * @param {integer|String} zoom You can pass an integer or percentage as * a string. 2 will scale the document up 2x, '200%' will scale up by the * same amount. You can also set it to 'fullwidth', 'fullheight', * 'fullpage', or 'original'. * * Only certain PDF readers support this, such as Adobe Acrobat. * * @param {string} layout Layout mode can be: 'continuous' - this is the * default continuous scroll. 'single' - the single page mode only shows one * page at a time. 'twoleft' - two column left mode, first page starts on * the left, and 'tworight' - pages are laid out in two columns, with the * first page on the right. This would be used for books. * @param {string} pmode 'UseOutlines' - it shows the * outline of the document on the left. 'UseThumbs' - shows thumbnails along * the left. 'FullScreen' - prompts the user to enter fullscreen mode. * * @returns {jsPDF} */ API.__private__.setDisplayMode = API.setDisplayMode = function( zoom, layout, pmode ) { setZoomMode(zoom); setLayoutMode(layout); setPageMode(pmode); return this; }; var documentProperties = { title: "", subject: "", author: "", keywords: "", creator: "" }; API.__private__.getDocumentProperty = function(key) { if (Object.keys(documentProperties).indexOf(key) === -1) { throw new Error("Invalid argument passed to jsPDF.getDocumentProperty"); } return documentProperties[key]; }; API.__private__.getDocumentProperties = function() { return documentProperties; }; /** * Adds a properties to the PDF document. * * @param {Object} A property_name-to-property_value object structure. * @function * @instance * @returns {jsPDF} * @memberof jsPDF# * @name setDocumentProperties */ API.__private__.setDocumentProperties = API.setProperties = API.setDocumentProperties = function( properties ) { // copying only those properties we can render. for (var property in documentProperties) { if (documentProperties.hasOwnProperty(property) && properties[property]) { documentProperties[property] = properties[property]; } } return this; }; API.__private__.setDocumentProperty = function(key, value) { if (Object.keys(documentProperties).indexOf(key) === -1) { throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty"); } return (documentProperties[key] = value); }; var fonts = {}; // collection of font objects, where key is fontKey - a dynamically created label for a given font. var fontmap = {}; // mapping structure fontName > fontStyle > font key - performance layer. See addFont() var activeFontKey; // will be string representing the KEY of the font as combination of fontName + fontStyle var fontStateStack = []; // var patterns = {}; // collection of pattern objects var patternMap = {}; // see fonts var gStates = {}; // collection of graphic state objects var gStatesMap = {}; // see fonts var activeGState = null; var scaleFactor; // Scale factor var page = 0; var pagesContext = []; var events = new PubSub(API); var hotfixes = options.hotfixes || []; var renderTargets = {}; var renderTargetMap = {}; var renderTargetStack = []; var pageX; var pageY; var pageMatrix; // only used for FormObjects /** * A matrix object for 2D homogenous transformations: <br> * | a b 0 | <br> * | c d 0 | <br> * | e f 1 | <br> * pdf multiplies matrices righthand: v' = v x m1 x m2 x ... * * @class * @name Matrix * @param {number} sx * @param {number} shy * @param {number} shx * @param {number} sy * @param {number} tx * @param {number} ty * @constructor */ var Matrix = function(sx, shy, shx, sy, tx, ty) { if (!(this instanceof Matrix)) { return new Matrix(sx, shy, shx, sy, tx, ty); } if (isNaN(sx)) sx = 1; if (isNaN(shy)) shy = 0; if (isNaN(shx)) shx = 0; if (isNaN(sy)) sy = 1; if (isNaN(tx)) tx = 0; if (isNaN(ty)) ty = 0; this._matrix = [sx, shy, shx, sy, tx, ty]; }; /** * @name sx * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "sx", { get: function() { return this._matrix[0]; }, set: function(value) { this._matrix[0] = value; } }); /** * @name shy * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "shy", { get: function() { return this._matrix[1]; }, set: function(value) { this._matrix[1] = value; } }); /** * @name shx * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "shx", { get: function() { return this._matrix[2]; }, set: function(value) { this._matrix[2] = value; } }); /** * @name sy * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "sy", { get: function() { return this._matrix[3]; }, set: function(value) { this._matrix[3] = value; } }); /** * @name tx * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "tx", { get: function() { return this._matrix[4]; }, set: function(value) { this._matrix[4] = value; } }); /** * @name ty * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "ty", { get: function() { return this._matrix[5]; }, set: function(value) { this._matrix[5] = value; } }); Object.defineProperty(Matrix.prototype, "a", { get: function() { return this._matrix[0]; }, set: function(value) { this._matrix[0] = value; } }); Object.defineProperty(Matrix.prototype, "b", { get: function() { return this._matrix[1]; }, set: function(value) { this._matrix[1] = value; } }); Object.defineProperty(Matrix.prototype, "c", { get: function() { return this._matrix[2]; }, set: function(value) { this._matrix[2] = value; } }); Object.defineProperty(Matrix.prototype, "d", { get: function() { return this._matrix[3]; }, set: function(value) { this._matrix[3] = value; } }); Object.defineProperty(Matrix.prototype, "e", { get: function() { return this._matrix[4]; }, set: function(value) { this._matrix[4] = value; } }); Object.defineProperty(Matrix.prototype, "f", { get: function() { return this._matrix[5]; }, set: function(value) { this._matrix[5] = value; } }); /** * @name rotation * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "rotation", { get: function() { return Math.atan2(this.shx, this.sx); } }); /** * @name scaleX * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "scaleX", { get: function() { return this.decompose().scale.sx; } }); /** * @name scaleY * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "scaleY", { get: function() { return this.decompose().scale.sy; } }); /** * @name isIdentity * @memberof Matrix# */ Object.defineProperty(Matrix.prototype, "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; } }); /** * Join the Matrix Values to a String * * @function join * @param {string} separator Specifies a string to separate each pair of adjacent elements of the array. The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If separator is an empty string, all elements are joined without any characters in between them. * @returns {string} A string with all array elements joined. * @memberof Matrix# */ Matrix.prototype.join = function(separator) { return [this.sx, this.shy, this.shx, this.sy, this.tx, this.ty] .map(hpf) .join(separator); }; /** * Multiply the matrix with given Matrix * * @function multiply * @param matrix * @returns {Matrix} * @memberof Matrix# */ Matrix.prototype.multiply = function(matrix) { var sx = matrix.sx * this.sx + matrix.shy * this.shx; var shy = matrix.sx * this.shy + matrix.shy * this.sy; var shx = matrix.shx * this.sx + matrix.sy * this.shx; var sy = matrix.shx * this.shy + matrix.sy * this.sy; var tx = matrix.tx * this.sx + matrix.ty * this.shx + this.tx; var ty = matrix.tx * this.shy + matrix.ty * this.sy + this.ty; return new Matrix(sx, shy, shx, sy, tx, ty); }; /** * @function decompose * @memberof Matrix# */ Matrix.prototype.decompose = function() { var a = this.sx; var b = this.shy; var c = this.shx; var d = this.sy; var e = this.tx; var f = this.ty; var scaleX = Math.sqrt(a * a + b * b); a /= scaleX; b /= scaleX; var shear = a * c + b * d; c -= a * shear; d -= b * shear; var scaleY = Math.sqrt(c * c + d * d); c /= scaleY; d /= scaleY; shear /= scaleY; if (a * d < b * c) { a = -a; b = -b; shear = -shear; scaleX = -scaleX; } return { scale: new Matrix(scaleX, 0, 0, scaleY, 0, 0), translate: new Matrix(1, 0, 0, 1, e, f), rotate: new Matrix(a, b, -b, a, 0, 0), sk