UNPKG

jspdf

Version:

PDF Document creation from JavaScript

1,678 lines (1,519 loc) 926 kB
/** @license * * jsPDF - PDF Document creation from JavaScript * Version 2.5.1 Built on 2022-01-28T15:37:57.791Z * CommitID 00000000 * * Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF * 2015-2021 yWorks GmbH, http://www.yworks.com * 2015-2021 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, https://github.com/willowsystems * 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 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var fflate = require('fflate'); var globalObject = (function() { return "undefined" !== typeof window ? window : "undefined" !== typeof global ? global : "undefined" !== typeof self ? self : this; })(); /** * 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 = require("atob"); btoa = require("btoa"); })(); 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 * Joseph Myers does not specify a particular license for his work. * * Author: Joseph Myers * Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js * * Modified by: Owen Leong */ function md5cycle(x, k) { var a = x[0], b = x[1], c = x[2], d = x[3]; a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586); c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330); a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426); c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983); a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417); c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162); a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101); c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329); a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632); c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302); a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083); c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848); a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690); c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501); a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784); c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734); a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463); c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556); a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353); c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640); a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222); c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189); a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835); c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651); a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415); c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[5], 21, -57434055); a = ii(a, b, c, d, k[12], 6, 1700485571); d = ii(d, a, b, c, k[3], 10, -1894986606); c = ii(c, d, a, b, k[10], 15, -1051523); b = ii(b, c, d, a, k[1], 21, -2054922799); a = ii(a, b, c, d, k[8], 6, 1873313359); d = ii(d, a, b, c, k[15], 10, -30611744); c = ii(c, d, a, b, k[6], 15, -1560198380); b = ii(b, c, d, a, k[13], 21, 1309151649); a = ii(a, b, c, d, k[4], 6, -145523070); d = ii(d, a, b, c, k[11], 10, -1120210379); c = ii(c, d, a, b, k[2], 15, 718787259); b = ii(b, c, d, a, k[9], 21, -343485551); x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]); } function cmn(q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); } function ff(a, b, c, d, x, s, t) { return cmn((b & c) | (~b & d), a, b, x, s, t); } function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & ~d), a, b, x, s, t); } function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); } function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | ~d), a, b, x, s, t); } function md51(s) { // txt = ''; var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i; for (i = 64; i <= s.length; i += 64) { md5cycle(state, md5blk(s.substring(i - 64, i))); } s = s.substring(i - 64); var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); tail[i >> 2] |= 0x80 << (i % 4 << 3); if (i > 55) { md5cycle(state, tail); for (i = 0; i < 16; i++) tail[i] = 0; } tail[14] = n * 8; md5cycle(state, tail); return state; } /* there needs to be support for Unicode here, * unless we pretend that we can redefine the MD-5 * algorithm for multi-byte characters (perhaps * by adding every four 16-bit characters and * shortening the sum to 32 bits). Otherwise * I suggest performing MD-5 as if every character * was two bytes--e.g., 0040 0025 = @%--but then * how will an ordinary MD-5 sum be matched? * There is no way to standardize text to something * like UTF-8 before transformation; speed cost is * utterly prohibitive. The JavaScript standard * itself needs to look at this: it should start * providing access to strings as preformed UTF-8 * 8-bit unsigned value arrays. */ function md5blk(s) { /* I figured global was faster. */ var md5blks = [], i; /* Andy King said do it this way. */ for (i = 0; i < 64; i += 4) { md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); } return md5blks; } var hex_chr = "0123456789abcdef".split(""); function rhex(n) { var s = "", j = 0; for (; j < 4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f]; return s; } function hex(x) { for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); return x.join(""); } // Converts a 4-byte number to byte string function singleToByteString(n) { return String.fromCharCode( (n & 0xff) >> 0, (n & 0xff00) >> 8, (n & 0xff0000) >> 16, (n & 0xff000000) >> 24 ); } // Converts an array of numbers to a byte string function toByteString(x) { return x.map(singleToByteString).join(""); } // Returns the MD5 hash as a byte string function md5Bin(s) { return toByteString(md51(s)); } // Returns MD5 hash as a hex string function md5(s) { return hex(md51(s)); } var md5Check = md5("hello") != "5d41402abc4b2a76b9719d911017c592"; function add32(a, b) { if (md5Check) { /* if the md5Check does not match the expected value, we're dealing with an old browser and need this function. */ var lsw = (a & 0xffff) + (b & 0xffff), msw = (a >> 16) + (b >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xffff); } else { /* this function is much faster, so if possible we use it. Some IEs are the only ones I know of that need the idiotic second function, generated by an if clause. */ return (a + b) & 0xffffffff; } } /** * @license * FPDF is released under a permissive license: there is no usage restriction. * You may embed it freely in your application (commercial or not), with or * without modifications. * * Reference: http://www.fpdf.org/en/script/script37.php */ function repeat(str, num) { return new Array(num + 1).join(str); } /** * Converts a byte string to a hex string * * @name rc4 * @function * @param {string} key Byte string of encryption key * @param {string} data Byte string of data to be encrypted * @returns {string} Encrypted string */ function rc4(key, data) { var lastKey, lastState; if (key !== lastKey) { var k = repeat(key, ((256 / key.length) >> 0) + 1); var state = []; for (var i = 0; i < 256; i++) { state[i] = i; } var j = 0; for (var i = 0; i < 256; i++) { var t = state[i]; j = (j + t + k.charCodeAt(i)) % 256; state[i] = state[j]; state[j] = t; } lastKey = key; lastState = state; } else { state = lastState; } var length = data.length; var a = 0; var b = 0; var out = ""; for (var i = 0; i < length; i++) { a = (a + 1) % 256; t = state[a]; b = (b + t) % 256; state[a] = state[b]; state[b] = t; k = state[(state[a] + state[b]) % 256]; out += String.fromCharCode(data.charCodeAt(i) ^ k); } return out; } /** * @license * Licensed under the MIT License. * http://opensource.org/licenses/mit-license * Author: Owen Leong (@owenl131) * Date: 15 Oct 2020 * References: * https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt * https://github.com/foliojs/pdfkit/blob/master/lib/security.js * http://www.fpdf.org/en/script/script37.php */ var permissionOptions = { print: 4, modify: 8, copy: 16, "annot-forms": 32 }; /** * Initializes encryption settings * * @name constructor * @function * @param {Array} permissions Permissions allowed for user, "print", "modify", "copy" and "annot-forms". * @param {String} userPassword Permissions apply to this user. Leaving this empty means the document * is not password protected but viewer has the above permissions. * @param {String} ownerPassword Owner has full functionalities to the file. * @param {String} fileId As hex string, should be same as the file ID in the trailer. * @example * var security = new PDFSecurity(["print"]) */ function PDFSecurity(permissions, userPassword, ownerPassword, fileId) { this.v = 1; // algorithm 1, future work can add in more recent encryption schemes this.r = 2; // revision 2 // set flags for what functionalities the user can access let protection = 192; permissions.forEach(function(perm) { if (typeof permissionOptions.perm !== "undefined") { throw new Error("Invalid permission: " + perm); } protection += permissionOptions[perm]; }); // padding is used to pad the passwords to 32 bytes, also is hashed and stored in the final PDF this.padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" + "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A"; let paddedUserPassword = (userPassword + this.padding).substr(0, 32); let paddedOwnerPassword = (ownerPassword + this.padding).substr(0, 32); this.O = this.processOwnerPassword(paddedUserPassword, paddedOwnerPassword); this.P = -((protection ^ 255) + 1); this.encryptionKey = md5Bin( paddedUserPassword + this.O + this.lsbFirstWord(this.P) + this.hexToBytes(fileId) ).substr(0, 5); this.U = rc4(this.encryptionKey, this.padding); } /** * Breaks down a 4-byte number into its individual bytes, with the least significant bit first * * @name lsbFirstWord * @function * @param {number} data 32-bit number * @returns {Array} */ PDFSecurity.prototype.lsbFirstWord = function(data) { return String.fromCharCode( (data >> 0) & 0xff, (data >> 8) & 0xff, (data >> 16) & 0xff, (data >> 24) & 0xff ); }; /** * Converts a byte string to a hex string * * @name toHexString * @function * @param {String} byteString Byte string * @returns {String} */ PDFSecurity.prototype.toHexString = function(byteString) { return byteString .split("") .map(function(byte) { return ("0" + (byte.charCodeAt(0) & 0xff).toString(16)).slice(-2); }) .join(""); }; /** * Converts a hex string to a byte string * * @name hexToBytes * @function * @param {String} hex Hex string * @returns {String} */ PDFSecurity.prototype.hexToBytes = function(hex) { for (var bytes = [], c = 0; c < hex.length; c += 2) bytes.push(String.fromCharCode(parseInt(hex.substr(c, 2), 16))); return bytes.join(""); }; /** * Computes the 'O' field in the encryption dictionary * * @name processOwnerPassword * @function * @param {String} paddedUserPassword Byte string of padded user password * @param {String} paddedOwnerPassword Byte string of padded owner password * @returns {String} */ PDFSecurity.prototype.processOwnerPassword = function( paddedUserPassword, paddedOwnerPassword ) { let key = md5Bin(paddedOwnerPassword).substr(0, 5); return rc4(key, paddedUserPassword); }; /** * Returns an encryptor function which can take in a byte string and returns the encrypted version * * @name encryptor * @function * @param {number} objectId * @param {number} generation Not sure what this is for, you can set it to 0 * @returns {Function} * @example * out("stream"); * encryptor = security.encryptor(object.id, 0); * out(encryptor(data)); * out("endstream"); */ PDFSecurity.prototype.encryptor = function(objectId, generation) { let key = md5Bin( this.encryptionKey + String.fromCharCode( objectId & 0xff, (objectId >> 8) & 0xff, (objectId >> 16) & 0xff, generation & 0xff, (generation >> 8) & 0xff ) ).substr(0, 10); return function(data) { return rc4(key, data); }; }; /** * Convert string to `PDF Name Object`. * Detail: PDF Reference 1.3 - Chapter 3.2.4 Name Object * @param str */ function toPDFName(str) { // eslint-disable-next-line no-control-regex if (/[^\u0000-\u00ff]/.test(str)) { // non ascii string throw new Error( "Invalid PDF Name Object: " + str + ", Only accept ASCII characters." ); } var result = "", strLength = str.length; for (var i = 0; i < strLength; i++) { var charCode = str.charCodeAt(i); if ( charCode < 0x21 || charCode === 0x23 /* # */ || charCode === 0x25 /* % */ || charCode === 0x28 /* ( */ || charCode === 0x29 /* ) */ || charCode === 0x2f /* / */ || charCode === 0x3c /* < */ || charCode === 0x3e /* > */ || charCode === 0x5b /* [ */ || charCode === 0x5d /* ] */ || charCode === 0x7b /* { */ || charCode === 0x7d /* } */ || charCode > 0x7e ) { // Char CharCode hexStr paddingHexStr Result // "\t" 9 9 09 #09 // " " 32 20 20 #20 // "©" 169 a9 a9 #a9 var hexStr = charCode.toString(16), paddingHexStr = ("0" + hexStr).slice(-2); result += "#" + paddingHexStr; } else { // Other ASCII printable characters between 0x21 <= X <= 0x7e result += str[i]; } } return result; } /* 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", "in", "px", "pc", "em" or "ex". Note that in order to get the correct scaling for "px" * units, you need to enable the hotfix "px_scaling" by setting options.hotfixes = ["px_scaling"]. * @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 {string[]} [options.hotfixes] An array of strings to enable hotfixes such as correct pixel scaling. * @param {Object} [options.encryption] * @param {string} [options.encryption.userPassword] Password for the user bound by the given permissions list. * @param {string} [options.encryption.ownerPassword] Both userPassword and ownerPassword should be set for proper authentication. * @param {string[]} [options.encryption.userPermissions] Array of permissions "print", "modify", "copy", "annot-forms", accessible by the user. * @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"; var encryptionOptions = null; options = options || {}; if (typeof options === "object") { orientation = options.orientation; unit = options.unit || unit; format = options.format || format; compressPdf = options.compress || options.compressPdf || compressPdf; encryptionOptions = options.encryption || null; if (encryptionOptions !== null) { encryptionOptions.userPassword = encryptionOptions.userPassword || ""; encryptionOptions.ownerPassword = encryptionOptions.ownerPassword || ""; encryptionOptions.userPermissions = encryptionOptions.userPermissions || []; } 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; } /** * @function combineFontStyleAndFontWeight * @param {string} fontStyle Fontstyle or variant. Example: "italic". * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400 * @returns {string} * @private */ var combineFontStyleAndFontWeight = (API.__private__.combineFontStyleAndFontWeight = function( fontStyle, fontWeight ) { if ( (fontStyle == "bold" && fontWeight == "normal") || (fontStyle == "bold" && fontWeight == 400) || (fontStyle == "normal" && fontWeight == "italic") || (fontStyle == "bold" && fontWeight == "italic") ) { throw new Error("Invalid Combination of fontweight and fontstyle"); } if (fontWeight) { fontStyle = fontWeight == 400 || fontWeight === "normal" ? fontStyle === "italic" ? "italic" : "normal" : (fontWeight == 700 || fontWeight === "bold") && fontStyle === "normal" ? "bold" : (fontWeight == 700 ? "bold" : fontWeight) + "" + fontStyle; } return fontStyle; }); /** * @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(""); } if (encryptionOptions !== null) { encryption = new PDFSecurity( encryptionOptions.userPermissions, encryptionOptions.userPassword, encryptionOptions.ownerPassword, fileId ); } 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; /**