diff --git a/examples/js/acroforms.js b/examples/js/acroforms.js index b4819172e..9e0a0477d 100644 --- a/examples/js/acroforms.js +++ b/examples/js/acroforms.js @@ -12,68 +12,135 @@ var { } = jsPDF.AcroForm; doc.setFontSize(12); -doc.text("ComboBox:", 10, 105); - -var comboBox = new ComboBox(); -comboBox.fieldName = "ChoiceField1"; -comboBox.topIndex = 1; -comboBox.Rect = [50, 100, 30, 10]; -comboBox.setOptions(["a", "b", "c"]); -comboBox.value = "b"; -comboBox.defaultValue = "b"; -doc.addField(comboBox); - -doc.text("ListBox:", 10, 115); -var listbox = new ListBox(); -listbox.edit = false; -listbox.fieldName = "ChoiceField2"; -listbox.topIndex = 2; -listbox.Rect = [50, 110, 30, 10]; -listbox.setOptions(["c", "a", "d", "f", "b", "s"]); -listbox.value = "s"; -doc.addField(listbox); - -doc.text("CheckBox:", 10, 125); -var checkBox = new CheckBox(); -checkBox.fieldName = "CheckBox1"; -checkBox.Rect = [50, 120, 30, 10]; -doc.addField(checkBox); - -doc.text("PushButton:", 10, 135); -var pushButton = new PushButton(); -pushButton.fieldName = "PushButton1"; -pushButton.Rect = [50, 130, 30, 10]; -doc.addField(pushButton); - -doc.text("TextField:", 10, 145); -var textField = new TextField(); -textField.Rect = [50, 140, 30, 10]; -textField.multiline = true; -textField.value = - "The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse"; // -textField.fieldName = "TestTextBox"; -doc.addField(textField); - -doc.text("Password:", 10, 155); -var passwordField = new PasswordField(); -passwordField.Rect = [50, 150, 30, 10]; -doc.addField(passwordField); - -doc.text("RadioGroup:", 50, 165); -var radioGroup = new RadioButton(); -radioGroup.value = "Test"; -radioGroup.Subtype = "Form"; - -doc.addField(radioGroup); - -var radioButton1 = radioGroup.createOption("Test"); -radioButton1.Rect = [50, 170, 30, 10]; -radioButton1.AS = "/Test"; - -var radioButton2 = radioGroup.createOption("Test2"); -radioButton2.Rect = [50, 180, 30, 10]; - -var radioButton3 = radioGroup.createOption("Test3"); -radioButton3.Rect = [50, 190, 20, 10]; - -radioGroup.setAppearance(Appearance.RadioButton.Cross); +var margin = 12; +let yPos = 20; + +addComboBox(); +addListBox(); +addCheckBox(); +addPushButton(); +addTextField(); +addPasswordField(); +addRadioGroups(); + +function addComboBox() { + doc.text("ComboBox:", 10, yPos); + var comboBox = new ComboBox(); + comboBox.fieldName = "ComboBox1"; + comboBox.topIndex = 1; + comboBox.Rect = [50, yPos - 5, 30, 10]; + comboBox.setOptions(["a", "b", "c"]); + comboBox.value = "b"; + comboBox.defaultValue = "b"; + comboBox.borderColor = [0]; // black + doc.addField(comboBox); + yPos += margin; +} + +function addListBox() { + doc.text("ListBox:", 10, yPos); + var listbox = new ListBox(); + listbox.edit = false; + listbox.fieldName = "ListBox1"; + listbox.topIndex = 2; + listbox.Rect = [50, yPos - 5, 30, 10]; + listbox.setOptions(["c", "a", "d", "f", "b", "s"]); + listbox.value = "s"; + listbox.borderColor = [0]; + doc.addField(listbox); + yPos += margin; +} + +function addCheckBox() { + doc.text("CheckBox:", 10, yPos); + var checkBox = new CheckBox(); + checkBox.fieldName = "CheckBox1"; + checkBox.Rect = [50, yPos - 5, 30, 10]; + checkBox.borderColor = [0]; + doc.addField(checkBox); + yPos += margin; +} + +function addPushButton() { + doc.text("PushButton:", 10, yPos); + var pushButton = new PushButton(); + pushButton.fieldName = "PushButton1"; + pushButton.Rect = [50, yPos - 5, 30, 10]; + pushButton.caption = "OK"; + pushButton.borderColor = [0]; + doc.addField(pushButton); + yPos += margin; +} + +function addTextField() { + doc.text("TextField:", 10, yPos); + var textField = new TextField(); + textField.Rect = [50, yPos - 5, 40, 10]; + textField.multiline = true; + textField.value = + "The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse"; + textField.fieldName = "TestTextBox"; + textField.borderColor = [0]; + doc.addField(textField); + yPos += margin; +} + +function addPasswordField() { + doc.text("Password:", 10, yPos); + var passwordField = new PasswordField(); + passwordField.Rect = [50, yPos - 5, 40, 10]; + passwordField.borderColor = [0]; + doc.addField(passwordField); + yPos += margin; +} + +function addRadioGroups() { + var boxDim = 10; + doc.text("RadioGroups:", 10, yPos); + + // First radio group + var radioGroup = new RadioButton(); + radioGroup.fieldName = "RadioGroup1"; + radioGroup.borderColor = [0]; + doc.addField(radioGroup); + yPos -= 5; + + var radioButton1 = radioGroup.createOption("RadioGroup1Option1"); + radioButton1.Rect = [50, yPos, boxDim, boxDim]; + + var radioButton2 = radioGroup.createOption("RadioGroup1Option2"); + radioButton2.Rect = [62, yPos, boxDim, boxDim]; + + var radioButton3 = radioGroup.createOption("RadioGroup1Option3"); + radioButton3.Rect = [74, yPos, boxDim, boxDim]; + radioGroup.setAppearance(Appearance.RadioButton.Cross); + yPos += boxDim + 5; + + // Second radio group + var radioGroup2 = new RadioButton("RadioGroup2"); + radioGroup2.value = "RadioGroup2Option3"; + radioGroup2.fieldName = "RadioGroup2"; + + // Will apply to all radio buttons in the group, unless overridden + radioGroup2.borderColor = [0.4, 0.4, 0.4]; // gray + radioGroup2.borderWidth = 2; + doc.addField(radioGroup2); + + var radioButton21 = radioGroup2.createOption("RadioGroup2Option1"); + radioButton21.Rect = [50, yPos, boxDim, boxDim]; + + // override the radioGroup's border settings for this one radio button + radioButton21.borderColor = [0, 1, 0]; // green + radioButton21.backgroundColor = [1, 0, 0]; // red + radioButton21.borderWidth = 3; + radioButton21.borderStyle = "dashed"; + + var radioButton22 = radioGroup2.createOption("RadioGroup2Option2"); + radioButton22.Rect = [62, yPos, boxDim, boxDim]; + + var radioButton23 = radioGroup2.createOption("RadioGroup2Option3"); + radioButton23.Rect = [74, yPos, boxDim, boxDim]; + radioButton23.AS = "/RadioGroup2Option3"; + + radioGroup2.setAppearance(Appearance.RadioButton.Circle); +} diff --git a/package-lock.json b/package-lock.json index d898dc4a4..6ffb3420e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,8 @@ "rollup-plugin-license": "^2.1.0", "rollup-plugin-node-resolve": "5.2.0", "rollup-plugin-preprocess": "0.0.4", - "rollup-plugin-terser": "^6.1.0", + "rollup-plugin-terser": "^7.0.2", + "terser": "^5.37.0", "typescript": "^5.6.2", "yarpm": "^0.2.1" }, @@ -1650,6 +1651,64 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@koa/cors": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz", @@ -3218,9 +3277,9 @@ "dev": true }, "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "node_modules/buffer-xor": { @@ -7063,16 +7122,17 @@ } }, "node_modules/jest-worker": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.1.0.tgz", - "integrity": "sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", "dev": true, "dependencies": { + "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^7.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">= 10.13.0" } }, "node_modules/jest-worker/node_modules/has-flag": { @@ -7085,9 +7145,9 @@ } }, "node_modules/jest-worker/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { "has-flag": "^4.0.0" @@ -10293,16 +10353,16 @@ } }, "node_modules/rollup-plugin-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-6.1.0.tgz", - "integrity": "sha512-4fB3M9nuoWxrwm39habpd4hvrbrde2W2GG4zEGPQg1YITNkM3Tqur5jSuXlWNzbv/2aMLJ+dZJaySc3GCD8oDw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", "dev": true, "dependencies": { - "@babel/code-frame": "^7.8.3", - "jest-worker": "^26.0.0", - "serialize-javascript": "^3.0.0", - "terser": "^4.7.0" + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" }, "peerDependencies": { "rollup": "^2.0.0" @@ -10793,9 +10853,9 @@ } }, "node_modules/serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -11065,9 +11125,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -11580,20 +11640,33 @@ } }, "node_modules/terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", + "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/terser/node_modules/commander": { @@ -11602,15 +11675,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index 9a13e6863..665c9071d 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "rollup-plugin-license": "^2.1.0", "rollup-plugin-node-resolve": "5.2.0", "rollup-plugin-preprocess": "0.0.4", - "rollup-plugin-terser": "^6.1.0", + "rollup-plugin-terser": "^7.0.2", "typescript": "^5.6.2", "yarpm": "^0.2.1" }, diff --git a/src/modules/acroform.js b/src/modules/acroform.js index d440d51f6..66b0c5a67 100644 --- a/src/modules/acroform.js +++ b/src/modules/acroform.js @@ -23,6 +23,7 @@ var pdfEscape = function(value) { .replace(/\(/g, "\\(") .replace(/\)/g, "\\)"); }; + var pdfUnescape = function(value) { return value .replace(/\\\\/g, "\\") @@ -475,6 +476,7 @@ var putForm = function(formObject) { formObject ); }; + /** * Create the Reference to the widgetAnnotation, so that it gets referenced * in the Annot[] int the+ (Requires the Annotation Plugin) @@ -759,6 +761,7 @@ var arrayToPdfArray = (jsPDFAPI.__acroform__.arrayToPdfArray = function( "Invalid argument passed to jsPDF.__acroform__.arrayToPdfArray" ); }); + function getMatches(string, regex, index) { index || (index = 1); // default to the first capturing group var matches = []; @@ -768,6 +771,7 @@ function getMatches(string, regex, index) { } return matches; } + var pdfArrayToStringArray = function(array) { var result = []; if (typeof array === "string") { @@ -938,7 +942,6 @@ var AcroFormXObject = function() { } }); }; - inherit(AcroFormXObject, AcroFormPDFObject); var AcroFormDictionary = function() { @@ -985,7 +988,6 @@ var AcroFormDictionary = function() { } }); }; - inherit(AcroFormDictionary, AcroFormPDFObject); /** @@ -1184,7 +1186,6 @@ var AcroFormField = function() { }); var _T = null; - Object.defineProperty(this, "T", { enumerable: true, configurable: false, @@ -1415,6 +1416,7 @@ var AcroFormField = function() { this.V = value; } }); + Object.defineProperty(this, "V", { enumerable: false, configurable: false, @@ -1671,6 +1673,193 @@ var AcroFormField = function() { } } }); + + var _MK = {}; + Object.defineProperty(this, "_MK", { + enumerable: false, + configurable: false, + get: function() { + return _MK; + }, + set: function(value) { + if (typeof value === "object") { + _MK = value; + } + } + }); + + //PDF 32000-1:2008, page 409, Table 189 + Object.defineProperty(this, "MK", { + enumerable: true, + configurable: false, + get: function() { + if (!Object.keys(_MK).length) { + return undefined; + } + + var result = []; + result.push("<<"); + for (let key in _MK) { + if (typeof _MK[key] === "string") { + result.push( + "/" + key + " " + toPdfString(_MK[key], this.objId, this.scope) + ); + } else if (Array.isArray(_MK[key])) { + result.push( + "/" + key + " " + arrayToPdfArray(_MK[key], this.objId, this.scope) + ); + } + } + + result.push(">>"); + return result.join("\n"); + }, + set: function(value) { + if (typeof value === "object") { + _MK = value; + } + } + }); + + /** + * (Optional) An array of numbers in the range 0.0 to 1.0 specifying the color of the widget annotation’s border. The number of array elements determines the color space in which the color is defined: + * 0 No color; transparent + * 1 DeviceGray + * 3 DeviceRGB + * 4 DeviceCMYK + * @name borderColor + * @memberof AcroFormField# + * @default (no color, transparent) + * @type {array} + */ + Object.defineProperty(this, "borderColor", { + enumerable: true, + configurable: true, + get: function get() { + return this._MK.BC; + }, + set: function set(value) { + if (!Array.isArray(value)) { + return; + } + _MK.BC = value; + } + }); + + /** + * (Optional) An array of numbers in the range 0.0 to 1.0 specifying the color of the widget annotation’s background. The number of array elements determines the color space, as described above for borderColor. + * @name backgroundColor + * @memberof AcroFormField# + * @default (no color, transparent) + * @type {array} + */ + Object.defineProperty(this, "backgroundColor", { + enumerable: true, + configurable: true, + get: function get() { + return _MK.BG; + }, + set: function set(value) { + if (!Array.isArray(value)) { + return; + } + _MK.BG = value; + } + }); + + /* Border Style */ + //PDF 32000-1:2008, page 386, 12.5.4 + var _BS = {}; + Object.defineProperty(this, "_BS", { + enumerable: false, + configurable: false, + get: function() { + return _BS; + }, + set: function(value) { + if (typeof value === "object") { + _BS = value; + } + } + }); + + Object.defineProperty(this, "BS", { + enumerable: true, + configurable: false, + get: function get() { + if (!Object.keys(_BS).length) { + return undefined; + } + + var borderStyles = { + dashed: "/D", + solid: "/l", // lowercase L + beveled: "/B", + inset: "/I", + underline: "/U" + }; + + var result = []; + result.push("<<"); + + if (borderStyles[_BS.borderStyle]) { + result.push(`/S ${borderStyles[_BS.borderStyle]}`); + } + + if (_BS.borderWidth) { + result.push(`/W ${_BS.borderWidth}`); + } + + result.push(">>"); + return result.join("\n"); + }, + set: function set(value) { + if (typeof value === "object") { + _BS = value; + } + } + }); + + /** + * (Optional) The border style: + * - 'solid' = A solid rectangle surrounding the annotation. (default) + * - 'dashed' = A dashed rectangle surrounding the annotation. + * - 'beveled' = A simulated embossed rectangle that appears to be raised above the surface of the page. + * - 'inset' = A simulated engraved rectangle that appears to be recessed below the surface of the page. + * - 'underline' = A single line along the bottom of the annotation rectangle. + * @name borderStyle + * @memberof AcroFormField# + * @default 'solid' + * @type {string} + */ + Object.defineProperty(this, "borderStyle", { + enumerable: true, + configurable: true, + get: function get() { + return _BS.borderStyle; + }, + set: function set(value) { + _BS.borderStyle = value; + } + }); + + /** + * (Optional) The border width in points. If this value is 0, no border shall be drawn. Default value: 1. + * @name borderWidth + * @memberof AcroFormField# + * @default 1 + * @type {number} + */ + Object.defineProperty(this, "borderWidth", { + enumerable: true, + configurable: true, + get: function get() { + return _BS.borderWidth; + }, + set: function set(value) { + _BS.borderWidth = value; + } + }); }; inherit(AcroFormField, AcroFormPDFObject); @@ -2065,34 +2254,6 @@ var AcroFormButton = function() { } }); - var _MK = {}; - Object.defineProperty(this, "MK", { - enumerable: false, - configurable: false, - get: function() { - var encryptor = function(data) { - return data; - }; - if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); - if (Object.keys(_MK).length !== 0) { - var result = []; - result.push("<<"); - var key; - for (key in _MK) { - result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); - } - result.push(">>"); - return result.join("\n"); - } - return undefined; - }, - set: function(value) { - if (typeof value === "object") { - _MK = value; - } - } - }); - /** * From the PDF reference: * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. @@ -2108,11 +2269,11 @@ var AcroFormButton = function() { enumerable: true, configurable: true, get: function() { - return _MK.CA || ""; + return this._MK.CA; }, set: function(value) { if (typeof value === "string") { - _MK.CA = value; + this._MK.CA = value; } } }); @@ -2168,6 +2329,7 @@ var AcroFormRadioButton = function() { AcroFormButton.call(this); this.radio = true; this.pushButton = false; + this.backgroundColor = [1]; // white var _Kids = []; Object.defineProperty(this, "Kids", { @@ -2191,11 +2353,11 @@ inherit(AcroFormRadioButton, AcroFormButton); * The Child class of a RadioButton (the radioGroup) -> The single Buttons * * @class AcroFormChildClass + * @extends AcroFormButton * @extends AcroFormField - * @ignore */ var AcroFormChildClass = function() { - AcroFormField.call(this); + AcroFormButton.call(this); var _parent; Object.defineProperty(this, "Parent", { @@ -2221,94 +2383,16 @@ var AcroFormChildClass = function() { } }); - var _MK = {}; - Object.defineProperty(this, "MK", { - enumerable: false, - configurable: false, - get: function() { - var encryptor = function(data) { - return data; - }; - if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); - var result = []; - result.push("<<"); - var key; - for (key in _MK) { - result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); - } - result.push(">>"); - return result.join("\n"); - }, - set: function(value) { - if (typeof value === "object") { - _MK = value; - } - } - }); - - /** - * From the PDF reference: - * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. - * Unlike the remaining entries listed in this Table which apply only to widget annotations associated with pushbutton fields (see Pushbuttons in 12.7.4.2, "Button Fields"), the CA entry may be used with any type of button field, including check boxes (see Check Boxes in 12.7.4.2, "Button Fields") and radio buttons (Radio Buttons in 12.7.4.2, "Button Fields"). - * - * - '8' = Cross, - * - 'l' = Circle, - * - '' = nothing - * @name AcroFormButton#caption - * @type {string} - */ - Object.defineProperty(this, "caption", { - enumerable: true, - configurable: true, - get: function() { - return _MK.CA || ""; - }, - set: function(value) { - if (typeof value === "string") { - _MK.CA = value; - } - } - }); - - var _AS; - Object.defineProperty(this, "AS", { - enumerable: false, - configurable: false, - get: function() { - return _AS; - }, - set: function(value) { - _AS = value; - } - }); - - /** - * (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) The annotation's appearance state, which selects the applicable appearance stream from an appearance subdictionary (see Section 12.5.5, "Appearance Streams") - * - * @name AcroFormButton#appearanceState - * @type {any} - */ - Object.defineProperty(this, "appearanceState", { - enumerable: true, - configurable: true, - get: function() { - return _AS.substr(1, _AS.length - 1); - }, - set: function(value) { - _AS = "/" + value; - } - }); - this.caption = "l"; this.appearanceState = "Off"; - // todo: set AppearanceType as variable that can be set from the - // outside... - this._AppearanceType = AcroFormAppearance.RadioButton.Circle; + + // todo: set AppearanceType as variable that can be set from the outside... // The Default appearanceType is the Circle + this._AppearanceType = AcroFormAppearance.RadioButton.Circle; this.appearanceStreamContent = this._AppearanceType.createAppearanceStream( this.optionName ); }; -inherit(AcroFormChildClass, AcroFormField); +inherit(AcroFormChildClass, AcroFormButton); AcroFormRadioButton.prototype.setAppearance = function(appearance) { if (!("createAppearanceStream" in appearance && "getCA" in appearance)) { @@ -2332,6 +2416,9 @@ AcroFormRadioButton.prototype.createOption = function(name) { var child = new AcroFormChildClass(); child.Parent = this; child.optionName = name; + child.BS = Object.assign({}, child.Parent._BS); + child.MK = Object.assign({}, child.Parent._MK); + // Add to Parent this.Kids.push(child); @@ -2684,121 +2771,567 @@ var AcroFormAppearance = { createAppearanceStream: function(name) { var appearanceStreamContent = { D: { + // push down Off: AcroFormAppearance.RadioButton.Circle.OffPushDown }, - N: {} + N: { + // normal + Off: AcroFormAppearance.RadioButton.Circle.OffNormal + } }; - appearanceStreamContent.N[name] = - AcroFormAppearance.RadioButton.Circle.YesNormal; + + // On appearanceStreamContent.D[name] = AcroFormAppearance.RadioButton.Circle.YesPushDown; + + appearanceStreamContent.N[name] = + AcroFormAppearance.RadioButton.Circle.YesNormal; + return appearanceStreamContent; }, getCA: function() { return "l"; }, - - YesNormal: function(formObject) { + OffNormal: function(formObject) { var xobj = createFormXObject(formObject); xobj.scope = formObject.scope; var stream = []; - // Make the Radius of the Circle relative to min(height, width) of formObject + var DotRadius = AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; - // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + + // Calculate other radii based on DotRadius + var outerRadius = Number((DotRadius * 2).toFixed(5)); + var borderRadius = Number((DotRadius * 1.89).toFixed(5)); + var shadowRadius = Number((DotRadius * 1.67).toFixed(5)); + + // Calculate Bezier curve control points var c = AcroFormAppearance.internal.Bezier_C; - var DotRadiusBezier = Number((DotRadius * c).toFixed(5)); - /* - * The Following is a Circle created with Bezier-Curves. - */ - stream.push("q"); - stream.push( - "1 0 0 1 " + - f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + - " " + - f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + - " cm" + var outerBezier = Number((outerRadius * c).toFixed(5)); + var borderBezier = Number((borderRadius * c).toFixed(5)); + var shadowBezier = Number((shadowRadius * c).toFixed(5)); + + // Center point calculation + var centerX = f5(AcroFormAppearance.internal.getWidth(formObject) / 2); + var centerY = f5(AcroFormAppearance.internal.getHeight(formObject) / 2); + + // Background circle + var backgroundColor = AcroFormAppearance.getColorStream( + "BG", + formObject ); - stream.push(DotRadius + " 0 m"); + stream.push(backgroundColor || "1 g"); // default to white + + stream.push("q"); + stream.push("1 0 0 1 " + centerX + " " + centerY + " cm"); + stream.push(outerRadius + " 0 m"); stream.push( - DotRadius + + outerRadius + " " + - DotRadiusBezier + + outerBezier + " " + - DotRadiusBezier + + outerBezier + " " + - DotRadius + + outerRadius + " 0 " + - DotRadius + + outerRadius + " c" ); stream.push( "-" + - DotRadiusBezier + + outerBezier + " " + - DotRadius + + outerRadius + " -" + - DotRadius + + outerRadius + " " + - DotRadiusBezier + + outerBezier + " -" + - DotRadius + + outerRadius + " 0 c" ); stream.push( "-" + - DotRadius + + outerRadius + " -" + - DotRadiusBezier + + outerBezier + " -" + - DotRadiusBezier + + outerBezier + " -" + - DotRadius + + outerRadius + " 0 -" + - DotRadius + + outerRadius + " c" ); stream.push( - DotRadiusBezier + + outerBezier + " -" + - DotRadius + + outerRadius + " " + - DotRadius + + outerRadius + " -" + - DotRadiusBezier + + outerBezier + " " + - DotRadius + + outerRadius + " 0 c" ); stream.push("f"); stream.push("Q"); - xobj.stream = stream.join("\n"); - return xobj; - }, - YesPushDown: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - var DotRadius = - AcroFormAppearance.internal.getWidth(formObject) <= + + // Main circle border + var strokeColor = AcroFormAppearance.getColorStream("BC", formObject); + if (strokeColor) { + stream.push(strokeColor); + } + + if (formObject.borderWidth) { + stream.push(`${formObject.borderWidth} w`); + } + + stream.push("q"); + stream.push("1 0 0 1 " + centerX + " " + centerY + " cm"); + stream.push(borderRadius + " 0 m"); + stream.push( + borderRadius + + " " + + borderBezier + + " " + + borderBezier + + " " + + borderRadius + + " 0 " + + borderRadius + + " c" + ); + stream.push( + "-" + + borderBezier + + " " + + borderRadius + + " -" + + borderRadius + + " " + + borderBezier + + " -" + + borderRadius + + " 0 c" + ); + stream.push( + "-" + + borderRadius + + " -" + + borderBezier + + " -" + + borderBezier + + " -" + + borderRadius + + " 0 -" + + borderRadius + + " c" + ); + stream.push( + borderBezier + + " -" + + borderRadius + + " " + + borderRadius + + " -" + + borderBezier + + " " + + borderRadius + + " 0 c" + ); + stream.push("s"); + stream.push("Q"); + + // Bottom-right shadow (darker) + stream.push("0.501953 G"); + stream.push("q"); + stream.push( + "0.7071 0.7071 -0.7071 0.7071 " + centerX + " " + centerY + " cm" + ); + stream.push(shadowRadius + " 0 m"); + stream.push( + shadowRadius + + " " + + shadowBezier + + " " + + shadowBezier + + " " + + shadowRadius + + " 0 " + + shadowRadius + + " c" + ); + stream.push( + "-" + + shadowBezier + + " " + + shadowRadius + + " -" + + shadowRadius + + " " + + shadowBezier + + " -" + + shadowRadius + + " 0 c" + ); + stream.push("S"); + stream.push("Q"); + + // Top-left highlight (lighter) + stream.push("0.75293 G"); + stream.push("q"); + stream.push( + "0.7071 0.7071 -0.7071 0.7071 " + centerX + " " + centerY + " cm" + ); + stream.push("-" + shadowRadius + " 0 m"); + stream.push( + "-" + + shadowRadius + + " -" + + shadowBezier + + " -" + + shadowBezier + + " -" + + shadowRadius + + " 0 -" + + shadowRadius + + " c" + ); + stream.push( + shadowBezier + + " -" + + shadowRadius + + " " + + shadowRadius + + " -" + + shadowBezier + + " " + + shadowRadius + + " 0 c" + ); + stream.push("S"); + stream.push("Q"); + + xobj.stream = stream.join("\n"); + return xobj; + }, + YesNormal: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + + // Make the Radius of the Circle relative to min(height, width) of formObject + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= + AcroFormAppearance.internal.getHeight(formObject) + ? AcroFormAppearance.internal.getWidth(formObject) / 4 + : AcroFormAppearance.internal.getHeight(formObject) / 4; + + // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + + // Calculate all radii based on DotRadius + var outerRadius = Number((DotRadius * 2).toFixed(5)); + var borderRadius = Number((DotRadius * 1.89).toFixed(5)); + var shadowRadius = Number((DotRadius * 1.67).toFixed(5)); + var innerDotRadius = Number((DotRadius * 0.78).toFixed(5)); + + // Calculate Bezier curve control points + var c = AcroFormAppearance.internal.Bezier_C; + var outerBezier = Number((outerRadius * c).toFixed(5)); + var borderBezier = Number((borderRadius * c).toFixed(5)); + var shadowBezier = Number((shadowRadius * c).toFixed(5)); + var innerDotBezier = Number((innerDotRadius * c).toFixed(5)); + + // Center point calculation + var centerX = f5(AcroFormAppearance.internal.getWidth(formObject) / 2); + var centerY = f5(AcroFormAppearance.internal.getHeight(formObject) / 2); + + // Background circle + var backgroundColor = AcroFormAppearance.getColorStream( + "BG", + formObject + ); + stream.push(backgroundColor || "1 g"); // default to white + + stream.push("q"); + stream.push("1 0 0 1 " + centerX + " " + centerY + " cm"); + stream.push(outerRadius + " 0 m"); + stream.push( + outerRadius + + " " + + outerBezier + + " " + + outerBezier + + " " + + outerRadius + + " 0 " + + outerRadius + + " c" + ); + stream.push( + "-" + + outerBezier + + " " + + outerRadius + + " -" + + outerRadius + + " " + + outerBezier + + " -" + + outerRadius + + " 0 c" + ); + stream.push( + "-" + + outerRadius + + " -" + + outerBezier + + " -" + + outerBezier + + " -" + + outerRadius + + " 0 -" + + outerRadius + + " c" + ); + stream.push( + outerBezier + + " -" + + outerRadius + + " " + + outerRadius + + " -" + + outerBezier + + " " + + outerRadius + + " 0 c" + ); + stream.push("f"); + stream.push("Q"); + + // Main circle border + var strokeColor = AcroFormAppearance.getColorStream("BC", formObject); + if (strokeColor) { + stream.push(strokeColor); + } + + if (formObject.borderWidth) { + stream.push(`${formObject.borderWidth} w`); + } + + stream.push("q"); + stream.push("1 0 0 1 " + centerX + " " + centerY + " cm"); + stream.push(borderRadius + " 0 m"); + stream.push( + borderRadius + + " " + + borderBezier + + " " + + borderBezier + + " " + + borderRadius + + " 0 " + + borderRadius + + " c" + ); + stream.push( + "-" + + borderBezier + + " " + + borderRadius + + " -" + + borderRadius + + " " + + borderBezier + + " -" + + borderRadius + + " 0 c" + ); + stream.push( + "-" + + borderRadius + + " -" + + borderBezier + + " -" + + borderBezier + + " -" + + borderRadius + + " 0 -" + + borderRadius + + " c" + ); + stream.push( + borderBezier + + " -" + + borderRadius + + " " + + borderRadius + + " -" + + borderBezier + + " " + + borderRadius + + " 0 c" + ); + stream.push("s"); + stream.push("Q"); + + // Bottom-right shadow + stream.push("0.501953 G"); + stream.push("q"); + stream.push( + "0.7071 0.7071 -0.7071 0.7071 " + centerX + " " + centerY + " cm" + ); + stream.push(shadowRadius + " 0 m"); + stream.push( + shadowRadius + + " " + + shadowBezier + + " " + + shadowBezier + + " " + + shadowRadius + + " 0 " + + shadowRadius + + " c" + ); + stream.push( + "-" + + shadowBezier + + " " + + shadowRadius + + " -" + + shadowRadius + + " " + + shadowBezier + + " -" + + shadowRadius + + " 0 c" + ); + stream.push("S"); + stream.push("Q"); + + // Top-left highlight + stream.push("0.75293 G"); + stream.push("q"); + stream.push( + "0.7071 0.7071 -0.7071 0.7071 " + centerX + " " + centerY + " cm" + ); + stream.push("-" + shadowRadius + " 0 m"); + stream.push( + "-" + + shadowRadius + + " -" + + shadowBezier + + " -" + + shadowBezier + + " -" + + shadowRadius + + " 0 -" + + shadowRadius + + " c" + ); + stream.push( + shadowBezier + + " -" + + shadowRadius + + " " + + shadowRadius + + " -" + + shadowBezier + + " " + + shadowRadius + + " 0 c" + ); + stream.push("S"); + stream.push("Q"); + + // Black center dot + stream.push("0 g"); + stream.push("q"); + stream.push("1 0 0 1 " + centerX + " " + centerY + " cm"); + stream.push(innerDotRadius + " 0 m"); + stream.push( + innerDotRadius + + " " + + innerDotBezier + + " " + + innerDotBezier + + " " + + innerDotRadius + + " 0 " + + innerDotRadius + + " c" + ); + stream.push( + "-" + + innerDotBezier + + " " + + innerDotRadius + + " -" + + innerDotRadius + + " " + + innerDotBezier + + " -" + + innerDotRadius + + " 0 c" + ); + stream.push( + "-" + + innerDotRadius + + " -" + + innerDotBezier + + " -" + + innerDotBezier + + " -" + + innerDotRadius + + " 0 -" + + innerDotRadius + + " c" + ); + stream.push( + innerDotBezier + + " -" + + innerDotRadius + + " " + + innerDotRadius + + " -" + + innerDotBezier + + " " + + innerDotRadius + + " 0 c" + ); + stream.push("f"); + stream.push("Q"); + + xobj.stream = stream.join("\n"); + return xobj; + }, + YesPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; // The Borderpadding... DotRadius = Number((DotRadius * 0.9).toFixed(5)); - // Save results for later use; no need to waste - // processor ticks on doing math var k = Number((DotRadius * 2).toFixed(5)); var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); var dc = Number( (DotRadius * AcroFormAppearance.internal.Bezier_C).toFixed(5) ); - stream.push("0.749023 g"); + // Draw outer circle (outline) stream.push("q"); stream.push( "1 0 0 1 " + @@ -2807,6 +3340,13 @@ var AcroFormAppearance = { f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + " cm" ); + var strokeColor = AcroFormAppearance.getColorStream("BC", formObject); + stream.push(strokeColor || "0 G"); // default to black + + if (formObject.borderWidth) { + stream.push(`${formObject.borderWidth} w`); + } + stream.push(k + " 0 m"); stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); stream.push( @@ -2816,9 +3356,10 @@ var AcroFormAppearance = { "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" ); stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); - stream.push("f"); + stream.push("s"); // Stroke the path stream.push("Q"); - stream.push("0 g"); + + // Draw inner circle for selected state stream.push("q"); stream.push( "1 0 0 1 " + @@ -2895,36 +3436,141 @@ var AcroFormAppearance = { : AcroFormAppearance.internal.getHeight(formObject) / 4; // The Borderpadding... DotRadius = Number((DotRadius * 0.9).toFixed(5)); - // Save results for later use; no need to waste - // processor ticks on doing math - var k = Number((DotRadius * 2).toFixed(5)); - var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); + // Calculate outer radius for background + var outerRadius = Number((DotRadius * 2).toFixed(5)); + var centerX = f5(AcroFormAppearance.internal.getWidth(formObject) / 2); + var centerY = f5(AcroFormAppearance.internal.getHeight(formObject) / 2); + var outerBezier = Number( + (outerRadius * AcroFormAppearance.internal.Bezier_C).toFixed(5) + ); + + // Gray background with calculated values stream.push("0.749023 g"); stream.push("q"); + stream.push("1 0 0 1 " + centerX + " " + centerY + " cm"); + stream.push(outerRadius + " 0 m"); stream.push( - "1 0 0 1 " + - f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + outerRadius + " " + - f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + - " cm" + outerBezier + + " " + + outerBezier + + " " + + outerRadius + + " 0 " + + outerRadius + + " c" ); - stream.push(k + " 0 m"); - stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); stream.push( - "-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c" + "-" + + outerBezier + + " " + + outerRadius + + " -" + + outerRadius + + " " + + outerBezier + + " -" + + outerRadius + + " 0 c" ); stream.push( - "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" + "-" + + outerRadius + + " -" + + outerBezier + + " -" + + outerBezier + + " -" + + outerRadius + + " 0 -" + + outerRadius + + " c" + ); + stream.push( + outerBezier + + " -" + + outerRadius + + " " + + outerRadius + + " -" + + outerBezier + + " " + + outerRadius + + " 0 c" ); - stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); stream.push("f"); stream.push("Q"); + + // Draw outer circle (outline) + stream.push("q"); + stream.push("1 0 0 1 " + centerX + " " + centerY + " cm"); + var strokeColor = AcroFormAppearance.getColorStream("BC", formObject); + stream.push(strokeColor || "0 G"); // default to black + + if (formObject.borderWidth) { + stream.push(`${formObject.borderWidth} w`); + } + + stream.push(outerRadius + " 0 m"); + stream.push( + outerRadius + + " " + + outerBezier + + " " + + outerBezier + + " " + + outerRadius + + " 0 " + + outerRadius + + " c" + ); + stream.push( + "-" + + outerBezier + + " " + + outerRadius + + " -" + + outerRadius + + " " + + outerBezier + + " -" + + outerRadius + + " 0 c" + ); + stream.push( + "-" + + outerRadius + + " -" + + outerBezier + + " -" + + outerBezier + + " -" + + outerRadius + + " 0 -" + + outerRadius + + " c" + ); + stream.push( + outerBezier + + " -" + + outerRadius + + " " + + outerRadius + + " -" + + outerBezier + + " " + + outerRadius + + " 0 c" + ); + stream.push("s"); + stream.push("Q"); + xobj.stream = stream.join("\n"); return xobj; } }, - Cross: { /** * Creates the Actual AppearanceDictionary-References @@ -2949,7 +3595,6 @@ var AcroFormAppearance = { getCA: function() { return "8"; }, - YesNormal: function(formObject) { var xobj = createFormXObject(formObject); xobj.scope = formObject.scope; @@ -3044,6 +3689,34 @@ var AcroFormAppearance = { var fontSize = formObject.fontSize; var result = "/" + fontKey + " " + fontSize + " Tf " + encodedColor; return result; + }, + getColorStream: function(bgOrBc, formObject) { + if (!formObject?._MK?.[bgOrBc]) { + return; + } + const colors = formObject._MK[bgOrBc]; + const colorLength = colors.length; + + let colorMode; + if (colorLength === 1) { + colorMode = "g"; // greyscale + } else if (colorLength === 3) { + colorMode = "rg"; // rgb + } else if (colorLength === 4) { + colorMode = "k"; // cmky + } else { + return; // invalid color + } + + if (bgOrBc === "BC") { + // uppercase for stroke color + colorMode = colorMode.toUpperCase(); + } else { + // BG + // keep lowercase for fill color + } + + return `${colors.join(" ")} ${colorMode}`; } }; @@ -3081,6 +3754,7 @@ AcroFormAppearance.internal = { return cross; } }; + AcroFormAppearance.internal.getWidth = function(formObject) { var result = 0; if (typeof formObject === "object") { @@ -3088,6 +3762,7 @@ AcroFormAppearance.internal.getWidth = function(formObject) { } return result; }; + AcroFormAppearance.internal.getHeight = function(formObject) { var result = 0; if (typeof formObject === "object") { diff --git a/test/reference/encrypted_printable.pdf b/test/reference/encrypted_printable.pdf index 0e7a97162..654e08a32 100644 Binary files a/test/reference/encrypted_printable.pdf and b/test/reference/encrypted_printable.pdf differ diff --git a/test/reference/encrypted_standard.pdf b/test/reference/encrypted_standard.pdf index cdc5d3188..ddf191b51 100644 Binary files a/test/reference/encrypted_standard.pdf and b/test/reference/encrypted_standard.pdf differ diff --git a/test/reference/encrypted_withAcroForm.pdf b/test/reference/encrypted_withAcroForm.pdf index 536d36f91..1f40b4655 100644 Binary files a/test/reference/encrypted_withAcroForm.pdf and b/test/reference/encrypted_withAcroForm.pdf differ diff --git a/test/reference/encrypted_withImage.pdf b/test/reference/encrypted_withImage.pdf index bd7865e7e..c21d3f46a 100644 Binary files a/test/reference/encrypted_withImage.pdf and b/test/reference/encrypted_withImage.pdf differ diff --git a/test/reference/pushbutton.pdf b/test/reference/pushbutton.pdf index fb4bf997b..c27880775 100644 Binary files a/test/reference/pushbutton.pdf and b/test/reference/pushbutton.pdf differ diff --git a/test/reference/radiogroup.pdf b/test/reference/radiogroup.pdf index f06979385..54438f19d 100644 Binary files a/test/reference/radiogroup.pdf and b/test/reference/radiogroup.pdf differ diff --git a/test/reference/radiogroup2.pdf b/test/reference/radiogroup2.pdf index b10bb81dd..ee5c3c9b4 100644 Binary files a/test/reference/radiogroup2.pdf and b/test/reference/radiogroup2.pdf differ diff --git a/test/reference/textfieldMultiline.pdf b/test/reference/textfieldMultiline.pdf index a78ceb310..efaa3a537 100644 Binary files a/test/reference/textfieldMultiline.pdf and b/test/reference/textfieldMultiline.pdf differ diff --git a/test/reference/textfieldMultilineSmallForm.pdf b/test/reference/textfieldMultilineSmallForm.pdf index e589d6990..fd2901d48 100644 Binary files a/test/reference/textfieldMultilineSmallForm.pdf and b/test/reference/textfieldMultilineSmallForm.pdf differ diff --git a/test/specs/acroform.spec.js b/test/specs/acroform.spec.js index b7582a826..d9a01c67d 100644 --- a/test/specs/acroform.spec.js +++ b/test/specs/acroform.spec.js @@ -214,7 +214,7 @@ describe("Module: Acroform Unit Test", function() { it("AcroFormField defaultValue", function() { var formObject = new TextField(); - + formObject.defaultValue = "test1"; expect(formObject.defaultValue).toEqual("test1"); expect(formObject.DV).toEqual("(test1)"); @@ -285,6 +285,7 @@ describe("Module: Acroform Unit Test", function() { expect(radioButton1.AS).toEqual("/Test"); }); + it("AcroFormField appearanceState", function() { var doc = new jsPDF({ orientation: "p", @@ -447,7 +448,7 @@ describe("Module: Acroform Unit Test", function() { field = new TextField(); expect(field.Ff).toEqual(0); - field = new TextField(); + field = new TextField(); expect(field.Ff).toEqual(0); expect(field.readOnly).toEqual(false); field.readOnly = true; @@ -537,6 +538,7 @@ describe("Module: Acroform Unit Test", function() { expect(field.richText).toEqual(false); expect(field.Ff).toEqual(0); }); + it("AcroFormComboBox", function() { expect(new ComboBox().combo).toEqual(true); var field = new ComboBox(); @@ -595,6 +597,7 @@ describe("Module: Acroform Unit Test", function() { expect(field.combo).toEqual(true); expect(field.edit).toEqual(true); }); + it("AcroFormButton", function() { expect(new Button().FT).toEqual("/Btn"); @@ -766,16 +769,97 @@ describe("Module: Acroform Unit Test", function() { expect(field.password).toEqual(true); expect(field.Ff).toEqual(Math.pow(2, 13)); }); + it("AcroFormPushButton", function() { expect(new PushButton().pushButton).toEqual(true); expect(new PushButton() instanceof Button).toEqual(true); }); + it("ComboBox TopIndex", function() { var comboBox = new ComboBox(); expect(comboBox.topIndex).toEqual(0); comboBox.topIndex = 1; expect(comboBox.topIndex).toEqual(1); }); + + it("AcroFormField MK", function() { + const textField = new TextField(); + expect(textField.MK).toEqual(undefined); + + expect(textField._MK.CA).toEqual(undefined); + expect(textField.caption).toEqual(undefined); + + textField.backgroundColor = [1, 0, 1]; + textField.borderColor = [1, 0, 0]; + expect(textField.MK).toEqual('<<\n/BG [1 0 1]\n/BC [1 0 0]\n>>'); + expect(textField._MK.BC).toEqual(textField.borderColor); + expect(textField._MK.BG).toEqual(textField.backgroundColor); + }); + + it("AcroFormButton MK", function(){ + var pushButton = new PushButton(); + pushButton.caption = "OK" + expect(pushButton.MK).toEqual('<<\n/CA (OK)\n>>'); + expect(pushButton.caption).toEqual("OK"); + + pushButton.MK = { + BC: [0], + BG: [1], + CA: "Cancel" + } + expect(pushButton.MK).toEqual('<<\n/BC [0]\n/BG [1]\n/CA (Cancel)\n>>'); + expect(pushButton.caption).toEqual("Cancel"); + }); + + it("AcroFormRadioButton MK", function() { + var doc = new jsPDF({}); + var radioGroup = new RadioButton(); + radioGroup.borderColor = [0, 0, 0, 0]; + doc.addField(radioGroup); + + var radioButton1 = radioGroup.createOption("Option1"); + var radioButton2 = radioGroup.createOption("Option2"); + var radioButton3 = radioGroup.createOption("Option3"); + radioButton3.borderColor = [1, 1, 1, 1]; + radioGroup.setAppearance(AcroForm.Appearance.RadioButton.Cross); + + expect(radioButton1.caption).toEqual("8"); + expect(radioButton1.MK).toEqual('<<\n/BG [1]\n/BC [0 0 0 0]\n/CA (8)\n>>'); // BG should be [1] by default for radio buttons + expect(radioButton2.MK).toEqual(radioButton1.MK); + expect(radioButton3.MK).toEqual('<<\n/BG [1]\n/BC [1 1 1 1]\n/CA (8)\n>>'); + + radioGroup.setAppearance(AcroForm.Appearance.RadioButton.Circle); + expect(radioButton1.caption).toEqual("l"); + expect(radioButton1.MK).toEqual('<<\n/BG [1]\n/BC [0 0 0 0]\n/CA (l)\n>>'); + }); + + it("AcroFormField BS", function() { + const textField = new TextField(); + expect(textField.BS).toEqual(undefined); + + textField.borderStyle = "dashed"; + expect(textField.BS).toEqual('<<\n/S /D\n>>'); + textField.borderStyle = "solid"; + expect(textField.BS).toEqual('<<\n/S /l\n>>'); + textField.borderStyle = "beveled"; + expect(textField.BS).toEqual('<<\n/S /B\n>>'); + textField.borderStyle = "inset"; + expect(textField.BS).toEqual('<<\n/S /I\n>>'); + textField.borderStyle = "underline"; + expect(textField.BS).toEqual('<<\n/S /U\n>>'); + + textField.borderWidth = 3; + expect(textField.BS).toEqual('<<\n/S /U\n/W 3\n>>'); + expect(textField._BS).toEqual({ + borderStyle: 'underline', + borderWidth: 3 + }); + expect(textField.borderStyle).toEqual('underline'); + expect(textField.borderWidth).toEqual(3); + + textField.BS = {}; + expect(textField.BS).toEqual(undefined); + }); }); describe("Module: Acroform Integration Test", function() { @@ -911,6 +995,7 @@ describe("Module: Acroform Integration Test", function() { }); doc.text(10, 135, "PushButton:"); var pushButton = new PushButton(); + pushButton.caption = "OK"; pushButton.T = "PushButton1"; pushButton.Rect = [50, 130, 30, 10]; doc.addField(pushButton); @@ -1051,6 +1136,10 @@ describe("Module: Acroform Integration Test", function() { radioGroup.Subtype = "Form"; doc.addField(radioGroup); + radioGroup.borderWidth = 2; + radioGroup.borderStyle = "dashed"; + radioGroup.borderColor = [0.4, 0.4, 0.4]; // gray + radioGroup.backgroundColor = [1]; // white var radioButton1 = radioGroup.createOption("Test"); radioButton1.Rect = [50, 170, 30, 10]; diff --git a/test/specs/encryption.spec.js b/test/specs/encryption.spec.js index 14beb7bba..0b80a9739 100644 --- a/test/specs/encryption.spec.js +++ b/test/specs/encryption.spec.js @@ -20,6 +20,7 @@ describe("Core: Standard Encryption", () => { doc.text(10, 10, "This is a test!"); comparePdf(doc.output(), "encrypted_standard.pdf", "encryption"); }); + it("should be printable", () => { const doc = jsPDF({ floatPrecision: 2, @@ -33,10 +34,13 @@ describe("Core: Standard Encryption", () => { doc.text(10, 10, "This is a test!"); comparePdf(doc.output(), "encrypted_printable.pdf", "encryption"); }); + it("should display forms properly", () => { var doc = new jsPDF({ floatPrecision: 2, - encryption: {} + encryption: { + userPermissions: ["print", "modify", "copy", "annot-forms"] + } }); doc.__private__.setFileId("0000000000000000000000000BADFACE"); doc.__private__.setCreationDate("D:19871210000000+00'00'"); @@ -52,68 +56,135 @@ describe("Core: Standard Encryption", () => { } = jsPDF.AcroForm; doc.setFontSize(12); - doc.text("ComboBox:", 10, 105); - - var comboBox = new ComboBox(); - comboBox.fieldName = "ChoiceField1"; - comboBox.topIndex = 1; - comboBox.Rect = [50, 100, 30, 10]; - comboBox.setOptions(["a", "b", "c"]); - comboBox.value = "b"; - comboBox.defaultValue = "b"; - doc.addField(comboBox); - - doc.text("ListBox:", 10, 115); - var listbox = new ListBox(); - listbox.edit = false; - listbox.fieldName = "ChoiceField2"; - listbox.topIndex = 2; - listbox.Rect = [50, 110, 30, 10]; - listbox.setOptions(["c", "a", "d", "f", "b", "s"]); - listbox.value = "s"; - doc.addField(listbox); - - doc.text("CheckBox:", 10, 125); - var checkBox = new CheckBox(); - checkBox.fieldName = "CheckBox1"; - checkBox.Rect = [50, 120, 30, 10]; - doc.addField(checkBox); - - doc.text("PushButton:", 10, 135); - var pushButton = new PushButton(); - pushButton.fieldName = "PushButton1"; - pushButton.Rect = [50, 130, 30, 10]; - doc.addField(pushButton); - - doc.text("TextField:", 10, 145); - var textField = new TextField(); - textField.Rect = [50, 140, 30, 10]; - textField.multiline = true; - textField.value = - "The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse"; // - textField.fieldName = "TestTextBox"; - doc.addField(textField); - - doc.text("Password:", 10, 155); - var passwordField = new PasswordField(); - passwordField.Rect = [50, 150, 30, 10]; - doc.addField(passwordField); - - doc.text("RadioGroup:", 50, 165); - var radioGroup = new RadioButton(); - radioGroup.value = "Test"; - doc.addField(radioGroup); - var radioButton1 = radioGroup.createOption("Test"); - radioButton1.Rect = [50, 170, 30, 10]; - radioButton1.AS = "/Test"; - var radioButton2 = radioGroup.createOption("Test2"); - radioButton2.Rect = [50, 180, 30, 10]; - var radioButton3 = radioGroup.createOption("Test3"); - radioButton3.Rect = [50, 190, 20, 10]; - radioGroup.setAppearance(Appearance.RadioButton.Cross); + var margin = 12; + let yPos = 20; + addComboBox(); + addListBox(); + addCheckBox(); + addPushButton(); + addTextField(); + addPasswordField(); + addRadioGroups(); + comparePdf(doc.output(), "encrypted_withAcroForm.pdf", "encryption"); + + function addComboBox() { + doc.text("ComboBox:", 10, yPos); + var comboBox = new ComboBox(); + comboBox.fieldName = "ComboBox1"; + comboBox.topIndex = 1; + comboBox.Rect = [50, yPos - 5, 30, 10]; + comboBox.setOptions(["a", "b", "c"]); + comboBox.value = "b"; + comboBox.defaultValue = "b"; + doc.addField(comboBox); + yPos += margin; + } + + function addListBox() { + doc.text("ListBox:", 10, yPos); + var listbox = new ListBox(); + listbox.edit = false; + listbox.fieldName = "ListBox1"; + listbox.topIndex = 2; + listbox.Rect = [50, yPos - 5, 30, 10]; + listbox.setOptions(["c", "a", "d", "f", "b", "s"]); + listbox.value = "s"; + doc.addField(listbox); + yPos += margin; + } + + function addCheckBox() { + doc.text("CheckBox:", 10, yPos); + var checkBox = new CheckBox(); + checkBox.fieldName = "CheckBox1"; + checkBox.Rect = [50, yPos - 5, 30, 10]; + doc.addField(checkBox); + yPos += margin; + } + + function addPushButton() { + doc.text("PushButton:", 10, yPos); + var pushButton = new PushButton(); + pushButton.fieldName = "PushButton1"; + pushButton.Rect = [50, yPos - 5, 30, 10]; + pushButton.caption = "OK"; + doc.addField(pushButton); + yPos += margin; + } + + function addTextField() { + doc.text("TextField:", 10, yPos); + var textField = new TextField(); + textField.Rect = [50, yPos - 5, 40, 10]; + textField.multiline = true; + textField.value = + "The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse The quick brown fox ate the lazy mouse"; + textField.fieldName = "TestTextBox"; + doc.addField(textField); + yPos += margin; + } + + function addPasswordField() { + doc.text("Password:", 10, yPos); + var passwordField = new PasswordField(); + passwordField.Rect = [50, yPos - 5, 40, 10]; + doc.addField(passwordField); + yPos += margin; + } + + function addRadioGroups() { + var boxDim = 10; + doc.text("RadioGroups:", 10, yPos); + + // First radio group + var radioGroup = new RadioButton(); + radioGroup.fieldName = "RadioGroup1"; + doc.addField(radioGroup); + yPos -= 5; + + var radioButton1 = radioGroup.createOption("RadioGroup1Option1"); + radioButton1.Rect = [50, yPos, boxDim, boxDim]; + + var radioButton2 = radioGroup.createOption("RadioGroup1Option2"); + radioButton2.Rect = [62, yPos, boxDim, boxDim]; + + var radioButton3 = radioGroup.createOption("RadioGroup1Option3"); + radioButton3.Rect = [74, yPos, boxDim, boxDim]; + radioGroup.setAppearance(Appearance.RadioButton.Cross); + yPos += boxDim + 5; + + // Second radio group + var radioGroup2 = new RadioButton("RadioGroup2"); + radioGroup2.value = "RadioGroup2Option3"; + radioGroup2.fieldName = "RadioGroup2"; + + // Will apply to all radio buttons in the group, unless overridden + radioGroup2.borderColor = [0.4, 0.4, 0.4]; // gray + radioGroup2.borderWidth = 2; + doc.addField(radioGroup2); + + var radioButton21 = radioGroup2.createOption("RadioGroup2Option1"); + radioButton21.Rect = [50, yPos, boxDim, boxDim]; + + // override the radioGroup's border settings for this one radio button + radioButton21.borderColor = [0, 1, 0]; // green + radioButton21.backgroundColor = [1, 0, 0]; // red + radioButton21.borderWidth = 3; + radioButton21.borderStyle = "dashed"; + + var radioButton22 = radioGroup2.createOption("RadioGroup2Option2"); + radioButton22.Rect = [62, yPos, boxDim, boxDim]; + + var radioButton23 = radioGroup2.createOption("RadioGroup2Option3"); + radioButton23.Rect = [74, yPos, boxDim, boxDim]; + radioButton23.AS = "/RadioGroup2Option3"; + + radioGroup2.setAppearance(Appearance.RadioButton.Circle); + } }); + it("colortype_3_indexed_single_colour_alpha_4_bit_png", () => { var colortype_3_indexed_single_colour_alpha_4_bit_png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEX/////AAD/pQD//wAA/wAAgAAAgIAAAP+BAIC08EFzAAAAAXRSTlMAQObYZgAAAJtJREFUCB0BkABv/wAREQAAAAAAAAAiIhEQAAAAAAAzMyIhEAAAAABERDMyIQAAAABVVUQzIhAAAABmZlVEMyEAAAB3d2ZVQzIQAACIh3dlVDIhAAAACId2VUMhAAAAAAiHZUMyEAAAAACHdlQyEAAAAAAIdlQyEAAAAAAId2VDIQAAAAAAh2VDIQAAAAAAh2VDIQAAAAAAh2VDIWfgFTHZzlYNAAAAAElFTkSuQmCC"; diff --git a/types/index.d.ts b/types/index.d.ts index 8a9553073..679ae1170 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -293,10 +293,16 @@ declare module "jspdf" { defaultValue: string; value: string; hasAnnotation: boolean; + hasAppearanceStream: boolean; + page: number; readOnly: boolean; required: boolean; noExport: boolean; textAlign: "left" | "center" | "right"; + borderColor: number[]; + backgroundColor: number[]; + borderStyle: "dashed" | "solid" | "beveled" | "inset" | "underline"; + borderWidth: number; } export class AcroFormChoiceField {} @@ -337,10 +343,9 @@ declare module "jspdf" { export interface AcroFormPushButton extends AcroFormButton {} export class AcroFormChildClass {} - export interface AcroFormChildClass extends AcroFormField { + export interface AcroFormChildClass extends AcroFormButton { Parent: any; optionName: string; - caption: string; appearanceState: "On" | "Off"; }