UNPKG

jspdf

Version:

PDF Document creation from JavaScript

1,586 lines (1,362 loc) 944 kB
/** @license * * jsPDF - PDF Document creation from JavaScript * Version 3.0.1 Built on 2025-03-17T14:19:36.870Z * 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 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.jspdf = {})); })(this, (function (exports) { 'use strict'; function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 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 }; 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 === "undefined" ? "undefined" : _typeof(window)) !== "object" || window !== globalObject ? function saveAs() { /* noop */ } : // Use download attribute first if possible (#193 Lumia mobile) unless this is a native app typeof HTMLAnchorElement !== "undefined" && "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 === "undefined" ? "undefined" : _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 process(bits) { return [parseInt(bits[1]), parseInt(bits[2]), parseInt(bits[3])]; } }, { re: /^(\w{2})(\w{2})(\w{2})$/, example: ["#00ff00", "336699"], process: function process(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 process(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.bind(globalObject); btoa = globalObject.btoa.bind(globalObject); return; })(); /** * @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 var 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"; var paddedUserPassword = (userPassword + this.padding).substr(0, 32); var 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) { var 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) { var 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; } /** * 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 advancedApiModeTrap(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 transformY(y) { if (apiMode === ApiMode.COMPAT) { return getPageHeight() - y; } else if (apiMode === ApiMode.ADVANCED) { return y; } }; var transformScaleY = function transformScaleY(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(Mat