From 93d7a40593909878f253561e1e4138485520254d Mon Sep 17 00:00:00 2001 From: Joshua Newell <3116028+imjosh@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:43:13 -0500 Subject: [PATCH 1/4] Update AcroForms module - Stylize circular radio buttons - Implement MK and BS properties to be able to set form fields' border/background colors and border widths - Fix AcroFormChildClass should inherit from AcroFormButton, not AcroFormField - Remove redundant sub-class properties which are inherited - Update AcroForms example to use new properties - Update index.d.ts types - Add unit tests, modify integration tests - Upgrade "rollup-plugin-terser" from 6.1.0 to 7.0.2 for optional chaining support --- examples/js/acroforms.js | 190 ++- package-lock.json | 156 ++- package.json | 2 +- src/modules/acroform.js | 1043 ++++++++++++++--- test/reference/checkbox.pdf | Bin 4154 -> 4178 bytes test/reference/combobox.pdf | Bin 3381 -> 3415 bytes test/reference/encrypted_printable.pdf | Bin 3314 -> 3314 bytes test/reference/encrypted_standard.pdf | Bin 3314 -> 3314 bytes test/reference/encrypted_withAcroForm.pdf | Bin 9198 -> 21402 bytes test/reference/encrypted_withImage.pdf | Bin 4152 -> 4152 bytes test/reference/listbox.pdf | Bin 3367 -> 3401 bytes test/reference/password.pdf | Bin 3319 -> 3353 bytes test/reference/pushbutton.pdf | Bin 3342 -> 3385 bytes test/reference/radiogroup.pdf | Bin 6070 -> 6203 bytes test/reference/radiogroup2.pdf | Bin 8260 -> 14888 bytes test/reference/textfield.pdf | Bin 3893 -> 3927 bytes test/reference/textfieldMultiline.pdf | Bin 3650 -> 3684 bytes .../reference/textfieldMultilineSmallForm.pdf | Bin 3746 -> 3780 bytes test/reference/with_annotations.pdf | Bin 8705 -> 8943 bytes test/specs/acroform.spec.js | 99 +- test/specs/encryption.spec.js | 191 ++- types/index.d.ts | 9 +- 22 files changed, 1322 insertions(+), 368 deletions(-) diff --git a/examples/js/acroforms.js b/examples/js/acroforms.js index b4819172e..945434df3 100644 --- a/examples/js/acroforms.js +++ b/examples/js/acroforms.js @@ -12,68 +12,128 @@ 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"; + 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); +} diff --git a/package-lock.json b/package-lock.json index 01bea0001..a7d0ea587 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,6 @@ "jasmine": "3.5.0", "jasmine-core": "3.5.0", "jasmine-expect": "4.0.3", - "js-yaml": "3.13.1", "jsdoc": "^3.6.3", "karma": "5.1.0", "karma-babel-preprocessor": "8.0.1", @@ -64,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" }, @@ -1651,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", @@ -3219,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 +7121,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 +7144,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" @@ -7102,19 +7161,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/js2xmlparser": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", @@ -10306,16 +10352,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" @@ -10806,9 +10852,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" @@ -11078,9 +11124,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", @@ -11593,20 +11639,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": { @@ -11615,15 +11674,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 4671227b9..f23b3fc38 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..8cde1dba3 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,191 @@ 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() { + _MK.BG = _MK.BG || [1, 1, 1]; // default to white background + _MK.BC = _MK.BC || [0, 0, 0]; // default to black border + + 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 [0, 0, 0] black + * @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 [1, 1, 1] white + * @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 'inset' + * @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# + * @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 +2252,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 +2267,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; } } }); @@ -2191,11 +2350,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 +2380,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 +2413,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 +2768,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) <= - AcroFormAppearance.internal.getHeight(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 +3337,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 +3353,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 +3433,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 @@ -3044,6 +3687,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 +3752,7 @@ AcroFormAppearance.internal = { return cross; } }; + AcroFormAppearance.internal.getWidth = function(formObject) { var result = 0; if (typeof formObject === "object") { @@ -3088,6 +3760,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/checkbox.pdf b/test/reference/checkbox.pdf index 04f299c170e69b646355523087071f6d3a03f182..9ab1f1032e00fc65d532ab917a05113b3b5838da 100644 GIT binary patch delta 441 zcmZXQJr2S!424xTf)FFCm(GoypC)S&FmiyZLk9*V#89zU;tD+oNNn7q90&;Q60p5k zpI-c)^MEJZ-pMj6wooF3u0&R>pp+20y1$ZsHqq4e^SZT&Aio7C5W^s)v+kcEqGF8l zoG~rA&M3`vcJydtV|3Cop;saEZ$Og9GlrfWLW)>G(#Aw@gMTP8jv_d+oc|0t%`PFY gS!hK*HW~IQ<)VP+ram0^jp(k6%hX5|X}aERK2p6}f&c&j delta 417 zcmcbluuEaXf5y$?Oc%JB3@s+VpnyjZBOUk@N!X z2LdBgQwt$QW4|Vfe(z(g?%R#zyAImVnJOHa10aU`bJ8W=?7m*W}~;O`Im? L=3J_(uKsQSz-(0g diff --git a/test/reference/combobox.pdf b/test/reference/combobox.pdf index 45f60356a506c358aa849a43d04d63a61388c7de..a242ce2d77ce28e84d39a9ccb52d106ce6656652 100644 GIT binary patch delta 369 zcmdlgbzN!!lY+0ef{hKAzLUE`w4nkJ#sb;S3eg4%Kp4wqXSewuBL^pwiP2;Z?xcDP zpdbh+I@AH%rL}^j4|vmG&Z#atE)${*wDn%5JR(> zkvWE#xhaNY3@t2-(A60k7-E=bWN2c7uFk;7$OOaHMy8gS2AY{$OrF6rmBZ44OI6j? G-wgou$4LDE delta 336 zcmcaEwN+{X(`F?mHclo3i^*EtN%baX3I-sckf*=}W*8WlnHyq=Sr}l7SsI(5t1~n( zH^LAzw8XH((AdZntgar(VnY*Ca}3R%@me>VWJS3$A> diff --git a/test/reference/encrypted_printable.pdf b/test/reference/encrypted_printable.pdf index 0e7a971624b228afcc19799a6de0cbc0a7ac4a89..654e08a32e70556c78bbf2cbd0d54c12989819f2 100644 GIT binary patch delta 133 zcmew)`AKraGftzd;s6&n1p_@G)a254E=o--$;{7lNi0cK&~ULbw6ricG%_@R0Br*U xbpwOV5?oW6T%kgC3N}y{PRdRpmdnnLtGFbwsHCDOHI2*2+`y1aRn^ts4FFsRAs_$% delta 116 zcmew)`AKraGft!BsS=j^=5BcxRi>lKrSDvnnpl#VpXZWTlB%H5sZ-^9s(1T`$V?s4 zE_r5m)%SZELtk!|;F`+B<>ct%=IHEdr(iSrAdi@;fq?;BB$mt0j;pvNv8be?C^e1C O$lSn?OI6j?-wgmRG$*kD diff --git a/test/reference/encrypted_standard.pdf b/test/reference/encrypted_standard.pdf index cdc5d3188880c1b9a70871b918d2754b247e82b8..ddf191b515046544c49dc1bf723d056bbd82fa0f 100644 GIT binary patch delta 134 zcmew)`AKraGfv~I;s6&n1p_@G)a254E=o--$;{7lNi0cK&~ULbw6ricG%_@R0Br*U wbs*X-$u)(^6)I+@U;}00r0f)8x$Nw?ic1oUN-By{)3}Vx4Gg(dRbBnv0C;gBNB{r; delta 117 zcmew)`AKraGfv~>N1870Utw|lRM8YoE`8^s)Wnj^{5+S$l2iqa&fW*hPw%k4c`|p( zysxpvErJ(WMV}tsv00L93KN%;ql=rPv#Xtg&E$hTVyXrP25^yBE;~D};*!Lol8U0# QG%h1^14AxVRabvE0NX_@mH+?% diff --git a/test/reference/encrypted_withAcroForm.pdf b/test/reference/encrypted_withAcroForm.pdf index 536d36f91fef348262278e1577240c1f01d7a864..27d043c37a48501ece3ec4ed078227d1e71a44bf 100644 GIT binary patch literal 21402 zcmch91zc3y_P>C1OGq<_l*BN_0MZ>2f;32@)X+#Hf`oxei;@!3f*?{VNJ=9hB1njI zhk(@o4B)*Y-uK@B_ujpHm^o+G?Ad$8cdfPeIcG06bs0Hc2p^1qt$ZkVm;eUi2RWG8 z5QvC?FL^pzg23vgS1bvDcV|nKD+mI7t3d$Puyk>7b2hhh0YMK4DwY=3rcw?bAOn7Y zfDl6PK?OmAa0s8EkP!h`5`}VbbpaU=fDq{BAOeuU;SqUw6g)f%9UdX*ZqN-O`y--H z_D4dWpgG+C3jX8dN3W2>Zh;}C9Z;?S!!DrxMg(GF1ePcZv|Q-V2l5esFIb|kxLyUp zp#tazF0Rg&ruGE;SQdxiuN;Jh}&KObEqQPGu;sU@WvxzCND=HP^l)u#m7Zk?uhs+~BDJyG;_A2o_TRID)w%zz_1F{@G9y3IBGy?n;4%OnV`-Yite zU0R{fdkB$~lYL87q_Nl=IB;`vE#oq}bDh{`uz`2?@wKnUO1*Cp=@fnR;WIs(TuBA| zDRo2);WG--$y}-iIa0sWIjvSo@hrI7b0aL$GYR@TyXVydFO)S$I)6!;B&5;6Yh)t8 zbzy; zvHTM%AZQ@|fQ6j3r5(Ec0jv%%bpX@@m>z)i5TcL++96I4QF@3`$YIL^ph6D%I{>NC zPlKcBe~b-+6M*Hc0F*=ckx;a!nY)4vAb=+!VIU+D!p9Eer^ozoWkVu97rP4~_5SLV5h*8QQ4qybyA z0zdp0wvyBY?RP4vzHe1k;2ro3f*wR#F)^^r56VHv1*=(E0a5_=(*TJ9e?I{h4f!KR z1pq66K)?ikQH6kjAfJ%n4^;>VA^Bj?UsRzXo^@`T&cqX9XlQ(>4PK%dvp6*gTR%*R zcsM}&*IM{V0V){(QUibH0JK~F%0HZ6=otGjIG>OJ2#P@P0e&qY0ONz9eE{ulmpt|j z8GxN3pTY3bwGOKo7#eV@0K8`ufkav6vlsGV1XPktFG;EI6`M)7Nu=~t$r*CWEPE`A zBq*pEvCc_{E7xzWgf`U(D?SnQ=PvN+x2QqA)Ldryq%c-dvV_C&K#=s+2HW^2emR`G zulM>##$S$M(yZIo?Ud$rnS7+DId#o4zi1wI-HH4^)zQBK2O1P$RS>h4`T8-2aod%r@5ghjDFGuDCsM`tN!E)13Pu_*V{LP^5t1 z!SFxaj#%(v;H6%wU^60DV0d(RbYyOMM_35EhrD2`i4KqdKH?o@lHW!9pQGOX_eVH7 z5+V=+0(?R+5CqB3$1iv|TRP$w|I+V&LVOdz^S`nx+U2dR{+zw-=PF=6b`Sy%93JiE z2MG$SVQOLRAn)wp<_G~|p|t}F2+apc3kVPU0poWq4ie-qA!0uf{!Rl(1RoLsI@AD^ zAI`@w^xtTJ@_(d(gZzG613NE>CX`i0D0m!eo3*E`OxjE-w+;= z5FiWMFHybA9p^}OdyOuzWmy3y)_pSYjPe|GGB1Ri&6*uj+dxbh$%*n#*zjEYY7umfNJZR_7V`LUVn zp^6Tj{6IxNoczB~(GlaKl>e?);p6&*%_|D}pF{v#Fr8PNZ9s~>CqzW^10di0#|xDH^zO8RiRc#tj7 z8iW581cBB+Ab3P$`xDY1es?hU(^WIG0oFl3Em7ngob7?A3cT;P04Bmxe_VYKMDX#$ zfv|nBmN<5);&9Oqf$qD~;rj8Ehvy5$$~9i*X_ao{bKgThz;`5`Mx=ynRhj#b6`5@n ziO+m9-{{qcg+Vqu6M`ncl{wGSXP;42M!fPpIjJ!mZ)8hyxyyP_#oB8xK7LTiD>>WcZk&$}mgeg=r;VZ zVW#QdF!^&*|G$6}0s&SL!0Pbdpc9JNPgRGup>7Idj$d#$#q3WF{8)#FT}^=%amhD_ zPL(Rmnn6hKvJD4gVdD-zDsuBJX0L}lCgDGEajsR;FE7w-g}{O zmd(*WY)-1MvM*0+Y77)zTkA8Z!1Dd-J!4+7(r4A84zh+}*{|Qo?{A@n&1dfq$qO+a&A<}Qo`R% z?Y_8}w=26Cr8=c-Ph;UPUGm-HH%$JV8vidq`OlbyKz_zaKpK}IlF?(7SftOZiV|8) zoQcup#?(aFDeb^&SjQ&kc}DP^Sm30;;ZW1Jd}%4p+n2TZ2=NJ(Gw*vIvyl+D8!*jD($6kM>emI52{HAkK&mENw*~r$;w@(B$FlJUt7D8<>jW<8pc52=Z@xI$} zi!HhM7D?xF>!Rm&Y<()fW0NR1bTaJG^UY8xZ2xjS!r1RPyO_U`>gOvyf1gxG5O-eH zg#TGx++qSL<|lUx658EN+KQ7JLr*a5TwYo+G5?0ipRdaNeM}xjoS{lHiNTm~%zMn| zL8Hd(K9`31u2h`%L&>n6H^2NoS4H~CBv|-4o2)fnjR4ZL|H()qppJ?{1VSaa7l z4%@G!;|1;|9L>|)`8@=mMuKfV#!y^zHz`~id!bSgfA4(JS1|0jRQs#3zh9~&h&ydC zRXWkZ5Sd-($6UR{LDFYLs$R=ZEj|8Kygi+j-=M9e%DbMt(hay+Es`}qgL^JBI``hg}mqrK;)UlJ3`d<2heMi^Pw z^9+)ElALXqCr8%CY3y)|t4Sq-dpS+%d1ew$U@X0ThcS`(8)N>rGn@a+m~cU%pF(#S zc7cj_K+WSbevJ37QQ&;7?$vmnvnU69>1Ehi3Om71mFI+aTNme~k36bZc)XKmO!wny zi8Rg4*V$9OlS)+OS=hs5Ct$Y96W0i;A`~@58ag^ocAX@Qq+QcWdl?U_3S^<~x;uh>v(wWvai|89N;<+pV3V!q`zCS7K#TCVSkiL=eX6RB;Y=HR;COP} zX5!`BGeS-4=LCaLD%Wd@GRNSyueK4Plr(L$n>G0MVop@uu^DqbEG%?BC#K^aywp#A zxL=n&))=j}ew2_M zo-veE_ZW0DmJ{E?@4hf9-=7_JQZ%Q&V&vgqakIMHJ@B_xLR#l*!L~Y2Y!``EglsS7{(iibuLn;gp@!Ru+(J0B*(*jvc(pmABSMmm>bR*QtDgq}3`**8Ja z%u7QEB4i2KNSQykGwo-K#GY&qAGsZi<+E~8U+TqR!fU(Y)JvlS zK`)`{9T9eB*QJ84kTX60V#vn&wn>4(dZ^Uw@=YhGbW%ebeX+P?+I4Nl{K(Xn(&paL z$;k5ymdp<{Zp27OO@8{+f9axl^nGIiie3TMSK*ignw#GD#;o1vt3kG9mQ%RKWUja- zl5m+iJ_g6iW$jz-=_In$k10sVI^FVL*33K^I!`cCb)Ij$YWHC|Q`Q(=V2Ryx87(ei zqh|f)`nC`Qt@ag_QC3##0p+FKg?8q?65^Fd?s5r$1>E4_2tewFtT0VI77*!<~Fan7#3 zl$*~1-Qn6ID;X=O&PZ#&=^E148Se%Yt>p50)AtZFDaLe5!W?o3)hJfzkL)1bfDMv| z80Zaa@yWbq8^5h-Alr@nNzM?3 z;tYvrpXo%K*_}Hp7zMib&h$7&6L(|!MHelxBQCxkn{R05z?!B!hd2M(NVzSTlfPw) z{IgZpGY!UfAy{G-b4D=ysWn(nab}$K%r#-G3p1Zvm-^Iq)y*V8XfGk|8!DPOh^ zzo;6d6Li;z)SqC^kU+N+UdQ;3rT;8ht11cfBSvYBwR_Epp>I%)`h4Su>9Y$oZI-LO zw-X%%h8BX>@0GfAZH(Mw^ciQmeNig#ZKEpBOm$}#~MpU+cI711J+!=wJ%%ak<{9$n!5RZs-635*JkY#d6E>BRTJAsAv?D(XN~t4 z{l>xnne`@wU-0L3c8znQz5VRhml+nOn`*g9Uq-b8H_8{SV{wYZHN1$dQ42g_BQ&R9 zrryhatgwRXQKcJ6;+tCE)=w~=*NbmNa6PAWOMYJE@`tETs(of@q=^!%laR_zHtlE0 zLNXESW+g!}LXKPG;PFc~>Wc8Q`OGiF4A%z22Tbk?1PLqTW?q+)GNX_WXCt{;#%;9y zI)-O3pG{*5(H~H3UFm1~YLc>Y$* ztxp7Vxe$|l?YwGNVfpNCXMBwmAH{{$-qtuB#>gr#O@6CVxQK0J{WIBHnaRF+9elEk z8W!44&%T`)uP@+qd(MAtQROS$*fkzPT|y?XdijD9qu5{+j}?zwc^s8v0hwd$>yZ`B z^JFvK+WhZzQYs(maDK5{TB)ezC&`MHRleM)F^mczEngrKeSd+ANxvJjyx(n{$FRaJWvz(ld`Fa1xmcz72-O^H-@aMoBRee-pLKOP_jEdZLzv zy#{ri3`&fvLsb?FYMCp^TJd#he;C&BTDYbxqj}{!u|mgayW% zo6oA4j4{5NR+o6a1BuJA;4-GG;&O25mZ6wHOK<8nG+3``+Kt!5L0RQ=a;M9)-ZaVu z5IvaRG{F}lixUQ|EV0_ZGZ1*X>smq+;dM1JJo%HW-vwiOwU*#Ki!2LV&2S8|hD|pE zDM*!XfzY#M)7i^%HZa`hL)V+Kou0%R!vtf zgfnHgYEjOq4^J(1I{i9~=2)qWdGF}f=_U3=H7!*oK3FqLr5kF|s*JpI+TjLwv3-rk zFxH7l$TjKSO9O88B+R%5LAClr!EfxcPQ8akjYMCfpd_E=n|i77ZSQmQ~w z8sV9>Jrlj)Sj0u}=9Z)9l_a7GAH(S>Z=oE$*2+wVMBnMV2z%!VCdiH^H%-1@sjanZFB`L97{F?ji@WN&W;32XW22~XdW z)>DqpF&47}wRMTCQ=E359{Oti&yOp9GAp47*Ss$q~}_T2_^EsCM;2^L^j`ME#jtZ=i8Y&T&}!qTr=KL0peabH*|tTaiTOZH@o&e zwb#5Hn_Z2K*mOzP+saX4O|>3Iw^wtlgqz>#25$-VkECava?0VIS6w&{-deyn%9if4 z@vtW7^7xJw4E{%UNP7<58JVnhCr!LploQ9(mQEQVyCTU2v zoCCGNm4W1oZ#zkxNiVq$>a0|L$f=@uD_A=RyWqISOl&)2PK8*m_5WhkvZY3lPnf?C<@US{JlWDTc#WI3nsei= z_T`?4J3;-mQpN)Dq!k`QSWJ0i>OC$wqa{fdGlkJa^=!q{Nr~JYM${QMB3>tWZdxd} zVphG8LA~4zE-bMm7)hucWii2yPZ>*EGww-G7Vl$jCX@ir1wXnBUrmE@4RbvP_oZ-> zn%^hTD3nr(+jD}?yIQ#kdl$X@yg>yyW%|+|vBM)%CInlh_3#)scti^}x;J5m?S>uD zJsKePq(RYfT4VBlN=>V=uzzMPHkLu2Ndr~|7dJEKnu~q<_ssqt?D~sXcl)`s)>LHN zE5@lW2d}5IE*bm=b^px8LbxFOC)B+(Gkz0Ax@eQwz(27o^h)0&L-P@V*tcCZ4;eo; z>lE)L6sh&N0UUk+HK>HzdrGOKCo~tX z%YebI(x}3m8YMYAhB;!p{&RCG=GY-v|D%hO>i!OseWK5u$TWBO{uT)NC6*;DlHOFmNbZT!yL z?jsv_*W~ru$U8RHm~E0FQuS*WgdJ-~7o>#U*VKGrM3{`2$P_2uoaeqY)B#sF2&Xq6 z?R7Xl2#lkl$V*Gy2rEo{TlqD~!8@-im?P3SR3jx!Y4+@z71woI?6UF+)%Kg}tI0`q z4<(hayse9SAUhnY+5V_%+$J6?Vz_uDbz!nuX@17bX!p}*UjCV6Pn^z>L~&TTIrU6)v*Co5V1lDC#5mqz`x+pm|9maq0s__a?`X^Sd4BobO5!snm9#~-s^ z30aw<)#-m-)~7-ga02zHk?F>`WMWugfd;XK@B)dp(I_FuQx3vPjY@y2=uUD5PWRdr zRS7vWjaJRDX!%`JKuJ9z6XEwivFOIXU**Wl-S z;yLG>tqgWTYWq4_N!9xL1h&iE8H-$6o_+6Z-T{HWf#cOO7{KwKm9H2(6i&M5a4u(q zpEXPhe&VI4!}sqYyWO+XQ@UN~!$V#jdAs6Ptwbg;<@+V?daH6X_X~zFR)J z(EAWZyx>7PuhAQC8`Pios#jloGUQ38eU|X;v=55RW6NZ2cj`r{gT|6l%Z)BtEBH;t zl9Y8yiW#Th9Fv)~p%^VUvjh9;xiI88tdM%=DSP$PR`NnXMLDG#tI#ltj#%GO8jGT!wnXa^lzHOvb*RL-NQQ6Obce_DlC!7x2$2W5Is1Bedg>9!YH_ZioBScdZ*Z_jnDB@m8BAI~WJ zkH7r=SH*#q*w5EmbgswZrn`DP5Qs3a73bx-ETXAZq3XZ&fV-wMz{pb&XFiN&%WLmG zi7r>hS(;8nZjDQ|@(#->lXG=}&x?5keRr}tNURxj{h8V1$xgGY-;v4FeC6!m_2vTT zbLiarYXn>Q?TscO+}0lDyEB2q>J~juK3^ejW19_lD$#KMgK4$gK)-}*nR8vB7m*(q zu98CITbW3o;Shtwn_EqI?~_(wVK!f)o3=%HX(%o(Qbk6xknn$^OFpm6*kN#`L*)}_ zrf=pLtUC#N6*2?S7+Y6Kbl(HPTRJA1GgT zkY=|u3ZG&;TbTEzPvRC;#DvQ$ML{9^L3O3%z)Fd!C(4VeTvvr>k zh@m#pn#Af_zQf|@_=13GPeytBEaoJBYe25Xng&D%d@6mfeO{6_Y$fM|(%pboqxf{6 z$0Oz^Og|Pd`<%#>Rtka#Mzf%LU^Y;P=k8#=+kNd2wa3$UZ^l1*;$+USd&?@i=3@+4 zN$kB``hfE(SL3a3Igy<8H#s8Oza+4Kz-3O7wIUQ@?=z)D}x?;30*6FPLI1}nkV$Q06O&f z9F}Si=jzf8niOv{XqM^M;)oEGL%MoXedXiwbMJ=aWNs^AI9Ja2z`GezRh~33S-rkb znqJCW%W0ZHX40NH<}+dYqJEu2Q2n;ME>1)jlA&~t@=cg8-xnU@%t~VM)3`J5<~4V- zHNVV~D4fl0_eX8>sU#@S%jr|eVfEfg!|eE8bFym9j!wnS+~zIk@Rs2Xs+pF;C${uh zaVrA4gzK23HU-n1H|1Hjd~n2*?dV%>x#3Mcfb!hHJ9%TejF9$puyT-0;As8a#M=v= zaw(@&#^~xw#u}dmt&df0u)7MX8Q*%#hNU+j-uKp9omm}}F^$46KoVaeoQgw*o=6l= zYCfesUgy@v-RCj4WXKf0sc|DLH(XQd9BMD;%G(~84A+Z+2nM=BD>!MlT;mDkv+U`- z`R%BIE-ZJR#!VL+qHnS#N;SIhJO&C#@$y&I{*A4>r{Z`X!yb9C&@-fUnX%eBDU6GL z@uqt>bthRJRT0L#*Fz$z`DE^L5AWDJFXOAjIr)zQ^O9Mvk7Y&Sv%Jlok6m@EB7GuR z81GM;mtOm>fxX&xU9#7@=1P9dfJ6}3%VDqCuuOt|9FL=38u7-GEZU)d^IavJEeoH_ zKRQVM0_oZp-YeMD0yBlmqGod#Z#trfe-p?4nVXtN*JHV7$jDJ#dPOnIloy0?@WKNj zbLLm5t`3tlDk^YZ!49nq<9=}-oNDXcs!MuqqAemTSU%!%ytP`qjqd~^j(vjm#Rqr_ zbCSOUp8GX*>D=$nb=+UF-ah#$jCzFF)M%np<>Hxzhtf$Ql_>9`g$h#kpvU9(U%h<| z(k;PUTkY61D zzOy%f&Pq?AO^LV{QYpGXf=ku9NPdR4)WtV+lffso*+5g~vPmJ;%vCi*Ph%PaX-(kb zj#e^;-X8D04U_r{xjJ53CXG{*8TSTaX}>9DGG3Ta{31fP{HQd=R@agJkz!($_lYpu zyl{$5;()d*S_zr;4_C2ytL_TUGO2dE=UfZ^dX4^4DA|@PS?I`DTO(nuO)+z);B~|LwzX|LNV=pC`#`9@GTW!PEsOGzA+ndEfg*o5uM> z2Kw^`(k4IeYqwXA^&GLFm&`@(#>LeX1EBwRajlq_KI@`SWhdE zuiF1{3hIuP$k?YG8ypK~y{u4Qy>QMi<^5qdnVUGvd)D`JAB`KN z&WUs^u*ofq$kwcr(e(X-y8=tHNg3|4&2qFeS&M=BpvQalOxFvn354`5dqu_?Nmgx){B%wOOSYmL$1PTWJqA zzfSqrX`C_O$(Y)1g?t1@j1vBMSd*wluBvTLK ztyPKp-?I}~mCRRfu*=%7*z29kVYk0%SziSey}_Zd1cg5XbG%Ck;4!9tyRa7<#qCV} zKJEGP)R#N!;TW1A86`y`)A9~Owt9SR#z<@xCMPDTTZ%lk-|mPwV_K98xLOaC@-PON z7*_`A&Vs(Aeto&x=m*~mDjWNNyg@s-%&JZ5i#>Lepn5npPpM*cGj#|heQ)HU>L^{^ z)^DKp&)n!2JhC49aQ$&6k(Zv9Kne@vdcr_CQmXtD?)H7pP~^_{+!O~`oC%g^i>T4l zkoV(`aEeHga!Zb3;j~IMoN4eaO1^amfL4s zZKH0gc$@P(N(^Z88s|qA4Ec&0`9spqtRZ}h=ZUGtcha2G6(?x?lwZ^86;a&s0z;z-yhcsCp_%HgzYl|Cz zR(R8pr&Bzexim%Nr!8|!K0bf3=js5qNk};t+hn{dT)}EnBeir&-Rv5|Y-Xw+pP#CK zhdGP4;KU+tTh`W`b`<P!>mkM7!7nCa)e9lv$$zBQ*yBb5dvO*|jV5n`sXgp-<&i zj3pa~Gg{IIO&4E`T~0G4GYRG1zR1`5E_7vQwIZW@#%S1oYwqKhRF6@yv*JsUvuU9# zZFL;YC3Y$=z=IiDGcW2WW3s~&c(Jxe$;QMU-;4E))Tej8my#zsx*HR&eX5RmyMLI= zQR2qwYm%qo{i1iTf~0%yf4n4!#|;Au&yw*KT-c*giuj;1;)p4M+4OQ{c=U{U`L3@o zrPpfMS-eMd!xym_@`fi=xfpv-yt<6Z^nR&?U(Z7H9)ksUpR7n6B|C8pXRN*VW9dQ^ zPVty(yI$uho!NfT^@a{Ez7=YTuTsJ5GTeV9qvR_4RMfWv6g-IdQi~W$@6p~l$O!=Io;|<(I`5ggU(csq z{xwIk}J?sucg%EY7AaHrF{p9J|WQGK0>dq^=lsVBaJ~;_XhT1u4Yg6l| z;uYSad8XK%z7wy_^k9a$ahRati!Ew4{{56h|4hyD&kEreJy4afIMm-^Tka#lEyF## z(&_Z|68FY~<*ivBN5PDDA2{2>PR;tFxVPSwcb|<%%-#3z>`{zz@9!jLcvS*Fx7bLu zmEA8VFBS(KJ@<5i!$b6$hN|W;iI4*i%)?=0>ht2v1ik+@!mV7l0@p6}quMBiPGx5p zbagq~rs0gN%z#16lWfv0QZ;fa>WpP9X6+EZM9t4im(B!GrsSNs#gt`)vh>90C@&o= zs6frvv=GUQKr-#?r>fI*Q}25yg4geciz`S(h~qJmG8V9Jy@F2pU*{RhT3yD>+1C4L zDIcJ2A-P-PY5%G#;<|CoWSX%m+c@ojzrBw@&ajE%tg$3zgBt|)i^U%oUA(AlGi*d{em6etHKU$ zhIoIYS#+_L<0+n`TKeEr`~^xj4r4q$f|AzeA+m%e=i$+n0LNS}OJ!tK26d}d$a4yA z?+Nd#zTXl>Mk6P&>r%GS;-+XSa+#kpY zt#Mg-SfzBTKJ(JM;iu4!ZB90UGg0FNYB#Qa>|t&#b*SYVX=%R5N;xUKbT4Pfzvj-H zyo}~98zVU%HBm*moHdwNRCc9jCXuvlbz3;+th$m!zzZ-Y54}Men**GUpuEnNYhzM; zNm4}RJ`9Q>*pDhk7E;~)DUHcYZo*{IdeLh zK6&O8YzouO{uS00io33zqj+88xATc4u)col&G5hF6lSFRoa?+W;W&RjJx*(JJ#GoE zgbzzxrc6+05 zS^TILa{T#%zZ{)c%E8X!Z(!m04Z^=>LCwt0+Q|*5*YZa(hyRr#j^8l)Yc9}=`1?3H zem~(?PNae2G}e~R|9dSU(0lxjUgZ3XvHvD8AdX+1_$vnfHUl7zuT}qwhrbmFh~o>H zU-`IXZExwqtKnd83RKhhU%Le2_zdg!pD1AN&8=qtu-p%rz~6iaKDR zFc(m1#L@#uaf~CBKPLZJf@PeiSRVmOsp-+MG3}{C~p8};o_CddY4iqi; zF&+xtWZ3=j^!xeN%k1Ig=HFN{O8fuP~C`og4-#@Bc0n-~-1rW;3&JF+xy6pv1PX{*u zNopudX$Lzudmtc6nmaq7w@?E`4EDSEbK2wKY-vTn4-$Y995M$S_AC4#Kvtk1G$gu` z75Wc^I;25_VCc;;M`*wYbm6OCXi)T10l&}$fl@)g&>#?WZI`3%ATadis9$IZK|q9G zXh`I-F`)c{$9@Zi2_72*Du8yNqy0gJfL-Ok&|omMn;dNi6F_fy|Ai)mF1K)$#*YLl zdi_F!LeV?ukG6wB1dh?*Q1tEAqwV1EUi2mnAyZfi)Z)TtR50p<}8d%E|$>AJ>5oz_QWV6DV`HpR3Sc%76?&BKwm4i#M_V cq{)8WC>K{#XV?9x3lS0mo^W7elU0-Ze;gLTIRF3v literal 9198 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 diff --git a/test/reference/password.pdf b/test/reference/password.pdf index 7e1b6ab4f1356c789bc434efc76fdf287a8da379..a8108a016769a219dd21e73aa7867e04518d62bd 100644 GIT binary patch delta 372 zcmew^Ia6xGEk;FsUvC8)8!mk(cZFy}1t5$CvYi#84HSSdmdnm=^G(M0984AllTUFa z)|(nyC>Vf%LY@K_m|2#QRabvE0Mh?UlK=n! delta 338 zcmbO!^<8qqEym4%7~gR)nV3y}!xe>ZL zLjywt3^7Av3_A>s%#1X9urG`2Lw&}?dKh#_WXis2YTa|<&Jb(V%0<{257SRko0 j1bf-Y&=kYfMkbaR1{#@K7)`F>naW{c#HFh0>hA^sVXH+K diff --git a/test/reference/pushbutton.pdf b/test/reference/pushbutton.pdf index fb4bf997b4cf63a68f6520bb50410dadc88063ee..12749f1dd21bc88cfa6040ffcb2ab7dc707ad8ab 100644 GIT binary patch delta 381 zcmeB^+9|c+BcqnSueXAY4VS*Nqk@LNwV8=0D67-(c> TVllapXDWx81(&L-tG^ona0yLp delta 338 zcmdlf)hD&#BjaWnrnel-MrM|idAO764a^k`KtLf+feXwqFfcPV!Vohz!xXcyG(}fu zXkcuNA!cZbVTYlSg&9~~J(9(SCWaOmnoUhjFvQF(FdSoOVPJ`&&e9meJR<{hLv(cp gMuz4Xt~N3?#4ymv%)oT=9GTS0Pd?pP5=M^ diff --git a/test/reference/radiogroup.pdf b/test/reference/radiogroup.pdf index f069793858ebd2774c90395c8567da60989d8dcc..1e4f197b9c3d8b0846ab6c99791c0948b9c04f1d 100644 GIT binary patch delta 776 zcmb`Ey-EW?5XZSh5U&yvPVAo zK7r35K8cNMu!uJx2<|k)%>4fQpWSEn!XMuwI6Q!EH)j2Pm81$g+Hh%$Q);k3@&M(WD9i3m~MG0vTJ@Hqy>qWtxzX^_Jvx;7) ze*2$#PfPc!b?2;qXl}KY0seKMfOwv8ZY@@8g8PqV=LRM1$kdW)Lsf+{ghh^oMYO~b zs&vk}MVn|eg;P#oDG+^>G`3yUrj$1P*U#mp7LwGA7XCPCJx&8L5JnZ223Is8UF0UPq49WTytYFWDMZj96vWQ~D3US-q$X)Nfe|HK0SSpF zXW=Zc3C*(8z*N@LJkRIx3O>x+XCr+zZqjkq&M$87CbPMY8uXuCoqsZQr) GeEJO*>5ZWP diff --git a/test/reference/radiogroup2.pdf b/test/reference/radiogroup2.pdf index b10bb81dd91c87d04145b4a1d0ae5ebe483e7413..d280b82231bc6df4b70fc903efe4cbacfcf39874 100644 GIT binary patch literal 14888 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 Fe*h9;PJ{pe diff --git a/test/reference/textfield.pdf b/test/reference/textfield.pdf index e1de3ce00d4fbc1ed52d28fda39eb3e3835a3aaa..8c80d6e2da0e58b0ec59afbff6ef197a29e8b924 100644 GIT binary patch delta 391 zcmdlgcU^8nD3gM(w}Oogm%fv`LbRa*5XJ)8&I-{63P2dkWoNfJm}wptvxTwo+Rdw}u0|5V$M)?2$ diff --git a/test/reference/textfieldMultiline.pdf b/test/reference/textfieldMultiline.pdf index a78ceb310e7c7770280583cb73567d58e0409b84..93588aad686ebab70c0ccdf8a117630c012df5fe 100644 GIT binary patch delta 522 zcmX>k^F(IDGe!kpZv`70E`29=g=j+sAdCgFofV=D6o4?6%g%1|6Gj70O#?k3)a254 zE=o--$;{7lNi0cK&~ULbw6ricG%_@R0Br+<$+p~wm@Ew@Gx8+Wo0utp#TD`txS%R5 zjLb2_EKQBj#S9HBFvW}vG0ZbGHZ{S}YhrEz7OOWvSZru!fMKzrxv?dNW(#vubTK0X z17i#^Lt|k0fB?b{BO_B3^ISmEM#h#HJ~1+}#BjBdg^3}CftE(*lb7<^$AJvO2kaDV i@JWN@>=Zze$yHpESX5F`l$yq6Y+z!}rK;-c?*;&;ZEODk delta 474 zcmaivyGjF55QZ_89%E&jVnG51VKcXL6It2K=CttzysWT-O@ws|J0D_SMDPhrzcPWcMMN>{RT6rz$_s*uMHhD& zL3CSo_xBwlQU{5xs!rlmX$s&~NQgXq9$ns4F8RPIq&m2=W}7@<#j&)DWof07BD?rs Xxi&>%p8KZjU%K|eFd&=hbh%1D_EBY) diff --git a/test/reference/textfieldMultilineSmallForm.pdf b/test/reference/textfieldMultilineSmallForm.pdf index e589d69905cc2e7becee0653ef4a4e6bce8c1633..0e160972222784f4e38c249a1bbc8b0f4a5b791d 100644 GIT binary patch delta 521 zcmZ1^dqj4FIFo{}w}Oogm%fv`LbRa*5XJ)8&I-{63P2dkWoNfplqsH5(?AahHM#Vi zi&7IyGV}9X5=&AQG+e9F3 zM#ku3h6W}U7-ELzm}16;Mi}Z$Oo7e>0fZffrsgJKv3g`da|29^EsQZ7XlQ9}h;E>f zfu$ven30hghM2J_ig_*|cNv*jVEDwy)DpwhMwZ6r=mr`a7#UAy<#UJy8H5MeDcInV g1xwi}fZ~#?xFoTtq@pM_jmy~3#F$G})z#k(00k^+NB{r; delta 473 zcmaivJxT;Y5QSkf+n;0uqlGqDbP;x|s;hgtLdb?An^&1t3P*WeCWMA!|nC$>HRcF(|cXP%H%P6hS3Iary2o zLGX&9(3ai(twWB~Lt?L@llLk#2{>gUQO47wtEQ~WR3(O*%IJKK;Tl$4F#_j#;#Am6 a_*c@HqA<@x+Yc{&_h1}?nAvP~Q*Qut!DHkA diff --git a/test/reference/with_annotations.pdf b/test/reference/with_annotations.pdf index eea66b8c336cdfb751d36fa8ad421affbc1d2c8e..73049374b6668914f4f58e3d43de0029b5e6032e 100644 GIT binary patch delta 709 zcma)3yADA?5RGWKl^|4-QQZ3Ob7pTM3eQ5s2Z(4ysiPIO&h;S@zoAt70F6p-sW8p% zG?O!D&z!Tn>pl9Xdbyd5N6>0TNoNRC3BG2&@4{4ouUXV?XSMPsfVB@{9V#JG>mb%W zh&8VUY&>79&q7`sJYO2O+>$Ja_kk5epGFv`kYOp&KW3$J3{#Xc%xIC7Al_Oh*^C6& zb(-s@r2g-sl>XO#y`$2GVH))t+kS^cld|B5ni)j0ED$=!9*BfwdL+$v5c%;+$}-n} U2U|kuY_@0~i%wt^$NkCR1%A?#H2?qr delta 489 zcmZXQJqp4=5QPyN4OY=0HWGz&7W+H1+oV!k!5b+4fJLlQ2hXs24ChBK^>'); + + expect(textField._MK.BC).toEqual([0, 0, 0]); + expect(textField.borderColor).toEqual([0, 0, 0]); + + expect(textField._MK.BG).toEqual([1, 1, 1]); + expect(textField.backgroundColor).toEqual([1, 1, 1]); + + expect(textField._MK.CA).toEqual(undefined); + expect(textField.caption).toEqual(undefined); + + textField.borderColor = [1, 0, 0]; + textField.backgroundColor = [1, 0, 1]; + 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/BG [1 1 1]\n/BC [0 0 0]\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]; + radioGroup.backgroundColor = [0.9, 0.9, 0.9, 0.9]; + 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/BC [0 0 0 0]\n/BG [0.9 0.9 0.9 0.9]\n/CA (8)\n>>'); + expect(radioButton3.MK).toEqual('<<\n/BC [1 1 1 1]\n/BG [0.9 0.9 0.9 0.9]\n/CA (8)\n>>'); + expect(radioButton2.MK).toEqual(radioButton2.MK); + + radioGroup.setAppearance(AcroForm.Appearance.RadioButton.Circle); + expect(radioButton1.caption).toEqual("l"); + }); + + 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 +1001,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 +1142,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"; } From d3c9ff4ad0bcd506976f7f3f4c831087f750cad3 Mon Sep 17 00:00:00 2001 From: Joshua Newell <3116028+imjosh@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:27:27 -0500 Subject: [PATCH 2/4] fix acroform jsDocs --- src/modules/acroform.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/acroform.js b/src/modules/acroform.js index 8cde1dba3..4f9c96c78 100644 --- a/src/modules/acroform.js +++ b/src/modules/acroform.js @@ -1828,7 +1828,7 @@ var AcroFormField = function() { * - 'underline' = A single line along the bottom of the annotation rectangle. * @name borderStyle * @memberof AcroFormField# - * @default 'inset' + * @default 'solid' * @type {string} */ Object.defineProperty(this, "borderStyle", { @@ -1846,6 +1846,7 @@ var AcroFormField = function() { * (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", { From 50177437f9529452dd9b088d28b75baf745b8b7b Mon Sep 17 00:00:00 2001 From: Joshua Newell <3116028+imjosh@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:47:26 -0500 Subject: [PATCH 3/4] don't default black border, only default white background for radioButtons --- examples/js/acroforms.js | 7 ++++++ src/modules/acroform.js | 13 ++++++------ test/reference/checkbox.pdf | Bin 4178 -> 4154 bytes test/reference/combobox.pdf | Bin 3415 -> 3381 bytes test/reference/encrypted_withAcroForm.pdf | Bin 21402 -> 21122 bytes test/reference/listbox.pdf | Bin 3401 -> 3367 bytes test/reference/password.pdf | Bin 3353 -> 3319 bytes test/reference/pushbutton.pdf | Bin 3385 -> 3361 bytes test/reference/radiogroup.pdf | Bin 6203 -> 6139 bytes test/reference/radiogroup2.pdf | Bin 14888 -> 14888 bytes test/reference/textfield.pdf | Bin 3927 -> 3893 bytes test/reference/textfieldMultiline.pdf | Bin 3684 -> 3650 bytes .../reference/textfieldMultilineSmallForm.pdf | Bin 3780 -> 3746 bytes test/reference/with_annotations.pdf | Bin 8943 -> 8705 bytes test/specs/acroform.spec.js | 20 ++++++------------ 15 files changed, 21 insertions(+), 19 deletions(-) diff --git a/examples/js/acroforms.js b/examples/js/acroforms.js index 945434df3..9e0a0477d 100644 --- a/examples/js/acroforms.js +++ b/examples/js/acroforms.js @@ -32,6 +32,7 @@ function addComboBox() { comboBox.setOptions(["a", "b", "c"]); comboBox.value = "b"; comboBox.defaultValue = "b"; + comboBox.borderColor = [0]; // black doc.addField(comboBox); yPos += margin; } @@ -45,6 +46,7 @@ function addListBox() { 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; } @@ -54,6 +56,7 @@ function addCheckBox() { var checkBox = new CheckBox(); checkBox.fieldName = "CheckBox1"; checkBox.Rect = [50, yPos - 5, 30, 10]; + checkBox.borderColor = [0]; doc.addField(checkBox); yPos += margin; } @@ -64,6 +67,7 @@ function addPushButton() { pushButton.fieldName = "PushButton1"; pushButton.Rect = [50, yPos - 5, 30, 10]; pushButton.caption = "OK"; + pushButton.borderColor = [0]; doc.addField(pushButton); yPos += margin; } @@ -76,6 +80,7 @@ function addTextField() { 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; } @@ -84,6 +89,7 @@ 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; } @@ -95,6 +101,7 @@ function addRadioGroups() { // First radio group var radioGroup = new RadioButton(); radioGroup.fieldName = "RadioGroup1"; + radioGroup.borderColor = [0]; doc.addField(radioGroup); yPos -= 5; diff --git a/src/modules/acroform.js b/src/modules/acroform.js index 4f9c96c78..66b0c5a67 100644 --- a/src/modules/acroform.js +++ b/src/modules/acroform.js @@ -1693,9 +1693,10 @@ var AcroFormField = function() { enumerable: true, configurable: false, get: function() { - _MK.BG = _MK.BG || [1, 1, 1]; // default to white background - _MK.BC = _MK.BC || [0, 0, 0]; // default to black border - + if (!Object.keys(_MK).length) { + return undefined; + } + var result = []; result.push("<<"); for (let key in _MK) { @@ -1728,7 +1729,7 @@ var AcroFormField = function() { * 4 DeviceCMYK * @name borderColor * @memberof AcroFormField# - * @default [0, 0, 0] black + * @default (no color, transparent) * @type {array} */ Object.defineProperty(this, "borderColor", { @@ -1749,7 +1750,7 @@ var AcroFormField = function() { * (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 [1, 1, 1] white + * @default (no color, transparent) * @type {array} */ Object.defineProperty(this, "backgroundColor", { @@ -2328,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", { @@ -3593,7 +3595,6 @@ var AcroFormAppearance = { getCA: function() { return "8"; }, - YesNormal: function(formObject) { var xobj = createFormXObject(formObject); xobj.scope = formObject.scope; diff --git a/test/reference/checkbox.pdf b/test/reference/checkbox.pdf index 9ab1f1032e00fc65d532ab917a05113b3b5838da..04f299c170e69b646355523087071f6d3a03f182 100644 GIT binary patch delta 417 zcmcbluuEaXf5y$?Oc%JB3@s+VpnyjZBOUk@N!X z2LdBgQwt$QW4|Vfe(z(g?%R#zyAImVnJOHa10aU`bJ8W=?7m*W}~;O`Im? L=3J_(uKsQSz-(0g delta 441 zcmZXQJr2S!424xTf)FFCm(GoypC)S&FmiyZLk9*V#89zU;tD+oNNn7q90&;Q60p5k zpI-c)^MEJZ-pMj6wooF3u0&R>pp+20y1$ZsHqq4e^SZT&Aio7C5W^s)v+kcEqGF8l zoG~rA&M3`vcJydtV|3Cop;saEZ$Og9GlrfWLW)>G(#Aw@gMTP8jv_d+oc|0t%`PFY gS!hK*HW~IQ<)VP+ram0^jp(k6%hX5|X}aERK2p6}f&c&j diff --git a/test/reference/combobox.pdf b/test/reference/combobox.pdf index a242ce2d77ce28e84d39a9ccb52d106ce6656652..45f60356a506c358aa849a43d04d63a61388c7de 100644 GIT binary patch delta 336 zcmcaEwN+{X(`F?mHclo3i^*EtN%baX3I-sckf*=}W*8WlnHyq=Sr}l7SsI(5t1~n( zH^LAzw8XH((AdZntgar(VnY*Ca}3R%@me>VWJS3$A> delta 369 zcmdlgbzN!!lY+0ef{hKAzLUE`w4nkJ#sb;S3eg4%Kp4wqXSewuBL^pwiP2;Z?xcDP zpdbh+I@AH%rL}^j4|vmG&Z#atE)${*wDn%5JR(> zkvWE#xhaNY3@t2-(A60k7-E=bWN2c7uFk;7$OOaHMy8gS2AY{$OrF6rmBZ44OI6j? G-wgou$4LDE diff --git a/test/reference/encrypted_withAcroForm.pdf b/test/reference/encrypted_withAcroForm.pdf index 27d043c37a48501ece3ec4ed078227d1e71a44bf..1f40b46550aec8e9def8bdf6001530d5f1709508 100644 GIT binary patch delta 5313 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 delta 371 zcmZ23by8}>8%70RZv`70E`29=g=j+sAdCgFofV=D6o4?6%g%1|D@I;UW@96Z$-lUg z>dnj)3_w63Pk{@}FfcH)G{g|IFv1kGG&Mn2XJ}w)gdt{Rh+&7Jv56^IT|JV;h9>6b z7@ExtjWNW`O)wl|Xkli7q0Z70!#pEHV_-0#dDzIv7{k>@rsfz18kw1yOzz^D%3)!` KrK;-c?*;&=?n>xe>ZL zLjywt3^7Av3_A>s%#1X9urG`2Lw&}?dKh#_WXis2YTa|<&Jb(V%0<{257SRko0 j1bf-Y&=kYfMkbaR1{#@K7)`F>naW{c#HFh0>hA^sVXH+K delta 372 zcmew^Ia6xGEk;FsUvC8)8!mk(cZFy}1t5$CvYi#84HSSdmdnm=^G(M0984AllTUFa z)|(nyC>Vf%LY@K_m|2#QRabvE0Mh?UlK=n! diff --git a/test/reference/pushbutton.pdf b/test/reference/pushbutton.pdf index 12749f1dd21bc88cfa6040ffcb2ab7dc707ad8ab..c27880775ccefc5518ed55d1ee2da76b069e0b3b 100644 GIT binary patch delta 338 zcmdlfwNPpUBhzL{rVkvTS0LQ69X#fBK delta 361 zcmZ1|wNq*XBa^tkleqT z!v{29_MK|;WZ#)*W@ith-~U$Lw-(orujY;3tI^ZM4C7t?Y5$dI)_KUAtM3ADXPtGg z&602X<9o5jp2~ch>in=aD6-@6AcW~ V&ITCb$a7xZEV~@J8VsJNFGt11fC&Hq delta 693 zcmaixKTbn2493;cg*SBou^}&&fC|l3YIngKVwzyhacNg zrlZsHu=Bhn?QJ@G9S@V0&%Lt~jetJ~?x1NOh-Tbpgve#SpVurtCfQtm^mQpkr)6ZP zWj5!DHMiPz*C@x}#TlJgh+Si0O?}fMmK_Tu9aj6-BQa11{xY-RSVrwLt!l@}Z(OC0 p+E)Ji8P!|AE)BsKjLA)+mYwN9>Y4=RSZ)rUc12?|nOt07egohUi^Tu{ diff --git a/test/reference/radiogroup2.pdf b/test/reference/radiogroup2.pdf index d280b82231bc6df4b70fc903efe4cbacfcf39874..ee5c3c9b4828289b69153f7a7a6705b4637e8fb1 100644 GIT binary patch delta 129 zcmZ2cvZ7?eQ$}%jg=oWAE`29wg=hmk69p)aoqUj4YO)wpix_tK$p=}~Ctqheja%A# QGBe8x9MYQ)vZR{=0O@8S&;S4c delta 77 zcmZ2cvZ7?eQ^v`gSp?af+!dk?V<#VEmYOWa)B+M>1q)3+$f7>^I@4(s5%0;&EGrNq Kn-8+2n*snIm>JLj diff --git a/test/reference/textfield.pdf b/test/reference/textfield.pdf index 8c80d6e2da0e58b0ec59afbff6ef197a29e8b924..e1de3ce00d4fbc1ed52d28fda39eb3e3835a3aaa 100644 GIT binary patch delta 358 zcmcaEw^eRKDAVS0ra4^9=9ZR|#dwqIjf@oxKtLf+feXwqFf=eW!w@qxGsF-xvcwcK zF*3zaXKIFNhna;rSY17`#TJH`7F(KFpc`mpU}1tGW@KoDA!clBi6LfUhGL!z$j?S* l1{gjuGB?0*wXuPz0lI<4hQ?--ckxZ-Fg4>+Rdw}u0|5V$M)?2$ delta 391 zcmdlgcU^8nD3gM(w}Oogm%fv`LbRa*5XJ)8&I-{63P2dkWoNfJm}wptvxTwo3sd6EJQjT8((Kp{_o3(PPuFt;?u5VJJ2Ko>JKFu@cv zoP3Z+xZc>v07JEji3x_7sX2yChUNyQ80suc3^Bwk%`DNyj0`NzKw=5V-Z3&V!Z6Ut k*c8L*MkZz$jyAF|FvqaO!qRy1ao(vMhQ?f~s;>TS0PP4wbpQYW delta 390 zcmX>k^F(IDGe!kpZv`70E`29=g=j+sAdCgFofV=D6o4?6%g%1|6Gmw+CQF0Kj66y8 zCT0o-AfS+^zy)R)7+4sYV~AOr8lj6B0u^G285v@jXJ~9{f}zgD+yX3Ck7Ti-nE{5y zhUUhW7@952P0_`S3=E7h#0-srK1TJNk&!8ic`hJnBV$VppBR}~Vz}DK!o(26KuaU@ S$xC^sa+sKNsj9mAy8!@PZ%yF< diff --git a/test/reference/textfieldMultilineSmallForm.pdf b/test/reference/textfieldMultilineSmallForm.pdf index 0e160972222784f4e38c249a1bbc8b0f4a5b791d..fd2901d48c86467845034d0cd51e9d9665307c3e 100644 GIT binary patch delta 342 zcmX>iyGV9}IMZfJCJ!zqQ=`evJV^m&h6)BCppd7)1!fo+SXfx1iy0ainqY_-nqZ0< zO;+R;t~W6>!%%H%Vt^rLW`=2#g#m_(3@we#(e)Y`n3-aT8Cn{F#1fFbV`OZEVW5$T jDTdRHOieKyZDeU+jA4nTh2>;*zNs7rmRzc;uKsQS-KRq6 delta 390 zcmZ1^dqj4FIFo{}w}Oogm%fv`LbRa*5XJ)8&I-{63P2dkWoNfpl*x;W$--=MB2QAi zrKy4e2q@$!aDf>H29`#~=wgNjCKecChUS=J#)d{1>P$?57Ngo>XliZ(7OO|H*wEYn z(_#x_3Hx@3&>qYCKebzF*3EpaJ7-8u{pYd#s)^l SlUezua+nx%sj9mAy8!@i155b; diff --git a/test/reference/with_annotations.pdf b/test/reference/with_annotations.pdf index 73049374b6668914f4f58e3d43de0029b5e6032e..eea66b8c336cdfb751d36fa8ad421affbc1d2c8e 100644 GIT binary patch delta 489 zcmZXQJqp4=5QPyN4OY=0HWGz&7W+H1+oV!k!5b+4fJLlQ2hXs24ChBK^pl9Xdbyd5N6>0TNoNRC3BG2&@4{4ouUXV?XSMPsfVB@{9V#JG>mb%W zh&8VUY&>79&q7`sJYO2O+>$Ja_kk5epGFv`kYOp&KW3$J3{#Xc%xIC7Al_Oh*^C6& zb(-s@r2g-sl>XO#y`$2GVH))t+kS^cld|B5ni)j0ED$=!9*BfwdL+$v5c%;+$}-n} U2U|kuY_@0~i%wt^$NkCR1%A?#H2?qr diff --git a/test/specs/acroform.spec.js b/test/specs/acroform.spec.js index 87a3f8dd8..d9a01c67d 100644 --- a/test/specs/acroform.spec.js +++ b/test/specs/acroform.spec.js @@ -784,19 +784,13 @@ describe("Module: Acroform Unit Test", function() { it("AcroFormField MK", function() { const textField = new TextField(); - expect(textField.MK).toEqual('<<\n/BG [1 1 1]\n/BC [0 0 0]\n>>'); - - expect(textField._MK.BC).toEqual([0, 0, 0]); - expect(textField.borderColor).toEqual([0, 0, 0]); - - expect(textField._MK.BG).toEqual([1, 1, 1]); - expect(textField.backgroundColor).toEqual([1, 1, 1]); + expect(textField.MK).toEqual(undefined); expect(textField._MK.CA).toEqual(undefined); expect(textField.caption).toEqual(undefined); - textField.borderColor = [1, 0, 0]; 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); @@ -805,7 +799,7 @@ describe("Module: Acroform Unit Test", function() { it("AcroFormButton MK", function(){ var pushButton = new PushButton(); pushButton.caption = "OK" - expect(pushButton.MK).toEqual('<<\n/CA (OK)\n/BG [1 1 1]\n/BC [0 0 0]\n>>'); + expect(pushButton.MK).toEqual('<<\n/CA (OK)\n>>'); expect(pushButton.caption).toEqual("OK"); pushButton.MK = { @@ -821,7 +815,6 @@ describe("Module: Acroform Unit Test", function() { var doc = new jsPDF({}); var radioGroup = new RadioButton(); radioGroup.borderColor = [0, 0, 0, 0]; - radioGroup.backgroundColor = [0.9, 0.9, 0.9, 0.9]; doc.addField(radioGroup); var radioButton1 = radioGroup.createOption("Option1"); @@ -831,12 +824,13 @@ describe("Module: Acroform Unit Test", function() { radioGroup.setAppearance(AcroForm.Appearance.RadioButton.Cross); expect(radioButton1.caption).toEqual("8"); - expect(radioButton1.MK).toEqual('<<\n/BC [0 0 0 0]\n/BG [0.9 0.9 0.9 0.9]\n/CA (8)\n>>'); - expect(radioButton3.MK).toEqual('<<\n/BC [1 1 1 1]\n/BG [0.9 0.9 0.9 0.9]\n/CA (8)\n>>'); - expect(radioButton2.MK).toEqual(radioButton2.MK); + 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() { From 2a0f4b955339a00c5ee99ea472b52d93a3862a78 Mon Sep 17 00:00:00 2001 From: Joshua Newell <3116028+imjosh@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:43:13 -0500 Subject: [PATCH 4/4] Update AcroForms module - Stylize circular radio buttons - Implement MK and BS properties to be able to set form fields' border/background colors and border widths - Set default background color for radio buttons to white - Fix AcroFormChildClass should inherit from AcroFormButton, not AcroFormField - Remove redundant sub-class properties which are inherited - Update AcroForms example to use new properties - Update index.d.ts types - Add unit tests, modify integration tests - Upgrade "rollup-plugin-terser" from 6.1.0 to 7.0.2 for optional chaining support --- examples/js/acroforms.js | 197 +++- package-lock.json | 165 ++- package.json | 4 +- src/modules/acroform.js | 1045 ++++++++++++++--- test/reference/encrypted_printable.pdf | Bin 3314 -> 3314 bytes test/reference/encrypted_standard.pdf | Bin 3314 -> 3314 bytes test/reference/encrypted_withAcroForm.pdf | Bin 9198 -> 21122 bytes test/reference/encrypted_withImage.pdf | Bin 4152 -> 4152 bytes test/reference/pushbutton.pdf | Bin 3342 -> 3361 bytes test/reference/radiogroup.pdf | Bin 6070 -> 6139 bytes test/reference/radiogroup2.pdf | Bin 8260 -> 14888 bytes test/reference/textfieldMultiline.pdf | Bin 3650 -> 3650 bytes .../reference/textfieldMultilineSmallForm.pdf | Bin 3746 -> 3746 bytes test/specs/acroform.spec.js | 93 +- test/specs/encryption.spec.js | 191 ++- types/index.d.ts | 9 +- 16 files changed, 1331 insertions(+), 373 deletions(-) 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 01bea0001..6ffb3420e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,6 @@ "jasmine": "3.5.0", "jasmine-core": "3.5.0", "jasmine-expect": "4.0.3", - "js-yaml": "3.13.1", "jsdoc": "^3.6.3", "karma": "5.1.0", "karma-babel-preprocessor": "8.0.1", @@ -64,14 +63,15 @@ "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" }, "optionalDependencies": { "canvg": "^3.0.6", "core-js": "^3.6.0", - "dompurify": "^2.5.4", + "dompurify": "^2.5.8", "html2canvas": "^1.0.0-rc.5" } }, @@ -1651,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", @@ -3219,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": { @@ -4671,9 +4729,10 @@ } }, "node_modules/dompurify": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz", - "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==", + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", + "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==", + "license": "(MPL-2.0 OR Apache-2.0)", "optional": true }, "node_modules/dot-case": { @@ -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" @@ -7102,19 +7162,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/js2xmlparser": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", @@ -10306,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" @@ -10806,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" @@ -11078,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", @@ -11593,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": { @@ -11615,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 4671227b9..665c9071d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "optionalDependencies": { "canvg": "^3.0.6", "core-js": "^3.6.0", - "dompurify": "^2.5.4", + "dompurify": "^2.5.8", "html2canvas": "^1.0.0-rc.5" }, "devDependencies": { @@ -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 0e7a971624b228afcc19799a6de0cbc0a7ac4a89..654e08a32e70556c78bbf2cbd0d54c12989819f2 100644 GIT binary patch delta 133 zcmew)`AKraGftzd;s6&n1p_@G)a254E=o--$;{7lNi0cK&~ULbw6ricG%_@R0Br*U xbpwOV5?oW6T%kgC3N}y{PRdRpmdnnLtGFbwsHCDOHI2*2+`y1aRn^ts4FFsRAs_$% delta 116 zcmew)`AKraGft!BsS=j^=5BcxRi>lKrSDvnnpl#VpXZWTlB%H5sZ-^9s(1T`$V?s4 zE_r5m)%SZELtk!|;F`+B<>ct%=IHEdr(iSrAdi@;fq?;BB$mt0j;pvNv8be?C^e1C O$lSn?OI6j?-wgmRG$*kD diff --git a/test/reference/encrypted_standard.pdf b/test/reference/encrypted_standard.pdf index cdc5d3188880c1b9a70871b918d2754b247e82b8..ddf191b515046544c49dc1bf723d056bbd82fa0f 100644 GIT binary patch delta 134 zcmew)`AKraGfv~I;s6&n1p_@G)a254E=o--$;{7lNi0cK&~ULbw6ricG%_@R0Br*U wbs*X-$u)(^6)I+@U;}00r0f)8x$Nw?ic1oUN-By{)3}Vx4Gg(dRbBnv0C;gBNB{r; delta 117 zcmew)`AKraGfv~>N1870Utw|lRM8YoE`8^s)Wnj^{5+S$l2iqa&fW*hPw%k4c`|p( zysxpvErJ(WMV}tsv00L93KN%;ql=rPv#Xtg&E$hTVyXrP25^yBE;~D};*!Lol8U0# QG%h1^14AxVRabvE0NX_@mH+?% diff --git a/test/reference/encrypted_withAcroForm.pdf b/test/reference/encrypted_withAcroForm.pdf index 536d36f91fef348262278e1577240c1f01d7a864..1f40b46550aec8e9def8bdf6001530d5f1709508 100644 GIT binary patch literal 21122 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 ZVUHrTGhPgFHp8^Be!mt__51tN$tTg5qICcO delta 712 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 Fe*h9;PJ{pe diff --git a/test/reference/textfieldMultiline.pdf b/test/reference/textfieldMultiline.pdf index a78ceb310e7c7770280583cb73567d58e0409b84..efaa3a5371b0d9e287e1beed019ba6b7ece6f352 100644 GIT binary patch delta 149 zcmX>kb4X@`2)Cwz9uR7B={pytCYEI8=eZ=7q$+5*SQ%Pcm>U`y8bE-yfx%{5?&VBz qP(Dt|PQeDJ98|(iA(qR|j;pvNv8be?C^e1C*uc=3OI6j?-wgnWFd~fr delta 149 zcmX>kb4X@`2)Cw@o{64;CYQc*QEFmIW`3SaVo9okhKrSvfsvu1fuX6Pp`nSPxwe7v zW?Sy%OmR+TZsr!oZWeAX7G{nv#-?uO28K??mX4;*u8yWAP9}B=HiVVLa@pB&6_+Fy Vl~fd^rg0e?7#ee_s=E5S0RTG*C2jx! diff --git a/test/reference/textfieldMultilineSmallForm.pdf b/test/reference/textfieldMultilineSmallForm.pdf index e589d69905cc2e7becee0653ef4a4e6bce8c1633..fd2901d48c86467845034d0cd51e9d9665307c3e 100644 GIT binary patch delta 148 zcmZ1^yGV9J3Ad(!9uR7B={pytCYEI8=eZ=7q$+5*SQ%Pcm>U`y8bE-yfx+gv+$)%3 qp?qAFoq`Q6DVVsOLM)e^9anKlVo^y&QED2Ov7vz_m#V6(zZ(FayCUTP delta 148 zcmZ1^yGV9J3Ad(^o{64;CYQc*QEFmIW`3SaVo9okhKrSvfsvu1fsuisk%@_^rM7|b z=DFM}m|`8>99^7UObpzN94(AZ%?-_*3|vi|O$-f8%*|a)T>'); + 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"; }