diff --git a/examples/js/annotations.js b/examples/js/annotations.js new file mode 100644 index 000000000..5914a0b31 --- /dev/null +++ b/examples/js/annotations.js @@ -0,0 +1,21 @@ +var doc = new jsPDF({ + unit: "px", + format: [200, 300], + floatPrecision: 2, +}); + +doc.textWithLink("Click me!", 10, 10, { + url: "https://parall.ax/", +}); +doc.createAnnotation({ + type: "text", + title: "note", + bounds: { + x: 10, + y: 10, + w: 200, + h: 80 + }, + contents: "This is text annotation (closed by default)", + open: false +}); \ No newline at end of file diff --git a/examples/js/editor.js b/examples/js/editor.js index 5187d43fc..872a4230d 100644 --- a/examples/js/editor.js +++ b/examples/js/editor.js @@ -20,10 +20,12 @@ var jsPDFEditor = (function() { "triangles.js": "Triangles", "user-input.js": "User input", "acroforms.js": "AcroForms", + "annotations.js": "Annotations", "autoprint.js": "Auto print", "arabic.js": "Arabic", "russian.js": "Russian", - "japanese.js": "Japanese" + "japanese.js": "Japanese", + "password.js": "Password" }; var aceEditor = function() { diff --git a/examples/js/password.js b/examples/js/password.js new file mode 100644 index 000000000..9147b8b43 --- /dev/null +++ b/examples/js/password.js @@ -0,0 +1,12 @@ +var doc = new jsPDF({ + encryption: { + userPassword: "user", + ownerPassword: "owner", + userPermissions: ["print", "modify", "copy", "annot-forms"] + // try changing the user permissions granted + } +}); + +doc.setFontSize(40); +doc.text("Octonyan loves jsPDF", 35, 25); +doc.addImage("examples/images/Octonyan.jpg", "JPEG", 15, 40, 180, 180); diff --git a/modules.conf.js b/modules.conf.js index 57ea8031c..b64dda56e 100644 --- a/modules.conf.js +++ b/modules.conf.js @@ -1,388 +1,322 @@ var configuration = { - - 'license': { - name: 'License', - folder: '', - description: 'MIT License Header', - deps: [] - }, - - - 'polyfill': { - name: 'Polyfill', - folder: 'libs', - description: 'Adds missing functions to older browsers', - deps: [] - }, - - 'jspdf': { - name: 'Core', - folder: '', - description: '', - deps: [ - 'license', - 'adler32cs', - 'rgbcolor' - ] - }, - - 'adler32cs': { - name: 'Adler32', - folder: 'libs', - description: 'Adler32', - deps: [] - }, - - 'rgbcolor': { - name: 'rgbcolor', - folder: 'libs', - description: 'RGBcolor', - deps: [] - }, - - 'standard_fonts_metrics': { - name: 'Standard Font Metrics Plugin', - folder: 'modules', - description: 'Adds the Font metrics of the 14 Standard Fonts', - deps: [ - 'jspdf' - ] - }, - - 'split_text_to_size': { - name: 'Split text to size Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'standard_fonts_metrics' - ] - }, - - 'acroform': { - name: 'AcroForm Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'annotations' - ] - }, - - 'addimage': { - name: 'AddImage Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'jpeg_support': { - name: 'JPEG Support', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'modules/addimage', - 'libs/JPEGEncoder' - ] - }, - - 'JPEGEncoder': { - name: 'JPEG Encoder', - folder: 'libs', - description: '', - deps: [] - }, - - 'bmp_support': { - name: 'BMP Support', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'jpeg_support', - 'BMPDecoder' - ] - }, - - 'BMPDecoder': { - name: 'BMP Encoder', - folder: 'libs', - description: '', - deps: [] - }, - - 'png_support': { - name: 'PNG Support', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'jpeg_support', - 'Deflater', - 'png', - 'zlib' - ] - }, - - 'png': { - name: 'PNG Encoder', - folder: 'libs', - description: '', - deps: [] - }, - 'zlib': { - name: 'zlib', - folder: 'libs', - description: '', - deps: [] - }, - 'Deflater': { - name: 'Deflater', - folder: 'libs', - description: '', - deps: [] - }, - 'gif_support': { - name: 'Gif Support', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'jpeg_support', - 'omggif' - ] - }, - 'omggif': { - name: 'omggif', - folder: 'libs', - description: '', - deps: [] - }, - - 'webp_support': { - name: 'WebP Support', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'jpeg_support', - 'WebPDecoder' - ] - }, - 'WebPDecoder': { - name: 'WebPDecoder', - folder: 'libs', - description: '', - deps: [] - }, - - 'annotations': { - name: 'Annotations Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'standard_fonts_metrics', - 'split_text_to_size' - ] - }, - - 'autoprint': { - name: 'AutoPrint Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'cell': { - name: 'cell Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'filters': { - name: 'Filter Plugin', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'fileloading': { - name: 'FileLoading Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'outline': { - name: 'Outline Plugin', - folder: 'modules', - deps: [ - 'jspdf' - ] - }, - - 'javascript': { - name: 'Javascript Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - 'canvas': { - name: 'Canvas Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'context2d': { - name: 'Context2D Plugin', - folder: 'modules', - deps: [ - 'jspdf', - 'canvas', - 'addimage', - 'standard_fonts_metrics', - 'split_text_to_size', - 'rgbcolor' - ] - }, - - 'total_pages': { - name: 'TotalPages Plugin', - folder: 'modules', - deps: [ - 'jspdf' - ] - }, - - 'setlanguage': { - name: 'Language Tag Plugin', - folder: 'modules', - deps: [ - 'jspdf' - ] - }, - - 'svg': { - name: 'SVG Plugin', - folder: 'modules', - deps: [ - 'jspdf' - ] - }, - - 'viewerpreferences': { - name: 'ViewerPreferences Plugin', - folder: 'modules', - deps: [ - 'jspdf' - ] - }, - - 'html': { - name: 'HTML Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'context2d', - 'annotations', - ] - }, - - 'ttfsupport': { - name: 'TTFFont Support', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'vfs', - 'ttffont' - ] - }, - 'ttffont': { - name: 'TTFFont Class', - folder: 'libs', - description: '', - deps: [] - }, - - 'modules/utf8': { - name: 'UTF8 Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf', - 'ttfsupport', - 'bidiEngine' - ] - }, - - 'bidiEngine': { - name: 'BiDiEngine', - folder: 'libs', - description: '', - deps: [] - }, - - 'arabic': { - name: 'Arabic Plugin', - folder: 'modules', - description: '', - deps: [ - 'utf8' - ] - }, - - 'vfs': { - name: 'virtual FileSystem Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'xmp_metadata': { - name: 'XMP Metadata Plugin', - folder: 'modules', - description: '', - deps: [ - 'jspdf' - ] - }, - - 'FileSaver': { - name: 'FileSaver', - folder: 'libs', - description: '', - deps: [ - 'Blob' - ] - }, - - 'Blob': { - name: 'Blob', - folder: 'libs', - description: '', - deps: [] - } -} - -module.exports = configuration; \ No newline at end of file + license: { + name: "License", + folder: "", + description: "MIT License Header", + deps: [] + }, + + polyfill: { + name: "Polyfill", + folder: "libs", + description: "Adds missing functions to older browsers", + deps: [] + }, + + jspdf: { + name: "Core", + folder: "", + description: "", + deps: ["license", "adler32cs", "rgbcolor"] + }, + + adler32cs: { + name: "Adler32", + folder: "libs", + description: "Adler32", + deps: [] + }, + + rgbcolor: { + name: "rgbcolor", + folder: "libs", + description: "RGBcolor", + deps: [] + }, + + standard_fonts_metrics: { + name: "Standard Font Metrics Plugin", + folder: "modules", + description: "Adds the Font metrics of the 14 Standard Fonts", + deps: ["jspdf"] + }, + + split_text_to_size: { + name: "Split text to size Plugin", + folder: "modules", + description: "", + deps: ["jspdf", "standard_fonts_metrics"] + }, + + acroform: { + name: "AcroForm Plugin", + folder: "modules", + description: "", + deps: ["jspdf", "annotations"] + }, + + addimage: { + name: "AddImage Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + + jpeg_support: { + name: "JPEG Support", + folder: "modules", + description: "", + deps: ["jspdf", "modules/addimage", "libs/JPEGEncoder"] + }, + + JPEGEncoder: { + name: "JPEG Encoder", + folder: "libs", + description: "", + deps: [] + }, + + bmp_support: { + name: "BMP Support", + folder: "modules", + description: "", + deps: ["jspdf", "jpeg_support", "BMPDecoder"] + }, + + BMPDecoder: { + name: "BMP Encoder", + folder: "libs", + description: "", + deps: [] + }, + + png_support: { + name: "PNG Support", + folder: "modules", + description: "", + deps: ["jspdf", "jpeg_support", "Deflater", "png", "zlib"] + }, + + png: { + name: "PNG Encoder", + folder: "libs", + description: "", + deps: [] + }, + zlib: { + name: "zlib", + folder: "libs", + description: "", + deps: [] + }, + Deflater: { + name: "Deflater", + folder: "libs", + description: "", + deps: [] + }, + gif_support: { + name: "Gif Support", + folder: "modules", + description: "", + deps: ["jspdf", "jpeg_support", "omggif"] + }, + omggif: { + name: "omggif", + folder: "libs", + description: "", + deps: [] + }, + + webp_support: { + name: "WebP Support", + folder: "modules", + description: "", + deps: ["jspdf", "jpeg_support", "WebPDecoder"] + }, + WebPDecoder: { + name: "WebPDecoder", + folder: "libs", + description: "", + deps: [] + }, + + annotations: { + name: "Annotations Plugin", + folder: "modules", + description: "", + deps: ["jspdf", "standard_fonts_metrics", "split_text_to_size"] + }, + + autoprint: { + name: "AutoPrint Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + + cell: { + name: "cell Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + + filters: { + name: "Filter Plugin", + description: "", + deps: ["jspdf"] + }, + + fileloading: { + name: "FileLoading Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + + outline: { + name: "Outline Plugin", + folder: "modules", + deps: ["jspdf"] + }, + + javascript: { + name: "Javascript Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + canvas: { + name: "Canvas Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + + context2d: { + name: "Context2D Plugin", + folder: "modules", + deps: [ + "jspdf", + "canvas", + "addimage", + "standard_fonts_metrics", + "split_text_to_size", + "rgbcolor" + ] + }, + + total_pages: { + name: "TotalPages Plugin", + folder: "modules", + deps: ["jspdf"] + }, + + setlanguage: { + name: "Language Tag Plugin", + folder: "modules", + deps: ["jspdf"] + }, + + svg: { + name: "SVG Plugin", + folder: "modules", + deps: ["jspdf"] + }, + + viewerpreferences: { + name: "ViewerPreferences Plugin", + folder: "modules", + deps: ["jspdf"] + }, + + html: { + name: "HTML Plugin", + folder: "modules", + description: "", + deps: ["jspdf", "context2d", "annotations"] + }, + + ttfsupport: { + name: "TTFFont Support", + folder: "modules", + description: "", + deps: ["jspdf", "vfs", "ttffont"] + }, + ttffont: { + name: "TTFFont Class", + folder: "libs", + description: "", + deps: [] + }, + + "modules/utf8": { + name: "UTF8 Plugin", + folder: "modules", + description: "", + deps: ["jspdf", "ttfsupport", "bidiEngine"] + }, + + bidiEngine: { + name: "BiDiEngine", + folder: "libs", + description: "", + deps: [] + }, + + arabic: { + name: "Arabic Plugin", + folder: "modules", + description: "", + deps: ["utf8"] + }, + + vfs: { + name: "virtual FileSystem Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + + xmp_metadata: { + name: "XMP Metadata Plugin", + folder: "modules", + description: "", + deps: ["jspdf"] + }, + + FileSaver: { + name: "FileSaver", + folder: "libs", + description: "", + deps: ["Blob"] + }, + + Blob: { + name: "Blob", + folder: "libs", + description: "", + deps: [] + }, + md5: { + name: "md5", + folder: "libs", + description: "Implementation of MD5 hashing", + deps: [] + }, + rc4: { + name: "rc4", + folder: "libs", + description: "Implementation of RC4 encryption", + deps: [] + }, + pdfsecurity: { + name: "pdfsecurity", + folder: "libs", + description: "", + deps: ["md5", "rc4"] + } +}; + +module.exports = configuration; diff --git a/src/jspdf.js b/src/jspdf.js index a111c5995..de3a7bf3c 100644 --- a/src/jspdf.js +++ b/src/jspdf.js @@ -7,6 +7,7 @@ import { globalObject } from "./libs/globalObject.js"; import { RGBColor } from "./libs/rgbcolor.js"; import { btoa } from "./libs/AtobBtoa.js"; import { console } from "./libs/console.js"; +import { PDFSecurity } from "./libs/pdfsecurity.js"; /** * jsPDF's Internal PubSub Implementation. @@ -186,6 +187,10 @@ function TilingPattern(boundingBox, xStep, yStep, gState, matrix) { * @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 @@ -211,6 +216,7 @@ function jsPDF(options) { var precision; var floatPrecision = 16; var defaultPathOperation = "S"; + var encryptionOptions = null; options = options || {}; @@ -219,6 +225,13 @@ function jsPDF(options) { 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") { @@ -546,6 +559,15 @@ function jsPDF(options) { }) .join(""); } + + if (encryptionOptions !== null) { + encryption = new PDFSecurity( + encryptionOptions.userPermissions, + encryptionOptions.userPassword, + encryptionOptions.ownerPassword, + fileId + ); + } return fileId; }); @@ -1710,6 +1732,18 @@ function jsPDF(options) { var alreadyAppliedFilters = options.alreadyAppliedFilters || []; var addLength1 = options.addLength1 || false; var valueOfLength1 = data.length; + var objectId = options.objectId; + var encryptor = function(data) { + return data; + }; + if (encryptionOptions !== null && typeof objectId == "undefined") { + throw new Error( + "ObjectId must be passed to putStream for file encryption" + ); + } + if (encryptionOptions !== null) { + encryptor = encryption.encryptor(objectId, 0); + } var processedData = {}; if (filters === true) { @@ -1778,7 +1812,7 @@ function jsPDF(options) { out(">>"); if (processedData.data.length !== 0) { out("stream"); - out(processedData.data); + out(encryptor(processedData.data)); out("endstream"); } }); @@ -1884,7 +1918,8 @@ function jsPDF(options) { newObjectDeferredBegin(pageContentsObjId, true); putStream({ data: pageContent, - filters: getFilters() + filters: getFilters(), + objectId: pageContentsObjId }); out("endobj"); return pageObjectNumber; @@ -1933,7 +1968,7 @@ function jsPDF(options) { var putFont = function(font) { var pdfEscapeWithNeededParanthesis = function(text, flags) { - var addParanthesis = text.indexOf(" ") !== -1; + var addParanthesis = text.indexOf(" ") !== -1; // no space in string return addParanthesis ? "(" + pdfEscape(text, flags) + ")" : pdfEscape(text, flags); @@ -2003,7 +2038,8 @@ function jsPDF(options) { var stream = xObject.pages[1].join("\n"); putStream({ data: stream, - additionalKeyValues: options + additionalKeyValues: options, + objectId: xObject.objectNumber }); out("endobj"); }; @@ -2084,7 +2120,8 @@ function jsPDF(options) { putStream({ data: stream, additionalKeyValues: options, - alreadyAppliedFilters: ["/ASCIIHexDecode"] + alreadyAppliedFilters: ["/ASCIIHexDecode"], + objectId: funcObjectNumber }); out("endobj"); @@ -2157,7 +2194,8 @@ function jsPDF(options) { putStream({ data: pattern.stream, - additionalKeyValues: options + additionalKeyValues: options, + objectId: pattern.objectNumber }); out("endobj"); }; @@ -2223,6 +2261,19 @@ function jsPDF(options) { out(">>"); }; + var putEncryptionDict = function() { + encryption.oid = newObject(); + out("<<"); + out("/Filter /Standard"); + out("/V " + encryption.v); + out("/R " + encryption.r); + out("/U <" + encryption.toHexString(encryption.U) + ">"); + out("/O <" + encryption.toHexString(encryption.O) + ">"); + out("/P " + encryption.P); + out(">>"); + out("endobj"); + }; + var putFontDict = function() { out("/Font <<"); @@ -2772,9 +2823,15 @@ function jsPDF(options) { }; var putInfo = (API.__private__.putInfo = function() { - newObject(); + var objectId = newObject(); + var encryptor = function(data) { + return data; + }; + if (encryptionOptions !== null) { + encryptor = encryption.encryptor(objectId, 0); + } out("<<"); - out("/Producer (jsPDF " + jsPDF.version + ")"); + out("/Producer (" + pdfEscape(encryptor("jsPDF " + jsPDF.version)) + ")"); for (var key in documentProperties) { if (documentProperties.hasOwnProperty(key) && documentProperties[key]) { out( @@ -2782,12 +2839,12 @@ function jsPDF(options) { key.substr(0, 1).toUpperCase() + key.substr(1) + " (" + - pdfEscape(documentProperties[key]) + + pdfEscape(encryptor(documentProperties[key])) + ")" ); } } - out("/CreationDate (" + creationDate + ")"); + out("/CreationDate (" + pdfEscape(encryptor(creationDate)) + ")"); out(">>"); out("endobj"); }); @@ -2858,8 +2915,12 @@ function jsPDF(options) { out("trailer"); out("<<"); out("/Size " + (objectNumber + 1)); + // Root and Info must be the last and second last objects written respectively out("/Root " + objectNumber + " 0 R"); out("/Info " + (objectNumber - 1) + " 0 R"); + if (encryptionOptions !== null) { + out("/Encrypt " + encryption.oid + " 0 R"); + } out("/ID [ <" + fileId + "> <" + fileId + "> ]"); out(">>"); }); @@ -2899,6 +2960,7 @@ function jsPDF(options) { putPages(); putAdditionalObjects(); putResources(); + if (encryptionOptions !== null) putEncryptionDict(); putInfo(); putCatalog(); @@ -3028,7 +3090,9 @@ function jsPDF(options) { "" + '