diff --git a/src/core/annotation.js b/src/core/annotation.js index 1e12c9536..2e4cbbb62 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -300,7 +300,6 @@ class Annotation { _isViewable(flags) { return ( !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && - !this._hasFlag(flags, AnnotationFlag.HIDDEN) && !this._hasFlag(flags, AnnotationFlag.NOVIEW) ); } @@ -311,11 +310,18 @@ class Annotation { _isPrintable(flags) { return ( this._hasFlag(flags, AnnotationFlag.PRINT) && - !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && - !this._hasFlag(flags, AnnotationFlag.HIDDEN) + !this._hasFlag(flags, AnnotationFlag.INVISIBLE) ); } + isHidden(annotationStorage) { + const data = annotationStorage && annotationStorage[this.data.id]; + if (data && "hidden" in data) { + return data.hidden; + } + return this._hasFlag(this.flags, AnnotationFlag.HIDDEN); + } + /** * @type {boolean} */ @@ -984,7 +990,7 @@ class WidgetAnnotation extends Annotation { } data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY); - data.hidden = this.hasFieldFlag(AnnotationFieldFlag.HIDDEN); + data.hidden = this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN); // Hide signatures because we cannot validate them, and unset the fieldValue // since it's (most likely) a `Dict` which is non-serializable and will thus @@ -1145,7 +1151,8 @@ class WidgetAnnotation extends Annotation { } async save(evaluator, task, annotationStorage) { - const value = annotationStorage[this.data.id]; + const value = + annotationStorage[this.data.id] && annotationStorage[this.data.id].value; if (value === this.data.fieldValue || value === undefined) { return null; } @@ -1229,7 +1236,8 @@ class WidgetAnnotation extends Annotation { if (!annotationStorage || isPassword) { return null; } - const value = annotationStorage[this.data.id]; + const value = + annotationStorage[this.data.id] && annotationStorage[this.data.id].value; if (value === undefined) { // The annotation hasn't been rendered so use the appearance return null; @@ -1712,7 +1720,9 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } if (annotationStorage) { - const value = annotationStorage[this.data.id]; + const value = + annotationStorage[this.data.id] && + annotationStorage[this.data.id].value; if (value === undefined) { return super.getOperatorList( evaluator, @@ -1767,7 +1777,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } async _saveCheckbox(evaluator, task, annotationStorage) { - const value = annotationStorage[this.data.id]; + const value = + annotationStorage[this.data.id] && annotationStorage[this.data.id].value; if (value === undefined) { return null; } @@ -1809,7 +1820,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } async _saveRadioButton(evaluator, task, annotationStorage) { - const value = annotationStorage[this.data.id]; + const value = + annotationStorage[this.data.id] && annotationStorage[this.data.id].value; if (value === undefined) { return null; } diff --git a/src/core/document.js b/src/core/document.js index 1c509b235..1cce067ab 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -347,7 +347,10 @@ class Page { // is resolved with the complete operator list for a single annotation. const opListPromises = []; for (const annotation of annotations) { - if (isAnnotationRenderable(annotation, intent)) { + if ( + isAnnotationRenderable(annotation, intent) && + !annotation.isHidden(annotationStorage) + ) { opListPromises.push( annotation .getOperatorList( diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 1220ec4ea..26271cbe6 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -494,7 +494,9 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { // NOTE: We cannot set the values using `element.value` below, since it // prevents the AnnotationLayer rasterizer in `test/driver.js` // from parsing the elements correctly for the reference tests. - const textContent = storage.getOrCreateValue(id, this.data.fieldValue); + const textContent = storage.getOrCreateValue(id, { + value: this.data.fieldValue, + }).value; if (this.data.multiLine) { element = document.createElement("textarea"); @@ -509,7 +511,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { element.setAttribute("id", id); element.addEventListener("input", function (event) { - storage.setValue(id, event.target.value); + storage.setValue(id, { value: event.target.value }); }); element.addEventListener("blur", function (event) { @@ -686,10 +688,9 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { const storage = this.annotationStorage; const data = this.data; const id = data.id; - const value = storage.getOrCreateValue( - id, - data.fieldValue && data.fieldValue !== "Off" - ); + const value = storage.getOrCreateValue(id, { + value: data.fieldValue && data.fieldValue !== "Off", + }).value; this.container.className = "buttonWidgetAnnotation checkBox"; @@ -702,7 +703,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { } element.addEventListener("change", function (event) { - storage.setValue(id, event.target.checked); + storage.setValue(id, { value: event.target.checked }); }); this.container.appendChild(element); @@ -728,10 +729,9 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { const storage = this.annotationStorage; const data = this.data; const id = data.id; - const value = storage.getOrCreateValue( - id, - data.fieldValue === data.buttonValue - ); + const value = storage.getOrCreateValue(id, { + value: data.fieldValue === data.buttonValue, + }).value; const element = document.createElement("input"); element.disabled = data.readOnly; @@ -747,11 +747,11 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { if (radio !== event.target) { storage.setValue( radio.parentNode.getAttribute("data-annotation-id"), - false + { value: false } ); } } - storage.setValue(id, event.target.checked); + storage.setValue(id, { value: event.target.checked }); }); this.container.appendChild(element); @@ -808,10 +808,10 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { // two field types is implemented, we should use the same pattern as the // other interactive widgets where the return value of `getOrCreateValue` is // used and the full array of field values is stored. - storage.getOrCreateValue( - id, - this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined - ); + storage.getOrCreateValue(id, { + value: + this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined, + }); const selectElement = document.createElement("select"); selectElement.disabled = this.data.readOnly; @@ -839,7 +839,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { selectElement.addEventListener("input", function (event) { const options = event.target.options; const value = options[options.selectedIndex].value; - storage.setValue(id, value); + storage.setValue(id, { value }); }); this.container.appendChild(selectElement); @@ -1684,6 +1684,9 @@ class AnnotationLayer { }); if (element.isRenderable) { const rendered = element.render(); + if (data.hidden) { + rendered.style.visibility = "hidden"; + } if (Array.isArray(rendered)) { for (const renderedElement of rendered) { parameters.div.appendChild(renderedElement); diff --git a/src/display/annotation_storage.js b/src/display/annotation_storage.js index 3bd751192..abc6dacfc 100644 --- a/src/display/annotation_storage.js +++ b/src/display/annotation_storage.js @@ -59,10 +59,22 @@ class AnnotationStorage { * @param {Object} value */ setValue(key, value) { - if (this._storage.get(key) !== value) { + const obj = this._storage.get(key); + let modified = false; + if (obj !== undefined) { + for (const [entry, val] of Object.entries(value)) { + if (obj[entry] !== val) { + modified = true; + obj[entry] = val; + } + } + } else { + this._storage.set(key, value); + modified = true; + } + if (modified) { this._setModified(); } - this._storage.set(key, value); } getAll() { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index a0516955d..82936b8a7 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -179,6 +179,7 @@ !devicen.pdf !cmykjpeg.pdf !issue840.pdf +!160F-2019.pdf !issue4402_reduced.pdf !issue845r.pdf !issue3405r.pdf diff --git a/test/pdfs/160F-2019.pdf b/test/pdfs/160F-2019.pdf new file mode 100644 index 000000000..76e8430a4 Binary files /dev/null and b/test/pdfs/160F-2019.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 1eb51b4de..935427136 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -2214,6 +2214,19 @@ "lastPage": 1, "type": "load" }, + { "id": "160F-2019", + "file": "pdfs/160F-2019.pdf", + "md5": "71591f11ee717e12887f529c84d5ae89", + "rounds": 1, + "type": "eq", + "print": true, + "annotationStorage": { + "427R": { + "hidden": false, + "value": "hello world" + } + } + }, { "id": "issue6342-eq", "file": "pdfs/issue6342.pdf", "md5": "2ea85ca8d17117798f105be88bdb2bfd", @@ -2818,7 +2831,9 @@ "type": "eq", "print": true, "annotationStorage": { - "2795R": "氏 名 又 は 名 称 Full name" + "2795R": { + "value": "氏 名 又 は 名 称 Full name" + } } }, { "id": "issue7598", @@ -3682,7 +3697,9 @@ "type": "eq", "print": true, "annotationStorage": { - "1605R": true + "1605R": { + "value": true + } } }, { "id": "clippath", @@ -4186,11 +4203,21 @@ "type": "eq", "print": true, "annotationStorage": { - "29R": true, - "33R": true, - "37R": true, - "65R": true, - "69R": true + "29R": { + "value": true + }, + "33R": { + "value": true + }, + "37R": { + "value": true + }, + "65R": { + "value": true + }, + "69R": { + "value": true + } } }, { "id": "issue1171.pdf", @@ -4584,13 +4611,27 @@ "type": "eq", "print": true, "annotationStorage": { - "61R": "Single line, unlimited length", - "62R": "Single lin", - "63R": "Single line, center aligned", - "64R": "Single line, right aligned", - "65R": "", - "66R": "zyxwvutsrqponmlkjihgfedcba", - "67R": "Multiline\nstring" + "61R": { + "value": "Single line, unlimited length" + }, + "62R": { + "value": "Single lin" + }, + "63R": { + "value": "Single line, center aligned" + }, + "64R": { + "value": "Single line, right aligned" + }, + "65R": { + "value": "" + }, + "66R": { + "value": "zyxwvutsrqponmlkjihgfedcba" + }, + "67R": { + "value": "Multiline\nstring" + } } }, { "id": "annotation-choice-widget-annotations", @@ -4614,11 +4655,21 @@ "type": "eq", "print": true, "annotationStorage": { - "57R": "Ipsum", - "58R": "Lorem", - "59R": "Dolor", - "62R": "Sit", - "63R": "" + "57R": { + "value": "Ipsum" + }, + "58R": { + "value": "Lorem" + }, + "59R": { + "value": "Dolor" + }, + "62R": { + "value": "Sit" + }, + "63R": { + "value": "" + } } }, { "id": "issue12233-forms", @@ -4637,7 +4688,9 @@ "type": "eq", "print": true, "annotationStorage": { - "20R": true + "20R": { + "value": true + } } }, { "id": "issue11931", @@ -4673,15 +4726,33 @@ "type": "eq", "print": true, "annotationStorage": { - "105R": true, - "106R": false, - "107R": false, - "108R": true, - "109R": false, - "110R": false, - "111R": true, - "112R": false, - "113R": false + "105R": { + "value": true + }, + "106R": { + "value": false + }, + "107R": { + "value": false + }, + "108R": { + "value": true + }, + "109R": { + "value": false + }, + "110R": { + "value": false + }, + "111R": { + "value": true + }, + "112R": { + "value": false + }, + "113R": { + "value": false + } } }, { "id": "annotation-polyline-polygon", diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 55a5157d2..d72a74aab 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -1602,7 +1602,7 @@ describe("annotation", function () { .then(annotation => { const id = annotation.data.id; const annotationStorage = {}; - annotationStorage[id] = "test\\print"; + annotationStorage[id] = { value: "test\\print" }; return annotation._getAppearance( partialEvaluator, task, @@ -1687,7 +1687,7 @@ describe("annotation", function () { .then(annotation => { const id = annotation.data.id; const annotationStorage = {}; - annotationStorage[id] = "test (print)"; + annotationStorage[id] = { value: "test (print)" }; return annotation._getAppearance( partialEvaluator, task, @@ -1723,7 +1723,7 @@ describe("annotation", function () { .then(annotation => { const id = annotation.data.id; const annotationStorage = {}; - annotationStorage[id] = "mypassword"; + annotationStorage[id] = { value: "mypassword" }; return annotation._getAppearance( partialEvaluator, task, @@ -1756,9 +1756,11 @@ describe("annotation", function () { .then(annotation => { const id = annotation.data.id; const annotationStorage = {}; - annotationStorage[id] = - "a aa aaa aaaa aaaaa aaaaaa " + - "pneumonoultramicroscopicsilicovolcanoconiosis"; + annotationStorage[id] = { + value: + "a aa aaa aaaa aaaaa aaaaaa " + + "pneumonoultramicroscopicsilicovolcanoconiosis", + }; return annotation._getAppearance( partialEvaluator, task, @@ -1823,15 +1825,17 @@ describe("annotation", function () { .then(annotation => { const id = annotation.data.id; const annotationStorage = {}; - annotationStorage[id] = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r" + - "Aliquam vitae felis ac lectus bibendum ultricies quis non diam.\n" + - "Morbi id porttitor quam, a iaculis dui.\r\n" + - "Pellentesque habitant morbi tristique senectus et " + - "netus et malesuada fames ac turpis egestas.\n\r\n\r" + - "Nulla consectetur, ligula in tincidunt placerat, " + - "velit augue consectetur orci, sed mattis libero nunc ut massa.\r" + - "Etiam facilisis tempus interdum."; + annotationStorage[id] = { + value: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r" + + "Aliquam vitae felis ac lectus bibendum ultricies quis non diam.\n" + + "Morbi id porttitor quam, a iaculis dui.\r\n" + + "Pellentesque habitant morbi tristique senectus et " + + "netus et malesuada fames ac turpis egestas.\n\r\n\r" + + "Nulla consectetur, ligula in tincidunt placerat, " + + "velit augue consectetur orci, sed mattis libero nunc ut massa.\r" + + "Etiam facilisis tempus interdum.", + }; return annotation._getAppearance( partialEvaluator, task, @@ -1865,7 +1869,7 @@ describe("annotation", function () { .then(annotation => { const id = annotation.data.id; const annotationStorage = {}; - annotationStorage[id] = "aa(aa)a\\"; + annotationStorage[id] = { value: "aa(aa)a\\" }; return annotation._getAppearance( partialEvaluator, task, @@ -1898,7 +1902,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = "hello world"; + annotationStorage[annotation.data.id] = { value: "hello world" }; return annotation.save(partialEvaluator, task, annotationStorage); }, done.fail) .then(data => { @@ -2151,7 +2155,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return annotation.getOperatorList( partialEvaluator, task, @@ -2209,7 +2213,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return Promise.all([ annotation, annotation.getOperatorList( @@ -2234,7 +2238,7 @@ describe("annotation", function () { }, done.fail) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = false; + annotationStorage[annotation.data.id] = { value: false }; return annotation.getOperatorList( partialEvaluator, task, @@ -2292,7 +2296,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return Promise.all([ annotation, annotation.getOperatorList( @@ -2317,7 +2321,7 @@ describe("annotation", function () { }) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return annotation.getOperatorList( partialEvaluator, task, @@ -2424,7 +2428,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return Promise.all([ annotation, annotation.save(partialEvaluator, task, annotationStorage), @@ -2443,7 +2447,7 @@ describe("annotation", function () { }, done.fail) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = false; + annotationStorage[annotation.data.id] = { value: false }; return annotation.save(partialEvaluator, task, annotationStorage); }, done.fail) .then(data => { @@ -2586,7 +2590,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return Promise.all([ annotation, annotation.getOperatorList( @@ -2611,7 +2615,7 @@ describe("annotation", function () { }, done.fail) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = false; + annotationStorage[annotation.data.id] = { value: false }; return annotation.getOperatorList( partialEvaluator, task, @@ -2729,7 +2733,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return Promise.all([ annotation, annotation.save(partialEvaluator, task, annotationStorage), @@ -2755,7 +2759,7 @@ describe("annotation", function () { }, done.fail) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = false; + annotationStorage[annotation.data.id] = { value: false }; return annotation.save(partialEvaluator, task, annotationStorage); }, done.fail) .then(data => { @@ -2800,7 +2804,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = true; + annotationStorage[annotation.data.id] = { value: true }; return Promise.all([ annotation, annotation.save(partialEvaluator, task, annotationStorage), @@ -3247,7 +3251,7 @@ describe("annotation", function () { .then(annotation => { const id = annotation.data.id; const annotationStorage = {}; - annotationStorage[id] = "a value"; + annotationStorage[id] = { value: "a value" }; return annotation._getAppearance( partialEvaluator, task, @@ -3282,7 +3286,7 @@ describe("annotation", function () { ) .then(annotation => { const annotationStorage = {}; - annotationStorage[annotation.data.id] = "C"; + annotationStorage[annotation.data.id] = { value: "C" }; return annotation.save(partialEvaluator, task, annotationStorage); }, done.fail) .then(data => { diff --git a/test/unit/annotation_storage_spec.js b/test/unit/annotation_storage_spec.js index 1f55da3f9..f8fa46abd 100644 --- a/test/unit/annotation_storage_spec.js +++ b/test/unit/annotation_storage_spec.js @@ -19,12 +19,16 @@ describe("AnnotationStorage", function () { describe("GetOrCreateValue", function () { it("should get and set a new value in the annotation storage", function (done) { const annotationStorage = new AnnotationStorage(); - let value = annotationStorage.getOrCreateValue("123A", "hello world"); + let value = annotationStorage.getOrCreateValue("123A", { + value: "hello world", + }).value; expect(value).toEqual("hello world"); // the second argument is the default value to use // if the key isn't in the storage - value = annotationStorage.getOrCreateValue("123A", "an other string"); + value = annotationStorage.getOrCreateValue("123A", { + value: "an other string", + }).value; expect(value).toEqual("hello world"); done(); }); @@ -33,8 +37,8 @@ describe("AnnotationStorage", function () { describe("SetValue", function () { it("should set a new value in the annotation storage", function (done) { const annotationStorage = new AnnotationStorage(); - annotationStorage.setValue("123A", "an other string"); - const value = annotationStorage.getAll()["123A"]; + annotationStorage.setValue("123A", { value: "an other string" }); + const value = annotationStorage.getAll()["123A"].value; expect(value).toEqual("an other string"); done(); }); @@ -46,15 +50,15 @@ describe("AnnotationStorage", function () { called = true; }; annotationStorage.onSetModified = callback; - annotationStorage.getOrCreateValue("asdf", "original"); + annotationStorage.getOrCreateValue("asdf", { value: "original" }); expect(called).toBe(false); // not changing value - annotationStorage.setValue("asdf", "original"); + annotationStorage.setValue("asdf", { value: "original" }); expect(called).toBe(false); // changing value - annotationStorage.setValue("asdf", "modified"); + annotationStorage.setValue("asdf", { value: "modified" }); expect(called).toBe(true); done(); }); @@ -68,15 +72,15 @@ describe("AnnotationStorage", function () { called = true; }; annotationStorage.onResetModified = callback; - annotationStorage.getOrCreateValue("asdf", "original"); + annotationStorage.getOrCreateValue("asdf", { value: "original" }); // not changing value - annotationStorage.setValue("asdf", "original"); + annotationStorage.setValue("asdf", { value: "original" }); annotationStorage.resetModified(); expect(called).toBe(false); // changing value - annotationStorage.setValue("asdf", "modified"); + annotationStorage.setValue("asdf", { value: "modified" }); annotationStorage.resetModified(); expect(called).toBe(true); done();