diff --git a/src/core/annotation.js b/src/core/annotation.js index 2184d2314601d..99f8f3e7b1d0c 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -881,7 +881,11 @@ class Annotation { ); if (!appearance) { if (!isUsingOwnCanvas) { - return new OperatorList(); + return { + opList: new OperatorList(), + separateForm: false, + separateCanvas: false, + }; } appearance = new StringStream(""); appearance.dict = new Dict(); @@ -930,7 +934,7 @@ class Annotation { opList.addOp(OPS.endMarkedContent, []); } this.reset(); - return opList; + return { opList, separateForm: false, separateCanvas: isUsingOwnCanvas }; } async save(evaluator, task, annotationStorage) { @@ -1619,7 +1623,11 @@ class WidgetAnnotation extends Annotation { // Do not render form elements on the canvas when interactive forms are // enabled. The display layer is responsible for rendering them instead. if (renderForms && !(this instanceof SignatureWidgetAnnotation)) { - return new OperatorList(); + return { + opList: new OperatorList(), + separateForm: true, + separateCanvas: false, + }; } if (!this._hasText) { @@ -1647,12 +1655,12 @@ class WidgetAnnotation extends Annotation { ); } - const operatorList = new OperatorList(); + const opList = new OperatorList(); // Even if there is an appearance stream, ignore it. This is the // behaviour used by Adobe Reader. if (!this._defaultAppearance || content === null) { - return operatorList; + return { opList, separateForm: false, separateCanvas: false }; } const matrix = [1, 0, 0, 1, 0, 0]; @@ -1672,10 +1680,10 @@ class WidgetAnnotation extends Annotation { ); } if (optionalContent !== undefined) { - operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); + opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); } - operatorList.addOp(OPS.beginAnnotation, [ + opList.addOp(OPS.beginAnnotation, [ this.data.id, this.data.rect, transform, @@ -1688,14 +1696,14 @@ class WidgetAnnotation extends Annotation { stream, task, resources: this._fieldResources.mergedResources, - operatorList, + operatorList: opList, }); - operatorList.addOp(OPS.endAnnotation, []); + opList.addOp(OPS.endAnnotation, []); if (optionalContent !== undefined) { - operatorList.addOp(OPS.endMarkedContent, []); + opList.addOp(OPS.endMarkedContent, []); } - return operatorList; + return { opList, separateForm: false, separateCanvas: false }; } _getMKDict(rotation) { @@ -2477,7 +2485,11 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } // No appearance - return new OperatorList(); + return { + opList: new OperatorList(), + separateForm: false, + separateCanvas: false, + }; } async save(evaluator, task, annotationStorage) { diff --git a/src/core/document.js b/src/core/document.js index 2d6328af4ed55..a9a4ec5269668 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -455,7 +455,7 @@ class Page { annotations.length === 0 || intent & RenderingIntentFlag.ANNOTATIONS_DISABLE ) { - pageOpList.flush(true); + pageOpList.flush(/* lastChunk = */ true); return { length: pageOpList.totalLength }; } const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS), @@ -493,10 +493,23 @@ class Page { } return Promise.all(opListPromises).then(function (opLists) { - for (const opList of opLists) { + let form = false, + canvas = false; + + for (const { opList, separateForm, separateCanvas } of opLists) { pageOpList.addOpList(opList); + + if (separateForm) { + form = separateForm; + } + if (separateCanvas) { + canvas = separateCanvas; + } } - pageOpList.flush(true); + pageOpList.flush( + /* lastChunk = */ true, + /* separateAnnots = */ { form, canvas } + ); return { length: pageOpList.totalLength }; }); }); diff --git a/src/core/operator_list.js b/src/core/operator_list.js index 21ef90cf34f3d..063249f624dff 100644 --- a/src/core/operator_list.js +++ b/src/core/operator_list.js @@ -690,7 +690,7 @@ class OperatorList { return transfers; } - flush(lastChunk = false) { + flush(lastChunk = false, separateAnnots = null) { this.optimizer.flush(); const length = this.length; this._totalLength += length; @@ -700,6 +700,7 @@ class OperatorList { fnArray: this.fnArray, argsArray: this.argsArray, lastChunk, + separateAnnots, length, }, 1, diff --git a/src/display/api.js b/src/display/api.js index 3e0bcdcfdc32d..bedb94033d429 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -1477,6 +1477,7 @@ class PDFPageProxy { fnArray: [], argsArray: [], lastChunk: false, + separateAnnots: null, }; if (this._stats) { @@ -1599,6 +1600,7 @@ class PDFPageProxy { fnArray: [], argsArray: [], lastChunk: false, + separateAnnots: null, }; if (this._stats) { @@ -1795,6 +1797,7 @@ class PDFPageProxy { intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]); } intentState.operatorList.lastChunk = operatorListChunk.lastChunk; + intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots; // Notify all the rendering tasks there are more operators to be consumed. for (const internalRenderTask of intentState.renderTasks) { @@ -3194,8 +3197,10 @@ class PDFObjects { * Allows controlling of the rendering tasks. */ class RenderTask { + #internalRenderTask = null; + constructor(internalRenderTask) { - this._internalRenderTask = internalRenderTask; + this.#internalRenderTask = internalRenderTask; /** * Callback for incremental rendering -- a function that will be called @@ -3211,7 +3216,7 @@ class RenderTask { * @type {Promise} */ get promise() { - return this._internalRenderTask.capability.promise; + return this.#internalRenderTask.capability.promise; } /** @@ -3220,7 +3225,23 @@ class RenderTask { * this object extends will be rejected when cancelled. */ cancel() { - this._internalRenderTask.cancel(); + this.#internalRenderTask.cancel(); + } + + /** + * Whether form fields are rendered separately from the main operatorList. + * @type {boolean} + */ + get separateAnnots() { + const { separateAnnots } = this.#internalRenderTask.operatorList; + if (!separateAnnots) { + return false; + } + const { annotationCanvasMap } = this.#internalRenderTask; + return ( + separateAnnots.form || + (separateAnnots.canvas && annotationCanvasMap?.size > 0) + ); } } diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 98b647b1e65dd..1d5cc8a24e839 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -1682,29 +1682,27 @@ describe("annotation", function () { ); const annotationStorage = new Map(); - const operatorList = await annotation.getOperatorList( + const { opList } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList.argsArray.length).toEqual(3); + expect(opList.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList.argsArray[0]).toEqual([ "271R", [0, 0, 32, 10], [32, 0, 0, 10, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( - new Uint8ClampedArray([26, 51, 76]) - ); + expect(opList.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76])); }); it("should render auto-sized text for printing", async function () { @@ -2369,29 +2367,29 @@ describe("annotation", function () { const annotationStorage = new Map(); annotationStorage.set(annotation.data.id, { value: true }); - const operatorList = await annotation.getOperatorList( + const { opList } = await annotation.getOperatorList( checkboxEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(5); - expect(operatorList.fnArray).toEqual([ + expect(opList.argsArray.length).toEqual(5); + expect(opList.fnArray).toEqual([ OPS.beginAnnotation, OPS.dependency, OPS.setFont, OPS.showText, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList.argsArray[0]).toEqual([ "124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[3][0][0].unicode).toEqual("4"); + expect(opList.argsArray[3][0][0].unicode).toEqual("4"); }); it("should render checkboxes for printing", async function () { @@ -2430,55 +2428,51 @@ describe("annotation", function () { const annotationStorage = new Map(); annotationStorage.set(annotation.data.id, { value: true }); - let operatorList = await annotation.getOperatorList( + const { opList: opList1 } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList1.argsArray.length).toEqual(3); + expect(opList1.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList1.argsArray[0]).toEqual([ "124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( - new Uint8ClampedArray([26, 51, 76]) - ); + expect(opList1.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76])); annotationStorage.set(annotation.data.id, { value: false }); - operatorList = await annotation.getOperatorList( + const { opList: opList2 } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList2.argsArray.length).toEqual(3); + expect(opList2.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList2.argsArray[0]).toEqual([ "124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( - new Uint8ClampedArray([76, 51, 26]) - ); + expect(opList2.argsArray[1]).toEqual(new Uint8ClampedArray([76, 51, 26])); }); it("should render checkboxes for printing twice", async function () { @@ -2520,27 +2514,27 @@ describe("annotation", function () { for (let i = 0; i < 2; i++) { annotationStorage.set(annotation.data.id, { value: true }); - const operatorList = await annotation.getOperatorList( + const { opList } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList.argsArray.length).toEqual(3); + expect(opList.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList.argsArray[0]).toEqual([ "1249R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( + expect(opList.argsArray[1]).toEqual( new Uint8ClampedArray([26, 51, 76]) ); } @@ -2582,29 +2576,27 @@ describe("annotation", function () { ); const annotationStorage = new Map(); - const operatorList = await annotation.getOperatorList( + const { opList } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList.argsArray.length).toEqual(3); + expect(opList.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList.argsArray[0]).toEqual([ "124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( - new Uint8ClampedArray([26, 51, 76]) - ); + expect(opList.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76])); }); it("should save checkboxes", async function () { @@ -2838,55 +2830,51 @@ describe("annotation", function () { const annotationStorage = new Map(); annotationStorage.set(annotation.data.id, { value: true }); - let operatorList = await annotation.getOperatorList( + const { opList: opList1 } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList1.argsArray.length).toEqual(3); + expect(opList1.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList1.argsArray[0]).toEqual([ "124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( - new Uint8ClampedArray([26, 51, 76]) - ); + expect(opList1.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76])); annotationStorage.set(annotation.data.id, { value: false }); - operatorList = await annotation.getOperatorList( + const { opList: opList2 } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList2.argsArray.length).toEqual(3); + expect(opList2.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList2.argsArray[0]).toEqual([ "124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( - new Uint8ClampedArray([76, 51, 26]) - ); + expect(opList2.argsArray[1]).toEqual(new Uint8ClampedArray([76, 51, 26])); }); it("should render radio buttons for printing using normal appearance", async function () { @@ -2926,29 +2914,27 @@ describe("annotation", function () { ); const annotationStorage = new Map(); - const operatorList = await annotation.getOperatorList( + const { opList } = await annotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, false, annotationStorage ); - expect(operatorList.argsArray.length).toEqual(3); - expect(operatorList.fnArray).toEqual([ + expect(opList.argsArray.length).toEqual(3); + expect(opList.fnArray).toEqual([ OPS.beginAnnotation, OPS.setFillRGBColor, OPS.endAnnotation, ]); - expect(operatorList.argsArray[0]).toEqual([ + expect(opList.argsArray[0]).toEqual([ "124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false, ]); - expect(operatorList.argsArray[1]).toEqual( - new Uint8ClampedArray([76, 51, 26]) - ); + expect(opList.argsArray[1]).toEqual(new Uint8ClampedArray([76, 51, 26])); }); it("should save radio buttons", async function () { @@ -4087,7 +4073,7 @@ describe("annotation", function () { ]) )[0]; - const operatorList = await freetextAnnotation.getOperatorList( + const { opList } = await freetextAnnotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, @@ -4095,8 +4081,8 @@ describe("annotation", function () { null ); - expect(operatorList.fnArray.length).toEqual(16); - expect(operatorList.fnArray).toEqual([ + expect(opList.fnArray.length).toEqual(16); + expect(opList.fnArray).toEqual([ OPS.beginAnnotation, OPS.save, OPS.constructPath, @@ -4322,7 +4308,7 @@ describe("annotation", function () { ]) )[0]; - const operatorList = await inkAnnotation.getOperatorList( + const { opList } = await inkAnnotation.getOperatorList( partialEvaluator, task, RenderingIntentFlag.PRINT, @@ -4330,8 +4316,8 @@ describe("annotation", function () { null ); - expect(operatorList.argsArray.length).toEqual(8); - expect(operatorList.fnArray).toEqual([ + expect(opList.argsArray.length).toEqual(8); + expect(opList.fnArray).toEqual([ OPS.beginAnnotation, OPS.setLineWidth, OPS.setLineCap, @@ -4343,20 +4329,18 @@ describe("annotation", function () { ]); // Linewidth. - expect(operatorList.argsArray[1]).toEqual([3]); + expect(opList.argsArray[1]).toEqual([3]); // LineCap. - expect(operatorList.argsArray[2]).toEqual([1]); + expect(opList.argsArray[2]).toEqual([1]); // LineJoin. - expect(operatorList.argsArray[3]).toEqual([1]); + expect(opList.argsArray[3]).toEqual([1]); // Color. - expect(operatorList.argsArray[4]).toEqual( - new Uint8ClampedArray([0, 255, 0]) - ); + expect(opList.argsArray[4]).toEqual(new Uint8ClampedArray([0, 255, 0])); // Path. - expect(operatorList.argsArray[5][0]).toEqual([OPS.moveTo, OPS.curveTo]); - expect(operatorList.argsArray[5][1]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + expect(opList.argsArray[5][0]).toEqual([OPS.moveTo, OPS.curveTo]); + expect(opList.argsArray[5][1]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); // Min-max. - expect(operatorList.argsArray[5][2]).toEqual([1, 1, 2, 2]); + expect(opList.argsArray[5][2]).toEqual([1, 1, 2, 2]); }); }); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 1546e5b51e4c9..3518702df6272 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -501,6 +501,7 @@ describe("api", function () { expect(opList.fnArray.length).toEqual(0); expect(opList.argsArray.length).toEqual(0); expect(opList.lastChunk).toEqual(true); + expect(opList.separateAnnots).toEqual(null); await loadingTask.destroy(); }); @@ -521,6 +522,7 @@ describe("api", function () { expect(opList.fnArray.length).toEqual(0); expect(opList.argsArray.length).toEqual(0); expect(opList.lastChunk).toEqual(true); + expect(opList.separateAnnots).toEqual(null); await loadingTask.destroy(); }); @@ -588,6 +590,7 @@ describe("api", function () { expect(opList.fnArray.length).toBeGreaterThan(5); expect(opList.argsArray.length).toBeGreaterThan(5); expect(opList.lastChunk).toEqual(true); + expect(opList.separateAnnots).toEqual(null); try { await pdfDocument2.getPage(1); @@ -631,6 +634,7 @@ describe("api", function () { expect(opList.fnArray.length).toBeGreaterThan(5); expect(opList.argsArray.length).toBeGreaterThan(5); expect(opList.lastChunk).toEqual(true); + expect(opList.separateAnnots).toEqual(null); } await Promise.all([loadingTask1.destroy(), loadingTask2.destroy()]); @@ -2402,6 +2406,10 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(operatorList.fnArray.length).toBeGreaterThan(100); expect(operatorList.argsArray.length).toBeGreaterThan(100); expect(operatorList.lastChunk).toEqual(true); + expect(operatorList.separateAnnots).toEqual({ + form: false, + canvas: false, + }); }); it("gets operatorList with JPEG image (issue 4888)", async function () { @@ -2442,6 +2450,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(opList.fnArray.length).toBeGreaterThan(100); expect(opList.argsArray.length).toBeGreaterThan(100); expect(opList.lastChunk).toEqual(true); + expect(opList.separateAnnots).toEqual(null); return loadingTask1.destroy(); }); @@ -2454,6 +2463,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(opList.fnArray.length).toEqual(0); expect(opList.argsArray.length).toEqual(0); expect(opList.lastChunk).toEqual(true); + expect(opList.separateAnnots).toEqual(null); return loadingTask2.destroy(); }); @@ -2475,6 +2485,10 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(operatorList.fnArray.length).toBeGreaterThan(20); expect(operatorList.argsArray.length).toBeGreaterThan(20); expect(operatorList.lastChunk).toEqual(true); + expect(operatorList.separateAnnots).toEqual({ + form: false, + canvas: false, + }); // The `getOperatorList` method, similar to the `render` method, // is supposed to include any existing Annotation-operatorLists. @@ -2498,6 +2512,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(opListAnnotDisable.fnArray.length).toEqual(0); expect(opListAnnotDisable.argsArray.length).toEqual(0); expect(opListAnnotDisable.lastChunk).toEqual(true); + expect(opListAnnotDisable.separateAnnots).toEqual(null); const opListAnnotEnable = await pdfPage.getOperatorList({ annotationMode: AnnotationMode.ENABLE, @@ -2505,6 +2520,10 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(opListAnnotEnable.fnArray.length).toBeGreaterThan(140); expect(opListAnnotEnable.argsArray.length).toBeGreaterThan(140); expect(opListAnnotEnable.lastChunk).toEqual(true); + expect(opListAnnotEnable.separateAnnots).toEqual({ + form: false, + canvas: true, + }); let firstAnnotIndex = opListAnnotEnable.fnArray.indexOf( OPS.beginAnnotation @@ -2518,6 +2537,10 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(opListAnnotEnableForms.fnArray.length).toBeGreaterThan(30); expect(opListAnnotEnableForms.argsArray.length).toBeGreaterThan(30); expect(opListAnnotEnableForms.lastChunk).toEqual(true); + expect(opListAnnotEnableForms.separateAnnots).toEqual({ + form: true, + canvas: true, + }); firstAnnotIndex = opListAnnotEnableForms.fnArray.indexOf( OPS.beginAnnotation @@ -2531,6 +2554,10 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(opListAnnotEnableStorage.fnArray.length).toBeGreaterThan(170); expect(opListAnnotEnableStorage.argsArray.length).toBeGreaterThan(170); expect(opListAnnotEnableStorage.lastChunk).toEqual(true); + expect(opListAnnotEnableStorage.separateAnnots).toEqual({ + form: false, + canvas: true, + }); firstAnnotIndex = opListAnnotEnableStorage.fnArray.indexOf( OPS.beginAnnotation @@ -2635,8 +2662,9 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(renderTask instanceof RenderTask).toEqual(true); await renderTask.promise; - const stats = pdfPage.stats; + expect(renderTask.separateAnnots).toEqual(false); + const { stats } = pdfPage; expect(stats instanceof StatTimer).toEqual(true); expect(stats.times.length).toEqual(3); @@ -2719,6 +2747,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(reRenderTask instanceof RenderTask).toEqual(true); await reRenderTask.promise; + expect(reRenderTask.separateAnnots).toEqual(false); CanvasFactory.destroy(canvasAndCtx); }); @@ -2785,8 +2814,9 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) expect(renderTask instanceof RenderTask).toEqual(true); await renderTask.promise; - await pdfDoc.cleanup(); + expect(renderTask.separateAnnots).toEqual(false); + await pdfDoc.cleanup(); expect(true).toEqual(true); CanvasFactory.destroy(canvasAndCtx); @@ -2831,6 +2861,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) ); } await renderTask.promise; + expect(renderTask.separateAnnots).toEqual(false); CanvasFactory.destroy(canvasAndCtx); await loadingTask.destroy(); @@ -2918,6 +2949,8 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) }); await renderTask.promise; + expect(renderTask.separateAnnots).toEqual(false); + const printData = canvasAndCtx.canvas.toDataURL(); CanvasFactory.destroy(canvasAndCtx); @@ -3002,6 +3035,8 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport, }); await renderTask.promise; + expect(renderTask.separateAnnots).toEqual(false); + const data = canvasAndCtx.canvas.toDataURL(); CanvasFactory.destroy(canvasAndCtx); return data; diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index df8dd2fc3ca94..842ce4a0c2201 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -99,7 +99,10 @@ const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216; class PDFPageView { #annotationMode = AnnotationMode.ENABLE_FORMS; - #useThumbnailCanvas = true; + #useThumbnailCanvas = { + initialOptionalContent: true, + regularAnnotations: true, + }; /** * @param {PDFPageViewOptions} options @@ -190,14 +193,15 @@ class PDFPageView { const { optionalContentConfigPromise } = options; if (optionalContentConfigPromise) { // Ensure that the thumbnails always display the *initial* document - // state. + // state, for documents with optional content. optionalContentConfigPromise.then(optionalContentConfig => { if ( optionalContentConfigPromise !== this._optionalContentConfigPromise ) { return; } - this.#useThumbnailCanvas = optionalContentConfig.hasInitialVisibility; + this.#useThumbnailCanvas.initialOptionalContent = + optionalContentConfig.hasInitialVisibility; }); } } @@ -408,14 +412,16 @@ class PDFPageView { if (optionalContentConfigPromise instanceof Promise) { this._optionalContentConfigPromise = optionalContentConfigPromise; - // Ensure that the thumbnails always display the *initial* document state. + // Ensure that the thumbnails always display the *initial* document state, + // for documents with optional content. optionalContentConfigPromise.then(optionalContentConfig => { if ( optionalContentConfigPromise !== this._optionalContentConfigPromise ) { return; } - this.#useThumbnailCanvas = optionalContentConfig.hasInitialVisibility; + this.#useThumbnailCanvas.initialOptionalContent = + optionalContentConfig.hasInitialVisibility; }); } @@ -772,6 +778,10 @@ class PDFPageView { } this._resetZoomLayer(/* removeFromDOM = */ true); + // Ensure that the thumbnails won't become partially (or fully) blank, + // for documents that contain interactive form elements. + this.#useThumbnailCanvas.regularAnnotations = !paintTask.separateAnnots; + this.eventBus.dispatch("pagerendered", { source: this, pageNumber: this.id, @@ -888,6 +898,9 @@ class PDFPageView { cancel() { renderTask.cancel(); }, + get separateAnnots() { + return renderTask.separateAnnots; + }, }; const viewport = this.viewport; @@ -1029,6 +1042,9 @@ class PDFPageView { cancel() { cancelled = true; }, + get separateAnnots() { + return false; + }, }; } @@ -1050,7 +1066,9 @@ class PDFPageView { * @ignore */ get thumbnailCanvas() { - return this.#useThumbnailCanvas ? this.canvas : null; + const { initialOptionalContent, regularAnnotations } = + this.#useThumbnailCanvas; + return initialOptionalContent && regularAnnotations ? this.canvas : null; } }