diff --git a/examples/js/acroforms.js b/examples/js/acroforms.js index b4819172e..be2062d3d 100644 --- a/examples/js/acroforms.js +++ b/examples/js/acroforms.js @@ -8,72 +8,145 @@ var { TextField, PasswordField, RadioButton, - Appearance + Appearance, + TextFieldGroup } = 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(); +addTextFieldGroup(); + +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]; + 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.value = "RadioGroup1Option3"; + 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]; + radioButton3.AS = "/RadioGroup1Option3"; + + radioGroup.setAppearance(Appearance.RadioButton.Cross); + yPos += boxDim + 5; + + // Second radio group + var radioGroup2 = new RadioButton("RadioGroup2"); + radioGroup2.value = "RadioGroup2Option3"; + radioGroup2.fieldName = "RadioGroup2"; + + doc.addField(radioGroup2); + + var radioButton21 = radioGroup2.createOption("RadioGroup2Option1"); + radioButton21.Rect = [50, yPos, boxDim, boxDim]; + + 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); + yPos += margin + boxDim; +} + +function addTextFieldGroup() { + doc.text("TextField Group:", 10, yPos); + + const txtDate = new TextFieldGroup(); + txtDate.fieldName = "Date"; + txtDate.value = new Date().toLocaleDateString("en-US"); + doc.addField(txtDate); + + const txtDate1 = txtDate.createChild(); + txtDate1.Rect = [50, yPos - 5, 40, 10]; + + yPos += margin; + + const txtDate2 = txtDate.createChild(); + txtDate2.Rect = [50, yPos - 5, 40, 10]; +} diff --git a/package-lock.json b/package-lock.json index d898dc4a4..b453c3563 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,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" }, @@ -1650,6 +1650,64 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@koa/cors": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz", @@ -3218,9 +3276,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" @@ -10293,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" @@ -10793,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" @@ -11065,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", @@ -11580,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": { @@ -11602,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 9a13e6863..665c9071d 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "rollup-plugin-license": "^2.1.0", "rollup-plugin-node-resolve": "5.2.0", "rollup-plugin-preprocess": "0.0.4", - "rollup-plugin-terser": "^6.1.0", + "rollup-plugin-terser": "^7.0.2", "typescript": "^5.6.2", "yarpm": "^0.2.1" }, diff --git a/src/modules/acroform.js b/src/modules/acroform.js index d440d51f6..fe1b85c4d 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, "\\") @@ -170,14 +171,18 @@ var calculateAppearanceStream = function(formObject) { return formObject.appearanceStreamContent; } - if (!formObject.V && !formObject.DV) { - return; + var text; + if (formObject instanceof AcroFormTextFieldChild) { + text = formObject.Parent._V || formObject.Parent.DV; + } else { + text = formObject._V || formObject.DV; } - // else calculate it + if (!text) { + return; + } var stream = []; - var text = formObject._V || formObject.DV; var calcRes = calculateX(formObject, text); var fontKey = formObject.scope.internal.getFont( formObject.fontName, @@ -475,6 +480,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) @@ -673,8 +679,7 @@ var createXFormObjectCallback = function(fieldArray, scope) { } }; -var initializeAcroForm = function(scope, formObject) { - formObject.scope = scope; +var initializeAcroForm = function(scope) { if ( scope.internal !== undefined && (scope.internal.acroformPlugin === undefined || @@ -759,6 +764,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 +774,7 @@ function getMatches(string, regex, index) { } return matches; } + var pdfArrayToStringArray = function(array) { var result = []; if (typeof array === "string") { @@ -938,7 +945,6 @@ var AcroFormXObject = function() { } }); }; - inherit(AcroFormXObject, AcroFormPDFObject); var AcroFormDictionary = function() { @@ -985,7 +991,6 @@ var AcroFormDictionary = function() { } }); }; - inherit(AcroFormDictionary, AcroFormPDFObject); /** @@ -1184,25 +1189,34 @@ var AcroFormField = function() { }); var _T = null; - Object.defineProperty(this, "T", { enumerable: true, configurable: false, get: function() { if (!_T || _T.length < 1) { - // In case of a Child from a Radio´Group, you don't need a FieldName - if (this instanceof AcroFormChildClass) { + // In case of a Child, you don't need a FieldName + if ( + this instanceof AcroFormChildClass || // Radio Group Child + this instanceof AcroFormTextFieldChild + ) { return undefined; } + _T = "FieldObject" + AcroFormField.FieldNum++; } + var encryptor = function(data) { return data; }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); return "(" + pdfEscape(encryptor(_T)) + ")"; }, set: function(value) { + if (this instanceof AcroFormTextFieldChild) { + return; + } + _T = value.toString(); } }); @@ -1415,6 +1429,7 @@ var AcroFormField = function() { this.V = value; } }); + Object.defineProperty(this, "V", { enumerable: false, configurable: false, @@ -1514,8 +1529,7 @@ var AcroFormField = function() { return _hasAppearanceStream; }, set: function(value) { - value = Boolean(value); - _hasAppearanceStream = value; + _hasAppearanceStream = Boolean(value); } }); @@ -2332,11 +2346,10 @@ AcroFormRadioButton.prototype.createOption = function(name) { var child = new AcroFormChildClass(); child.Parent = this; child.optionName = name; + // Add to Parent this.Kids.push(child); - addField.call(this.scope, child); - return child; }; @@ -2526,12 +2539,114 @@ var AcroFormTextField = function() { enumerable: true, configurable: true, get: function() { - return this.V || this.DV; + return Boolean(this.V || this.DV); } }); }; inherit(AcroFormTextField, AcroFormField); +/* + PDF 32000-1:2008, 12.7.3.2, “Field Names”, page 434 + It is possible for different field dictionaries to have the same fully qualified field name if they are descendants of a common ancestor with that name and have no partial field names (T entries) of their own. +*/ + +/** + * Creates a TextField without an appearance to serve as the common ancestor for a group of child TextFields that share the same fully qualified field name and the same value. Changing the value of one will change the value of all the others. + * + * @class AcroFormTextFieldGroup + * @extends AcroFormTextField + * @extends AcroFormField + */ +var AcroFormTextFieldGroup = function() { + AcroFormTextField.call(this); + this.F = null; + + var _Kids = []; + Object.defineProperty(this, "Kids", { + enumerable: true, + configurable: false, + get: function() { + return _Kids; + }, + set: function(value) { + if (typeof value !== "undefined") { + _Kids = value; + } else { + _Kids = []; + } + } + }); + + Object.defineProperty(this, "hasAppearanceStream", { + enumerable: true, + configurable: true, + get: function() { + return false; + } + }); +}; +inherit(AcroFormTextFieldGroup, AcroFormTextField); + +/** + * The Child TextField of a TextFieldGroup + * + * @class AcroFormTextFieldChild + * @extends AcroFormTextField + * @extends AcroFormField + */ +var AcroFormTextFieldChild = function() { + AcroFormTextField.call(this); + + var _parent; + Object.defineProperty(this, "Parent", { + enumerable: false, + configurable: false, + get: function() { + return _parent; + }, + set: function(value) { + _parent = value; + } + }); + + Object.defineProperty(this, "hasAppearanceStream", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(this.Parent.V || this.Parent.DV); + } + }); + + Object.defineProperty(this, "value", { + enumerable: true, + configurable: true, + get: function() { + return this.Parent.value; + }, + set: function(value) { + this.Parent.value = value; + } + }); +}; +inherit(AcroFormTextFieldChild, AcroFormTextField); + +/** + * Creates a new TextFieldChild belonging to the parent TextFieldGroup + * + * @function AcroFormTextFieldGroup.createChild + * @returns {AcroFormTextFieldChild} + */ +AcroFormTextFieldGroup.prototype.createChild = function() { + var child = new AcroFormTextFieldChild(); + child.Parent = this; + child.BS = Object.assign({}, child.Parent._BS); + child.MK = Object.assign({}, child.Parent._MK); + + this.Kids.push(child); + addField.call(this.scope, child); + return child; +}; + /** * @class AcroFormPasswordField * @extends AcroFormTextField @@ -3081,6 +3196,7 @@ AcroFormAppearance.internal = { return cross; } }; + AcroFormAppearance.internal.getWidth = function(formObject) { var result = 0; if (typeof formObject === "object") { @@ -3088,8 +3204,10 @@ AcroFormAppearance.internal.getWidth = function(formObject) { } return result; }; + AcroFormAppearance.internal.getHeight = function(formObject) { var result = 0; + if (typeof formObject === "object") { result = scale(formObject.Rect[3]); } @@ -3108,14 +3226,15 @@ AcroFormAppearance.internal.getHeight = function(formObject) { * @returns {jsPDF} */ var addField = (jsPDFAPI.addField = function(fieldObject) { - initializeAcroForm(this, fieldObject); + initializeAcroForm(this); - if (fieldObject instanceof AcroFormField) { - putForm(fieldObject); - } else { + if (!(fieldObject instanceof AcroFormField)) { throw new Error("Invalid argument passed to jsPDF.addField."); } - fieldObject.page = fieldObject.scope.internal.getCurrentPageInfo().pageNumber; + + fieldObject.scope = this; + fieldObject.page = this.internal.getCurrentPageInfo().pageNumber; + putForm(fieldObject); return this; }); @@ -3128,6 +3247,7 @@ jsPDFAPI.AcroFormPushButton = AcroFormPushButton; jsPDFAPI.AcroFormRadioButton = AcroFormRadioButton; jsPDFAPI.AcroFormCheckBox = AcroFormCheckBox; jsPDFAPI.AcroFormTextField = AcroFormTextField; +jsPDFAPI.AcroFormTextFieldGroup = AcroFormTextFieldGroup; jsPDFAPI.AcroFormPasswordField = AcroFormPasswordField; jsPDFAPI.AcroFormAppearance = AcroFormAppearance; @@ -3141,6 +3261,7 @@ jsPDFAPI.AcroForm = { RadioButton: AcroFormRadioButton, CheckBox: AcroFormCheckBox, TextField: AcroFormTextField, + TextFieldGroup: AcroFormTextFieldGroup, PasswordField: AcroFormPasswordField, Appearance: AcroFormAppearance }; @@ -3155,6 +3276,7 @@ jsPDF.AcroForm = { RadioButton: AcroFormRadioButton, CheckBox: AcroFormCheckBox, TextField: AcroFormTextField, + TextFieldGroup: AcroFormTextFieldGroup, PasswordField: AcroFormPasswordField, Appearance: AcroFormAppearance }; @@ -3172,6 +3294,7 @@ export { AcroFormRadioButton, AcroFormCheckBox, AcroFormTextField, + AcroFormTextFieldGroup, AcroFormPasswordField, AcroFormAppearance }; diff --git a/test/deployment/amd/loadGlobals.js b/test/deployment/amd/loadGlobals.js index d3bb38d11..a3db26dd9 100644 --- a/test/deployment/amd/loadGlobals.js +++ b/test/deployment/amd/loadGlobals.js @@ -25,6 +25,7 @@ window.loadGlobals = function loadGlobals() { window.PushButton = jsPDF.AcroFormPushButton; window.RadioButton = jsPDF.AcroFormRadioButton; window.CheckBox = jsPDF.AcroFormCheckBox; + window.TextFieldGroup = jsPDF.AcroFormTextFieldGroup; window.TextField = jsPDF.AcroFormTextField; window.PasswordField = jsPDF.AcroFormPasswordField; window.Appearance = jsPDF.AcroFormAppearance; diff --git a/test/deployment/esm/asyncImportHelper.js b/test/deployment/esm/asyncImportHelper.js index 308864fc8..e696ecd5a 100644 --- a/test/deployment/esm/asyncImportHelper.js +++ b/test/deployment/esm/asyncImportHelper.js @@ -10,6 +10,7 @@ import { AcroFormRadioButton, AcroFormCheckBox, AcroFormTextField, + AcroFormTextFieldGroup, AcroFormPasswordField, AcroFormAppearance } from "../../../dist/jspdf.es.js"; @@ -26,6 +27,7 @@ window.importsReady({ AcroFormRadioButton, AcroFormCheckBox, AcroFormTextField, + AcroFormTextFieldGroup, AcroFormPasswordField, AcroFormAppearance }); diff --git a/test/deployment/esm/loadGlobals.js b/test/deployment/esm/loadGlobals.js index a2c580ee4..dbef5cb0e 100644 --- a/test/deployment/esm/loadGlobals.js +++ b/test/deployment/esm/loadGlobals.js @@ -21,6 +21,7 @@ window.loadGlobals = async function loadGlobals() { window.PushButton = jsPDF.AcroFormPushButton; window.RadioButton = jsPDF.AcroFormRadioButton; window.CheckBox = jsPDF.AcroFormCheckBox; + window.TextFieldGroup = jsPDF.AcroFormTextFieldGroup; window.TextField = jsPDF.AcroFormTextField; window.PasswordField = jsPDF.AcroFormPasswordField; window.Appearance = jsPDF.AcroFormAppearance; diff --git a/test/deployment/globals/loadGlobals.js b/test/deployment/globals/loadGlobals.js index 83214f74c..4e2f35ef6 100644 --- a/test/deployment/globals/loadGlobals.js +++ b/test/deployment/globals/loadGlobals.js @@ -14,6 +14,7 @@ window.loadGlobals = function loadGlobals() { window.RadioButton = jsPDF.AcroFormRadioButton; window.CheckBox = jsPDF.AcroFormCheckBox; window.TextField = jsPDF.AcroFormTextField; + window.TextFieldGroup = jsPDF.AcroFormTextFieldGroup; window.PasswordField = jsPDF.AcroFormPasswordField; window.Appearance = jsPDF.AcroFormAppearance; window.Canvg = window.canvg.Canvg; diff --git a/test/deployment/node/loadGlobals.js b/test/deployment/node/loadGlobals.js index 2e6f875ff..aa85e9268 100644 --- a/test/deployment/node/loadGlobals.js +++ b/test/deployment/node/loadGlobals.js @@ -15,6 +15,7 @@ global.loadGlobals = function() { global.PushButton = jsPDF.AcroFormPushButton; global.RadioButton = jsPDF.AcroFormRadioButton; global.CheckBox = jsPDF.AcroFormCheckBox; + global.TextFieldGroup = jsPDF.AcroFormTextFieldGroup; global.TextField = jsPDF.AcroFormTextField; global.PasswordField = jsPDF.AcroFormPasswordField; global.Appearance = jsPDF.AcroFormAppearance; diff --git a/test/reference/textfieldGroup.pdf b/test/reference/textfieldGroup.pdf new file mode 100644 index 000000000..6934946cf Binary files /dev/null and b/test/reference/textfieldGroup.pdf differ diff --git a/test/saucelabs/loadGlobals.js b/test/saucelabs/loadGlobals.js index 83214f74c..4e2f35ef6 100644 --- a/test/saucelabs/loadGlobals.js +++ b/test/saucelabs/loadGlobals.js @@ -14,6 +14,7 @@ window.loadGlobals = function loadGlobals() { window.RadioButton = jsPDF.AcroFormRadioButton; window.CheckBox = jsPDF.AcroFormCheckBox; window.TextField = jsPDF.AcroFormTextField; + window.TextFieldGroup = jsPDF.AcroFormTextFieldGroup; window.PasswordField = jsPDF.AcroFormPasswordField; window.Appearance = jsPDF.AcroFormAppearance; window.Canvg = window.canvg.Canvg; diff --git a/test/specs/acroform.spec.js b/test/specs/acroform.spec.js index b7582a826..837a3a44a 100644 --- a/test/specs/acroform.spec.js +++ b/test/specs/acroform.spec.js @@ -1,5 +1,5 @@ /* eslint-disable no-self-assign */ -/* global describe, it, expect, jsPDF, comparePdf, Button, ComboBox, ChoiceField, EditBox, ListBox, PushButton, CheckBox, TextField, PasswordField, RadioButton, AcroForm */ +/* global describe, it, expect, jsPDF, comparePdf, Button, ComboBox, ChoiceField, EditBox, ListBox, PushButton, CheckBox, TextField, TextFieldGroup, PasswordField, RadioButton, AcroForm */ /** * Acroform testing @@ -214,7 +214,7 @@ describe("Module: Acroform Unit Test", function() { it("AcroFormField defaultValue", function() { var formObject = new TextField(); - + formObject.defaultValue = "test1"; expect(formObject.defaultValue).toEqual("test1"); expect(formObject.DV).toEqual("(test1)"); @@ -285,6 +285,7 @@ describe("Module: Acroform Unit Test", function() { expect(radioButton1.AS).toEqual("/Test"); }); + it("AcroFormField appearanceState", function() { var doc = new jsPDF({ orientation: "p", @@ -447,7 +448,7 @@ describe("Module: Acroform Unit Test", function() { field = new TextField(); expect(field.Ff).toEqual(0); - field = new TextField(); + field = new TextField(); expect(field.Ff).toEqual(0); expect(field.readOnly).toEqual(false); field.readOnly = true; @@ -537,6 +538,7 @@ describe("Module: Acroform Unit Test", function() { expect(field.richText).toEqual(false); expect(field.Ff).toEqual(0); }); + it("AcroFormComboBox", function() { expect(new ComboBox().combo).toEqual(true); var field = new ComboBox(); @@ -595,6 +597,7 @@ describe("Module: Acroform Unit Test", function() { expect(field.combo).toEqual(true); expect(field.edit).toEqual(true); }); + it("AcroFormButton", function() { expect(new Button().FT).toEqual("/Btn"); @@ -766,16 +769,48 @@ describe("Module: Acroform Unit Test", function() { expect(field.password).toEqual(true); expect(field.Ff).toEqual(Math.pow(2, 13)); }); + it("AcroFormPushButton", function() { expect(new PushButton().pushButton).toEqual(true); expect(new PushButton() instanceof Button).toEqual(true); }); + it("ComboBox TopIndex", function() { var comboBox = new ComboBox(); expect(comboBox.topIndex).toEqual(0); comboBox.topIndex = 1; expect(comboBox.topIndex).toEqual(1); }); + + it("AcroFormTextFieldGroup, AcroFormTextFieldChild", function() { + const textFieldGroup = new TextFieldGroup(); + textFieldGroup.fieldName = "LastName"; + textFieldGroup.value = "Smith"; + + expect(textFieldGroup.F).toEqual(null); + expect(textFieldGroup.Kids).toEqual([]); + expect(textFieldGroup.hasAppearanceStream).toEqual(false); + + const doc = new jsPDF({ + orientation: "p", + unit: "mm", + format: "a4", + floatPrecision: 2 + }); + + doc.addField(textFieldGroup); + + const child1 = textFieldGroup.createChild(); + expect(child1.value).toEqual("Smith"); + expect(child1.hasAppearanceStream).toEqual(true); + + const child2 = textFieldGroup.createChild(); + expect(child2.value).toEqual("Smith"); + + child2.value = "Jones"; + expect(child1.value).toEqual("Jones"); + expect(textFieldGroup.value).toEqual("Jones"); + }); }); describe("Module: Acroform Integration Test", function() { @@ -958,6 +993,52 @@ describe("Module: Acroform Integration Test", function() { comparePdf(doc.output(), "textfield.pdf", "acroform"); }); + it("should add a TextFieldGroup", function() { + const doc = new jsPDF({ + format: "a6", + orientation: "landscape", + unit: "mm", + floatPrecision: 2 + }); + + const txtNameGroup = new TextFieldGroup(); + txtNameGroup.fieldName = "Name"; + txtNameGroup.value = "Smith, Robert"; + doc.addField(txtNameGroup); + + const txtDateGroup = new TextFieldGroup(); + txtDateGroup.fieldName = "Date"; + txtDateGroup.value = "12/31/2033"; + doc.addField(txtDateGroup); + + addHeader(); + doc.addPage(); + addHeader(); + + comparePdf(doc.output(), "textfieldGroup.pdf", "acroform"); + + function addHeader() { + let yPos = 9; + let xPos = 5; + let txtChild; + + doc.text("Name:", xPos, yPos); + xPos += doc.getTextWidth("Name:") + 1; + txtChild = txtNameGroup.createChild(); + txtChild.Rect = [xPos, yPos - 6, 60, 9]; + + xPos += 70; + + doc.text("Date:", xPos, yPos); + xPos += doc.getTextWidth("Date:") + 1; + txtChild = txtDateGroup.createChild(); + txtChild.Rect = [xPos, yPos - 6, 35, 9]; + + yPos += 5; + doc.line(0, yPos, doc.internal.pageSize.width, yPos); + } + }); + it("should add a Password", function() { var doc = new jsPDF({ orientation: "p", diff --git a/test/unit/asyncImportHelper.js b/test/unit/asyncImportHelper.js index 5499019e3..85094ba7a 100644 --- a/test/unit/asyncImportHelper.js +++ b/test/unit/asyncImportHelper.js @@ -10,6 +10,7 @@ import { AcroFormRadioButton, AcroFormCheckBox, AcroFormTextField, + AcroFormTextFieldGroup, AcroFormPasswordField, AcroFormAppearance } from "/base/src/index.js"; @@ -26,6 +27,7 @@ window.importsReady({ AcroFormRadioButton, AcroFormCheckBox, AcroFormTextField, + AcroFormTextFieldGroup, AcroFormPasswordField, AcroFormAppearance }); diff --git a/test/unit/loadGlobals.js b/test/unit/loadGlobals.js index a2c580ee4..5745fba8f 100644 --- a/test/unit/loadGlobals.js +++ b/test/unit/loadGlobals.js @@ -22,6 +22,7 @@ window.loadGlobals = async function loadGlobals() { window.RadioButton = jsPDF.AcroFormRadioButton; window.CheckBox = jsPDF.AcroFormCheckBox; window.TextField = jsPDF.AcroFormTextField; + window.TextFieldGroup = jsPDF.AcroFormTextFieldGroup; window.PasswordField = jsPDF.AcroFormPasswordField; window.Appearance = jsPDF.AcroFormAppearance; window.Canvg = window.canvg.Canvg; diff --git a/types/index.d.ts b/types/index.d.ts index 8a9553073..a199f0d8d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -367,6 +367,17 @@ declare module "jspdf" { hasAppearanceStream: boolean; } + export class AcroFormTextFieldGroup {} + export interface AcroFormTextFieldGroup extends AcroFormTextField { + Kids: AcroFormTextFieldChild[]; + createChild(): AcroFormTextFieldChild; + } + + export class AcroFormTextFieldChild {} + export interface AcroFormTextFieldChild extends AcroFormTextField { + Parent: AcroFormTextFieldGroup; + } + export class AcroFormPasswordField {} export interface AcroFormPasswordField extends AcroFormTextField {} // jsPDF plugin: Context2D