diff --git a/extensions/chromium/preferences_schema.json b/extensions/chromium/preferences_schema.json index 49190d6ed..9b30f007f 100644 --- a/extensions/chromium/preferences_schema.json +++ b/extensions/chromium/preferences_schema.json @@ -144,9 +144,15 @@ "description": "Whether to prevent the extension from reporting the extension and browser version to the extension developers.", "default": false }, - "renderInteractiveForms": { - "type": "boolean", - "default": true + "annotationMode": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "default": 2 }, "enableScripting": { "type": "boolean", diff --git a/src/core/document.js b/src/core/document.js index 19f87d55d..9d35432a5 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -380,7 +380,10 @@ class Page { // page's operator list to render them. return Promise.all([pageListPromise, this._parsedAnnotations]).then( function ([pageOpList, annotations]) { - if (annotations.length === 0) { + if ( + annotations.length === 0 || + intent & RenderingIntentFlag.ANNOTATIONS_DISABLE + ) { pageOpList.flush(true); return { length: pageOpList.totalLength }; } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 2fdd7fcab..b69d84288 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -46,7 +46,7 @@ const DEFAULT_TAB_INDEX = 1000; * @property {AnnotationStorage} [annotationStorage] * @property {string} [imageResourcesPath] - Path for image resources, mainly * for annotation icons. Include trailing slash. - * @property {boolean} renderInteractiveForms + * @property {boolean} renderForms * @property {Object} svgFactory * @property {boolean} [enableScripting] * @property {boolean} [hasJSActions] @@ -154,7 +154,7 @@ class AnnotationElement { this.linkService = parameters.linkService; this.downloadManager = parameters.downloadManager; this.imageResourcesPath = parameters.imageResourcesPath; - this.renderInteractiveForms = parameters.renderInteractiveForms; + this.renderForms = parameters.renderForms; this.svgFactory = parameters.svgFactory; this.annotationStorage = parameters.annotationStorage; this.enableScripting = parameters.enableScripting; @@ -676,7 +676,7 @@ class WidgetAnnotationElement extends AnnotationElement { class TextWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { const isRenderable = - parameters.renderInteractiveForms || + parameters.renderForms || (!parameters.data.hasAppearance && !!parameters.data.fieldValue); super(parameters, { isRenderable }); } @@ -700,7 +700,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { this.container.className = "textWidgetAnnotation"; let element = null; - if (this.renderInteractiveForms) { + if (this.renderForms) { // 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. @@ -952,7 +952,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { - super(parameters, { isRenderable: parameters.renderInteractiveForms }); + super(parameters, { isRenderable: parameters.renderForms }); } render() { @@ -1031,7 +1031,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { - super(parameters, { isRenderable: parameters.renderInteractiveForms }); + super(parameters, { isRenderable: parameters.renderForms }); } render() { @@ -1123,7 +1123,7 @@ class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { - super(parameters, { isRenderable: parameters.renderInteractiveForms }); + super(parameters, { isRenderable: parameters.renderForms }); } render() { @@ -2033,7 +2033,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement { * @property {DownloadManager} downloadManager * @property {string} [imageResourcesPath] - Path for image resources, mainly * for annotation icons. Include trailing slash. - * @property {boolean} renderInteractiveForms + * @property {boolean} renderForms * @property {boolean} [enableScripting] - Enable embedded script execution. * @property {boolean} [hasJSActions] - Some fields have JS actions. * The default value is `false`. @@ -2076,7 +2076,7 @@ class AnnotationLayer { linkService: parameters.linkService, downloadManager: parameters.downloadManager, imageResourcesPath: parameters.imageResourcesPath || "", - renderInteractiveForms: parameters.renderInteractiveForms !== false, + renderForms: parameters.renderForms !== false, svgFactory: new DOMSVGFactory(), annotationStorage: parameters.annotationStorage || new AnnotationStorage(), diff --git a/src/display/api.js b/src/display/api.js index 776e6b180..40fe7599f 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -19,6 +19,7 @@ import { AbortException, + AnnotationMode, assert, createPromiseCapability, getVerbosityLevel, @@ -1135,9 +1136,18 @@ class PDFDocumentProxy { * the `PDFPageProxy.getViewport` method. * @property {string} [intent] - Rendering intent, can be 'display', 'print', * or 'any'. The default value is 'display'. - * @property {boolean} [renderInteractiveForms] - Whether or not interactive - * form elements are rendered in the display layer. If so, we do not render - * them on the canvas as well. The default value is `false`. + * @property {number} [annotationMode] Controls which annotations are rendered + * onto the canvas, for annotations with appearance-data; the values from + * {@link AnnotationMode} should be used. The following values are supported: + * - `AnnotationMode.DISABLE`, which disables all annotations. + * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus + * it also depends on the `intent`-option, see above). + * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain + * interactive form elements (those will be rendered in the display layer). + * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations + * (as above) but where interactive form elements are updated with data + * from the {@link AnnotationStorage}-instance; useful e.g. for printing. + * The default value is `AnnotationMode.ENABLE`. * @property {Array} [transform] - Additional transform, applied just * before viewport transform. * @property {Object} [imageLayer] - An object that has `beginLayout`, @@ -1149,9 +1159,6 @@ class PDFDocumentProxy { * value, a `CanvasGradient` object (a linear or radial gradient) or * a `CanvasPattern` object (a repetitive image). The default value is * 'rgb(255,255,255)'. - * @property {boolean} [includeAnnotationStorage] - Render stored interactive - * form element data, from the {@link AnnotationStorage}-instance, onto the - * canvas itself; useful e.g. for printing. The default value is `false`. * @property {Promise} [optionalContentConfigPromise] - * A promise that should resolve with an {@link OptionalContentConfig} * created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`, @@ -1165,6 +1172,18 @@ class PDFDocumentProxy { * @typedef {Object} GetOperatorListParameters * @property {string} [intent] - Rendering intent, can be 'display', 'print', * or 'any'. The default value is 'display'. + * @property {number} [annotationMode] Controls which annotations are included + * in the operatorList, for annotations with appearance-data; the values from + * {@link AnnotationMode} should be used. The following values are supported: + * - `AnnotationMode.DISABLE`, which disables all annotations. + * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus + * it also depends on the `intent`-option, see above). + * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain + * interactive form elements (those will be rendered in the display layer). + * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations + * (as above) but where interactive form elements are updated with data + * from the {@link AnnotationStorage}-instance; useful e.g. for printing. + * The default value is `AnnotationMode.ENABLE`. */ /** @@ -1280,7 +1299,7 @@ class PDFPageProxy { * {Array} of the annotation objects. */ getAnnotations({ intent = "display" } = {}) { - const intentArgs = this._transport.getRenderingIntent(intent, {}); + const intentArgs = this._transport.getRenderingIntent(intent); let promise = this._annotationPromises.get(intentArgs.cacheKey); if (!promise) { @@ -1324,22 +1343,21 @@ class PDFPageProxy { canvasContext, viewport, intent = "display", - renderInteractiveForms = false, + annotationMode = AnnotationMode.ENABLE, transform = null, imageLayer = null, canvasFactory = null, background = null, - includeAnnotationStorage = false, optionalContentConfigPromise = null, }) { if (this._stats) { this._stats.time("Overall"); } - const intentArgs = this._transport.getRenderingIntent(intent, { - renderForms: renderInteractiveForms === true, - includeAnnotationStorage: includeAnnotationStorage === true, - }); + const intentArgs = this._transport.getRenderingIntent( + intent, + annotationMode + ); // If there was a pending destroy, cancel it so no cleanup happens during // this call to render. this.pendingCleanup = false; @@ -1460,7 +1478,10 @@ class PDFPageProxy { * @returns {Promise} A promise resolved with an * {@link PDFOperatorList} object that represents the page's operator list. */ - getOperatorList({ intent = "display" } = {}) { + getOperatorList({ + intent = "display", + annotationMode = AnnotationMode.ENABLE, + } = {}) { function operatorListChanged() { if (intentState.operatorList.lastChunk) { intentState.opListReadCapability.resolve(intentState.operatorList); @@ -1469,9 +1490,11 @@ class PDFPageProxy { } } - const intentArgs = this._transport.getRenderingIntent(intent, { - isOpList: true, - }); + const intentArgs = this._transport.getRenderingIntent( + intent, + annotationMode, + /* isOpList = */ true + ); let intentState = this._intentStates.get(intentArgs.cacheKey); if (!intentState) { intentState = Object.create(null); @@ -1792,7 +1815,7 @@ class PDFPageProxy { } } intentState.streamReader - .cancel(new AbortException(reason?.message)) + .cancel(new AbortException(reason.message)) .catch(() => { // Avoid "Uncaught promise" messages in the console. }); @@ -2351,7 +2374,8 @@ class WorkerTransport { getRenderingIntent( intent, - { renderForms = false, includeAnnotationStorage = false, isOpList = false } + annotationMode = AnnotationMode.ENABLE, + isOpList = false ) { let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value. let lastModified = ""; @@ -2369,13 +2393,22 @@ class WorkerTransport { warn(`getRenderingIntent - invalid intent: ${intent}`); } - if (renderForms) { - renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS; - } - if (includeAnnotationStorage) { - renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE; + switch (annotationMode) { + case AnnotationMode.DISABLE: + renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE; + break; + case AnnotationMode.ENABLE: + break; + case AnnotationMode.ENABLE_FORMS: + renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS; + break; + case AnnotationMode.ENABLE_STORAGE: + renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE; - lastModified = this.annotationStorage.lastModified; + lastModified = this.annotationStorage.lastModified; + break; + default: + warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`); } if (isOpList) { diff --git a/src/pdf.js b/src/pdf.js index 9a1bfce30..8bbf9efa8 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -27,15 +27,7 @@ import { RenderingCancelledException, } from "./display/display_utils.js"; import { - build, - getDocument, - LoopbackPort, - PDFDataRangeTransport, - PDFWorker, - setPDFNetworkStreamFactory, - version, -} from "./display/api.js"; -import { + AnnotationMode, CMapCompressionType, createObjectURL, createPromiseCapability, @@ -52,6 +44,15 @@ import { Util, VerbosityLevel, } from "./shared/util.js"; +import { + build, + getDocument, + LoopbackPort, + PDFDataRangeTransport, + PDFWorker, + setPDFNetworkStreamFactory, + version, +} from "./display/api.js"; import { AnnotationLayer } from "./display/annotation_layer.js"; import { GlobalWorkerOptions } from "./display/worker_options.js"; import { isNodeJS } from "./shared/is_node.js"; @@ -110,14 +111,8 @@ export { PDFDateString, RenderingCancelledException, getXfaPageViewport, - // From "./display/api.js": - build, - getDocument, - LoopbackPort, - PDFDataRangeTransport, - PDFWorker, - version, // From "./shared/util.js": + AnnotationMode, CMapCompressionType, createObjectURL, createPromiseCapability, @@ -133,6 +128,13 @@ export { UNSUPPORTED_FEATURES, Util, VerbosityLevel, + // From "./display/api.js": + build, + getDocument, + LoopbackPort, + PDFDataRangeTransport, + PDFWorker, + version, // From "./display/annotation_layer.js": AnnotationLayer, // From "./display/worker_options.js": diff --git a/src/shared/util.js b/src/shared/util.js index 9371c3676..d893e398e 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -23,9 +23,10 @@ const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; * how these flags are being used: * - ANY, DISPLAY, and PRINT are the normal rendering intents, note the * `PDFPageProxy.{render, getOperatorList, getAnnotations}`-methods. - * - ANNOTATIONS_FORMS, and ANNOTATIONS_STORAGE controls which annotations are - * rendered onto the canvas, note the `renderInteractiveForms`- respectively - * `includeAnnotationStorage`-options in the `PDFPageProxy.render`-method. + * - ANNOTATIONS_FORMS, ANNOTATIONS_STORAGE, ANNOTATIONS_DISABLE control which + * annotations are rendered onto the canvas (i.e. by being included in the + * operatorList), note the `PDFPageProxy.{render, getOperatorList}`-methods + * and their `annotationMode`-option. * - OPLIST is used with the `PDFPageProxy.getOperatorList`-method, note the * `OperatorList`-constructor (on the worker-thread). */ @@ -35,9 +36,17 @@ const RenderingIntentFlag = { PRINT: 0x04, ANNOTATIONS_FORMS: 0x10, ANNOTATIONS_STORAGE: 0x20, + ANNOTATIONS_DISABLE: 0x40, OPLIST: 0x100, }; +const AnnotationMode = { + DISABLE: 0, + ENABLE: 1, + ENABLE_FORMS: 2, + ENABLE_STORAGE: 3, +}; + // Permission flags from Table 22, Section 7.6.3.2 of the PDF specification. const PermissionFlag = { PRINT: 0x04, @@ -1027,6 +1036,7 @@ export { AnnotationFieldFlag, AnnotationFlag, AnnotationMarkedState, + AnnotationMode, AnnotationReplyType, AnnotationReviewState, AnnotationStateModelType, diff --git a/test/driver.js b/test/driver.js index 63bb73f68..5946561a8 100644 --- a/test/driver.js +++ b/test/driver.js @@ -17,6 +17,16 @@ "use strict"; +const { + AnnotationLayer, + AnnotationMode, + getDocument, + GlobalWorkerOptions, + renderTextLayer, + XfaLayer, +} = pdfjsLib; +const { SimpleLinkService } = pdfjsViewer; + const WAITING_TIME = 100; // ms const PDF_TO_CSS_UNITS = 96.0 / 72.0; const CMAP_URL = "/build/generic/web/cmaps/"; @@ -162,7 +172,7 @@ var rasterizeTextLayer = (function rasterizeTextLayerClosure() { style.textContent = cssRules; // Rendering text layer as HTML. - var task = pdfjsLib.renderTextLayer({ + var task = renderTextLayer({ textContent, container: div, viewport, @@ -219,7 +229,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() { annotations, page, imageResourcesPath, - renderInteractiveForms + renderForms = false ) { return new Promise(function (resolve, reject) { // Building SVG with size of the viewport. @@ -250,11 +260,11 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() { div, annotations, page, - linkService: new pdfjsViewer.SimpleLinkService(), + linkService: new SimpleLinkService(), imageResourcesPath, - renderInteractiveForms, + renderForms, }; - pdfjsLib.AnnotationLayer.render(parameters); + AnnotationLayer.render(parameters); // Inline SVG images from text annotations. await resolveImages(div); @@ -319,7 +329,7 @@ var rasterizeXfaLayer = (function rasterizeXfaLayerClosure() { .then(async cssRules => { style.textContent = fontRules + "\n" + cssRules; - pdfjsLib.XfaLayer.render({ + XfaLayer.render({ xfa, div, viewport: viewport.clone({ dontFlip: true }), @@ -365,7 +375,7 @@ var Driver = (function DriverClosure() { // eslint-disable-next-line no-shadow function Driver(options) { // Configure the global worker options. - pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_SRC; + GlobalWorkerOptions.workerSrc = WORKER_SRC; // Set the passed options this.inflight = options.inflight; @@ -494,7 +504,7 @@ var Driver = (function DriverClosure() { .appendChild(xfaStyleElement); } - const loadingTask = pdfjsLib.getDocument({ + const loadingTask = getDocument({ url: absoluteUrl, password: task.password, cMapUrl: CMAP_URL, @@ -752,12 +762,13 @@ var Driver = (function DriverClosure() { var renderContext = { canvasContext: ctx, viewport, - renderInteractiveForms: renderForms, optionalContentConfigPromise: task.optionalContentConfigPromise, }; - if (renderPrint) { + if (renderForms) { + renderContext.annotationMode = AnnotationMode.ENABLE_FORMS; + } else if (renderPrint) { if (task.annotationStorage) { - renderContext.includeAnnotationStorage = true; + renderContext.annotationMode = AnnotationMode.ENABLE_STORAGE; } renderContext.intent = "print"; } diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 1769601c5..28afdb91e 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -14,11 +14,7 @@ */ import { - buildGetDocumentParams, - DefaultFileReaderFactory, - TEST_PDFS_PATH, -} from "./test_utils.js"; -import { + AnnotationMode, createPromiseCapability, FontType, ImageKind, @@ -30,6 +26,11 @@ import { PermissionFlag, StreamType, } from "../../src/shared/util.js"; +import { + buildGetDocumentParams, + DefaultFileReaderFactory, + TEST_PDFS_PATH, +} from "./test_utils.js"; import { DefaultCanvasFactory, getDocument, @@ -1739,6 +1740,56 @@ describe("api", function () { await loadingTask.destroy(); }); + it("gets operator list, with `annotationMode`-option", async function () { + const loadingTask = getDocument(buildGetDocumentParams("evaljs.pdf")); + const pdfDoc = await loadingTask.promise; + const pdfPage = await pdfDoc.getPage(2); + + pdfDoc.annotationStorage.setValue("30R", { value: "test" }); + pdfDoc.annotationStorage.setValue("31R", { value: true }); + + const opListAnnotDisable = await pdfPage.getOperatorList({ + annotationMode: AnnotationMode.DISABLE, + }); + expect(opListAnnotDisable.fnArray.length).toEqual(0); + expect(opListAnnotDisable.argsArray.length).toEqual(0); + expect(opListAnnotDisable.lastChunk).toEqual(true); + + const opListAnnotEnable = await pdfPage.getOperatorList({ + annotationMode: AnnotationMode.ENABLE, + }); + expect(opListAnnotEnable.fnArray.length).toBeGreaterThan(150); + expect(opListAnnotEnable.argsArray.length).toBeGreaterThan(150); + expect(opListAnnotEnable.lastChunk).toEqual(true); + + const opListAnnotEnableForms = await pdfPage.getOperatorList({ + annotationMode: AnnotationMode.ENABLE_FORMS, + }); + expect(opListAnnotEnableForms.fnArray.length).toBeGreaterThan(40); + expect(opListAnnotEnableForms.argsArray.length).toBeGreaterThan(40); + expect(opListAnnotEnableForms.lastChunk).toEqual(true); + + const opListAnnotEnableStorage = await pdfPage.getOperatorList({ + annotationMode: AnnotationMode.ENABLE_STORAGE, + }); + expect(opListAnnotEnableStorage.fnArray.length).toBeGreaterThan(170); + expect(opListAnnotEnableStorage.argsArray.length).toBeGreaterThan(170); + expect(opListAnnotEnableStorage.lastChunk).toEqual(true); + + // Sanity check to ensure that the `annotationMode` is correctly applied. + expect(opListAnnotDisable.fnArray.length).toBeLessThan( + opListAnnotEnableForms.fnArray.length + ); + expect(opListAnnotEnableForms.fnArray.length).toBeLessThan( + opListAnnotEnable.fnArray.length + ); + expect(opListAnnotEnable.fnArray.length).toBeLessThan( + opListAnnotEnableStorage.fnArray.length + ); + + await loadingTask.destroy(); + }); + it("gets document stats after parsing page", async function () { const stats = await page.getOperatorList().then(function () { return pdfDocument.getStats(); diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index 0f7ce4060..3c4e3c7d3 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -24,7 +24,7 @@ import { SimpleLinkService } from "./pdf_link_service.js"; * @property {AnnotationStorage} [annotationStorage] * @property {string} [imageResourcesPath] - Path for image resources, mainly * for annotation icons. Include trailing slash. - * @property {boolean} renderInteractiveForms + * @property {boolean} renderForms * @property {IPDFLinkService} linkService * @property {DownloadManager} downloadManager * @property {IL10n} l10n - Localization service. @@ -44,7 +44,7 @@ class AnnotationLayerBuilder { downloadManager, annotationStorage = null, imageResourcesPath = "", - renderInteractiveForms = true, + renderForms = true, l10n = NullL10n, enableScripting = false, hasJSActionsPromise = null, @@ -55,7 +55,7 @@ class AnnotationLayerBuilder { this.linkService = linkService; this.downloadManager = downloadManager; this.imageResourcesPath = imageResourcesPath; - this.renderInteractiveForms = renderInteractiveForms; + this.renderForms = renderForms; this.l10n = l10n; this.annotationStorage = annotationStorage; this.enableScripting = enableScripting; @@ -90,7 +90,7 @@ class AnnotationLayerBuilder { annotations, page: this.pdfPage, imageResourcesPath: this.imageResourcesPath, - renderInteractiveForms: this.renderInteractiveForms, + renderForms: this.renderForms, linkService: this.linkService, downloadManager: this.downloadManager, annotationStorage: this.annotationStorage, @@ -139,7 +139,7 @@ class DefaultAnnotationLayerFactory { * @param {AnnotationStorage} [annotationStorage] * @param {string} [imageResourcesPath] - Path for image resources, mainly * for annotation icons. Include trailing slash. - * @param {boolean} renderInteractiveForms + * @param {boolean} renderForms * @param {IL10n} l10n * @param {boolean} [enableScripting] * @param {Promise} [hasJSActionsPromise] @@ -151,7 +151,7 @@ class DefaultAnnotationLayerFactory { pdfPage, annotationStorage = null, imageResourcesPath = "", - renderInteractiveForms = true, + renderForms = true, l10n = NullL10n, enableScripting = false, hasJSActionsPromise = null, @@ -161,7 +161,7 @@ class DefaultAnnotationLayerFactory { pageDiv, pdfPage, imageResourcesPath, - renderInteractiveForms, + renderForms, linkService: new SimpleLinkService(), l10n, annotationStorage, diff --git a/web/app.js b/web/app.js index a00f5ad78..f1c8fa003 100644 --- a/web/app.js +++ b/web/app.js @@ -515,8 +515,8 @@ const PDFViewerApplication = { renderer: AppOptions.get("renderer"), l10n: this.l10n, textLayerMode: AppOptions.get("textLayerMode"), + annotationMode: AppOptions.get("annotationMode"), imageResourcesPath: AppOptions.get("imageResourcesPath"), - renderInteractiveForms: AppOptions.get("renderInteractiveForms"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"), maxCanvasPixels: AppOptions.get("maxCanvasPixels"), @@ -1555,7 +1555,7 @@ const PDFViewerApplication = { this.fallback(UNSUPPORTED_FEATURES.forms); } else if ( (info.IsAcroFormPresent || info.IsXFAPresent) && - !this.pdfViewer.renderInteractiveForms + !this.pdfViewer.renderForms ) { console.warn("Warning: Interactive form support is not enabled"); this.fallback(UNSUPPORTED_FEATURES.forms); diff --git a/web/app_options.js b/web/app_options.js index 4a0c0245f..094758872 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -59,6 +59,11 @@ const OptionKind = { * values below *explicitly* rather than relying on imported types. */ const defaultOptions = { + annotationMode: { + /** @type {number} */ + value: 2, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, cursorToolOnLoad: { /** @type {number} */ value: 0, @@ -145,11 +150,6 @@ const defaultOptions = { value: "canvas", kind: OptionKind.VIEWER, }, - renderInteractiveForms: { - /** @type {boolean} */ - value: true, - kind: OptionKind.VIEWER + OptionKind.PREFERENCE, - }, sidebarViewOnLoad: { /** @type {number} */ value: -1, diff --git a/web/base_viewer.js b/web/base_viewer.js index 299f8baef..81a9c7701 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { createPromiseCapability, version } from "pdfjs-lib"; +import { AnnotationMode, createPromiseCapability, version } from "pdfjs-lib"; import { CSS_UNITS, DEFAULT_SCALE, @@ -67,10 +67,13 @@ const DEFAULT_CACHE_SIZE = 10; * selection and searching is created, and if the improved text selection * behaviour is enabled. The constants from {TextLayerMode} should be used. * The default value is `TextLayerMode.ENABLE`. + * @property {number} [annotationMode] - Controls if the annotation layer is + * created, and if interactive form elements or `AnnotationStorage`-data are + * being rendered. The constants from {@link AnnotationMode} should be used; + * see also {@link RenderParameters} and {@link GetOperatorListParameters}. + * The default value is `AnnotationMode.ENABLE_FORMS`. * @property {string} [imageResourcesPath] - Path for image resources, mainly * mainly for annotation icons. Include trailing slash. - * @property {boolean} [renderInteractiveForms] - Enables rendering of - * interactive form elements. The default value is `true`. * @property {boolean} [enablePrintAutoRotate] - Enables automatic rotation of * landscape pages upon printing. The default is `false`. * @property {string} renderer - 'canvas' or 'svg'. The default is 'canvas'. @@ -182,11 +185,10 @@ class BaseViewer { this.findController = options.findController || null; this._scriptingManager = options.scriptingManager || null; this.removePageBorders = options.removePageBorders || false; - this.textLayerMode = Number.isInteger(options.textLayerMode) - ? options.textLayerMode - : TextLayerMode.ENABLE; + this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; + this._annotationMode = + options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.imageResourcesPath = options.imageResourcesPath || ""; - this.renderInteractiveForms = options.renderInteractiveForms !== false; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; this.renderer = options.renderer || RendererType.CANVAS; this.useOnlyCssZoom = options.useOnlyCssZoom || false; @@ -240,6 +242,13 @@ class BaseViewer { }); } + /** + * @type {boolean} + */ + get renderForms() { + return this._annotationMode === AnnotationMode.ENABLE_FORMS; + } + /** * @type {boolean} */ @@ -529,6 +538,8 @@ class BaseViewer { this.textLayerMode !== TextLayerMode.DISABLE && !isPureXfa ? this : null; + const annotationLayerFactory = + this._annotationMode !== AnnotationMode.DISABLE ? this : null; const xfaLayerFactory = isPureXfa ? this : null; for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) { @@ -542,12 +553,12 @@ class BaseViewer { renderingQueue: this.renderingQueue, textLayerFactory, textLayerMode: this.textLayerMode, - annotationLayerFactory: this, + annotationLayerFactory, + annotationMode: this._annotationMode, xfaLayerFactory, textHighlighterFactory: this, structTreeLayerFactory: this, imageResourcesPath: this.imageResourcesPath, - renderInteractiveForms: this.renderInteractiveForms, renderer: this.renderer, useOnlyCssZoom: this.useOnlyCssZoom, maxCanvasPixels: this.maxCanvasPixels, @@ -1289,7 +1300,7 @@ class BaseViewer { * data in forms. * @param {string} [imageResourcesPath] - Path for image resources, mainly * for annotation icons. Include trailing slash. - * @param {boolean} renderInteractiveForms + * @param {boolean} renderForms * @param {IL10n} l10n * @param {boolean} [enableScripting] * @param {Promise} [hasJSActionsPromise] @@ -1301,7 +1312,7 @@ class BaseViewer { pdfPage, annotationStorage = null, imageResourcesPath = "", - renderInteractiveForms = false, + renderForms = true, l10n = NullL10n, enableScripting = null, hasJSActionsPromise = null, @@ -1313,7 +1324,7 @@ class BaseViewer { annotationStorage: annotationStorage || this.pdfDocument?.annotationStorage, imageResourcesPath, - renderInteractiveForms, + renderForms, linkService: this.linkService, downloadManager: this.downloadManager, l10n, diff --git a/web/firefox_print_service.js b/web/firefox_print_service.js index 5fb6dba4e..227632447 100644 --- a/web/firefox_print_service.js +++ b/web/firefox_print_service.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { RenderingCancelledException, shadow } from "pdfjs-lib"; +import { AnnotationMode, RenderingCancelledException, shadow } from "pdfjs-lib"; import { getXfaHtmlForPrinting } from "./print_utils.js"; import { PDFPrintServiceFactory } from "./app.js"; @@ -68,7 +68,7 @@ function composePage( transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }), intent: "print", - includeAnnotationStorage: true, + annotationMode: AnnotationMode.ENABLE_STORAGE, optionalContentConfigPromise, }; currentRenderTask = thisRenderTask = pdfPage.render(renderContext); diff --git a/web/interfaces.js b/web/interfaces.js index 512d4c42a..bbb58a41b 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -186,7 +186,7 @@ class IPDFAnnotationLayerFactory { * data in forms. * @param {string} [imageResourcesPath] - Path for image resources, mainly * for annotation icons. Include trailing slash. - * @param {boolean} renderInteractiveForms + * @param {boolean} renderForms * @param {IL10n} l10n * @param {boolean} [enableScripting] * @param {Promise} [hasJSActionsPromise] @@ -198,7 +198,7 @@ class IPDFAnnotationLayerFactory { pdfPage, annotationStorage = null, imageResourcesPath = "", - renderInteractiveForms = true, + renderForms = true, l10n = undefined, enableScripting = false, hasJSActionsPromise = null, diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 3f1769aa3..0b11a9456 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -13,6 +13,12 @@ * limitations under the License. */ +import { + AnnotationMode, + createPromiseCapability, + RenderingCancelledException, + SVGGraphics, +} from "pdfjs-lib"; import { approximateFraction, CSS_UNITS, @@ -22,11 +28,6 @@ import { roundToDivide, TextLayerMode, } from "./ui_utils.js"; -import { - createPromiseCapability, - RenderingCancelledException, - SVGGraphics, -} from "pdfjs-lib"; import { compatibilityParams } from "./app_options.js"; import { NullL10n } from "./l10n_utils.js"; import { RenderingStates } from "./pdf_rendering_queue.js"; @@ -47,13 +48,16 @@ import { RenderingStates } from "./pdf_rendering_queue.js"; * selection and searching is created, and if the improved text selection * behaviour is enabled. The constants from {TextLayerMode} should be used. * The default value is `TextLayerMode.ENABLE`. + * @property {number} [annotationMode] - Controls if the annotation layer is + * created, and if interactive form elements or `AnnotationStorage`-data are + * being rendered. The constants from {@link AnnotationMode} should be used; + * see also {@link RenderParameters} and {@link GetOperatorListParameters}. + * The default value is `AnnotationMode.ENABLE_FORMS`. * @property {IPDFAnnotationLayerFactory} annotationLayerFactory * @property {IPDFXfaLayerFactory} xfaLayerFactory * @property {IPDFStructTreeLayerFactory} structTreeLayerFactory * @property {string} [imageResourcesPath] - Path for image resources, mainly * for annotation icons. Include trailing slash. - * @property {boolean} renderInteractiveForms - Turns on rendering of - * interactive form elements. The default value is `true`. * @property {string} renderer - 'canvas' or 'svg'. The default is 'canvas'. * @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default * value is `false`. @@ -88,11 +92,10 @@ class PDFPageView { this._optionalContentConfigPromise = options.optionalContentConfigPromise || null; this.hasRestrictedScaling = false; - this.textLayerMode = Number.isInteger(options.textLayerMode) - ? options.textLayerMode - : TextLayerMode.ENABLE; + this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; + this._annotationMode = + options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.imageResourcesPath = options.imageResourcesPath || ""; - this.renderInteractiveForms = options.renderInteractiveForms !== false; this.useOnlyCssZoom = options.useOnlyCssZoom || false; this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS; @@ -638,7 +641,10 @@ class PDFPageView { } ); - if (this.annotationLayerFactory) { + if ( + this._annotationMode !== AnnotationMode.DISABLE && + this.annotationLayerFactory + ) { if (!this.annotationLayer) { this.annotationLayer = this.annotationLayerFactory.createAnnotationLayerBuilder( @@ -646,9 +652,9 @@ class PDFPageView { pdfPage, /* annotationStorage = */ null, this.imageResourcesPath, - this.renderInteractiveForms, + this._annotationMode === AnnotationMode.ENABLE_FORMS, this.l10n, - /* enableScripting */ null, + /* enableScripting = */ null, /* hasJSActionsPromise = */ null, /* mouseState = */ null ); @@ -787,7 +793,7 @@ class PDFPageView { canvasContext: ctx, transform, viewport: this.viewport, - renderInteractiveForms: this.renderInteractiveForms, + annotationMode: this._annotationMode, optionalContentConfigPromise: this._optionalContentConfigPromise, }; const renderTask = this.pdfPage.render(renderContext); @@ -839,24 +845,28 @@ class PDFPageView { const pdfPage = this.pdfPage; const actualSizeViewport = this.viewport.clone({ scale: CSS_UNITS }); - const promise = pdfPage.getOperatorList().then(opList => { - ensureNotCancelled(); - const svgGfx = new SVGGraphics( - pdfPage.commonObjs, - pdfPage.objs, - /* forceDataSchema = */ compatibilityParams.disableCreateObjectURL - ); - return svgGfx.getSVG(opList, actualSizeViewport).then(svg => { + const promise = pdfPage + .getOperatorList({ + annotationMode: this._annotatationMode, + }) + .then(opList => { ensureNotCancelled(); - this.svg = svg; - this.paintedViewportMap.set(svg, actualSizeViewport); + const svgGfx = new SVGGraphics( + pdfPage.commonObjs, + pdfPage.objs, + /* forceDataSchema = */ compatibilityParams.disableCreateObjectURL + ); + return svgGfx.getSVG(opList, actualSizeViewport).then(svg => { + ensureNotCancelled(); + this.svg = svg; + this.paintedViewportMap.set(svg, actualSizeViewport); - svg.style.width = wrapper.style.width; - svg.style.height = wrapper.style.height; - this.renderingState = RenderingStates.FINISHED; - wrapper.appendChild(svg); + svg.style.width = wrapper.style.width; + svg.style.height = wrapper.style.height; + this.renderingState = RenderingStates.FINISHED; + wrapper.appendChild(svg); + }); }); - }); return { promise, diff --git a/web/pdf_print_service.js b/web/pdf_print_service.js index f2bd607f6..58acfeb61 100644 --- a/web/pdf_print_service.js +++ b/web/pdf_print_service.js @@ -14,6 +14,7 @@ */ import { PDFPrintServiceFactory, PDFViewerApplication } from "./app.js"; +import { AnnotationMode } from "pdfjs-lib"; import { compatibilityParams } from "./app_options.js"; import { getXfaHtmlForPrinting } from "./print_utils.js"; @@ -49,7 +50,7 @@ function renderPage( transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }), intent: "print", - includeAnnotationStorage: true, + annotationMode: AnnotationMode.ENABLE_STORAGE, optionalContentConfigPromise, }; return pdfPage.render(renderContext).promise;