diff --git a/src/core/catalog.js b/src/core/catalog.js index c308a87f395c7b..2a5f2c1695c0af 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -445,18 +445,8 @@ class Catalog { continue; } groupRefs.put(groupRef); - const group = this.xref.fetch(groupRef); - groups.push({ - id: groupRef.toString(), - name: - typeof group.get("Name") === "string" - ? stringToPDFString(group.get("Name")) - : null, - intent: - typeof group.get("Intent") === "string" - ? stringToPDFString(group.get("Intent")) - : null, - }); + + groups.push(this.#readOptionalContentGroup(groupRef)); } config = this._readOptionalContentConfig(defaultConfig, groupRefs); config.groups = groups; @@ -469,6 +459,65 @@ class Catalog { return shadow(this, "optionalContentConfig", config); } + #readOptionalContentGroup(groupRef) { + const group = this.xref.fetch(groupRef); + const obj = { + id: groupRef.toString(), + name: null, + intent: null, + usage: { + print: null, + view: null, + }, + }; + + const name = group.get("Name"); + if (typeof name === "string") { + obj.name = stringToPDFString(name); + } + + let intent = group.getArray("Intent"); + if (typeof intent === "string") { + intent = [intent]; + } + if (Array.isArray(intent) && intent.every(i => i instanceof Name)) { + obj.intent = intent.map(i => i.name); + } + + const usage = group.get("Usage"); + if (usage instanceof Dict) { + const usageObj = obj.usage; + + const print = usage.get("Print"); + if (print instanceof Dict) { + const printState = print.get("PrintState"); + + if (printState instanceof Name) { + switch (printState.name) { + case "ON": + case "OFF": + usageObj.print = { printState: printState.name }; + } + } + } + + const view = usage.get("View"); + if (view instanceof Dict) { + const viewState = view.get("ViewState"); + + if (viewState instanceof Name) { + switch (viewState.name) { + case "ON": + case "OFF": + usageObj.view = { viewState: viewState.name }; + } + } + } + } + + return obj; + } + _readOptionalContentConfig(config, contentGroupRefs) { function parseOnOff(refs) { const onParsed = []; diff --git a/src/display/api.js b/src/display/api.js index 2ae61ea2904e1e..47b02485cd2c7a 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -949,12 +949,16 @@ class PDFDocumentProxy { } /** + * @param {string} [intent] - Rendering intent, can be 'display', 'print', + * or 'any'. The default value is 'display'. * @returns {Promise} A promise that is resolved with * an {@link OptionalContentConfig} that contains all the optional content * groups (assuming that the document has any). */ - getOptionalContentConfig() { - return this._transport.getOptionalContentConfig(); + getOptionalContentConfig(intent = "display") { + const { renderingIntent } = this._transport.getRenderingIntent(intent); + + return this._transport.getOptionalContentConfig(renderingIntent); } /** @@ -1418,7 +1422,9 @@ class PDFPageProxy { this.#abortDelayedCleanup(); if (!optionalContentConfigPromise) { - optionalContentConfigPromise = this._transport.getOptionalContentConfig(); + optionalContentConfigPromise = this._transport.getOptionalContentConfig( + intentArgs.renderingIntent + ); } let intentState = this._intentStates.get(intentArgs.cacheKey); @@ -1433,9 +1439,8 @@ class PDFPageProxy { intentState.streamReaderCancelTimeout = null; } - const intentPrint = !!( - intentArgs.renderingIntent & RenderingIntentFlag.PRINT - ); + const { renderingIntent } = intentArgs; + const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); // If there's no displayReadyCapability yet, then the operatorList // was never requested before. Make the request and create the promise. @@ -1512,6 +1517,12 @@ class PDFPageProxy { } this._stats?.time("Rendering"); + if (!(optionalContentConfig.renderingIntent & renderingIntent)) { + throw new Error( + "Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + + "and `PDFDocumentProxy.getOptionalContentConfig` methods." + ); + } internalRenderTask.initializeGraphics({ transparency, optionalContentConfig, @@ -2994,10 +3005,10 @@ class WorkerTransport { return this.messageHandler.sendWithPromise("GetOutline", null); } - getOptionalContentConfig() { + getOptionalContentConfig(renderingIntent) { return this.messageHandler .sendWithPromise("GetOptionalContentConfig", null) - .then(results => new OptionalContentConfig(results)); + .then(data => new OptionalContentConfig(data, renderingIntent)); } getPermissions() { diff --git a/src/display/optional_content_config.js b/src/display/optional_content_config.js index 12c9a76211ad47..c904b15645861b 100644 --- a/src/display/optional_content_config.js +++ b/src/display/optional_content_config.js @@ -13,33 +13,61 @@ * limitations under the License. */ -import { info, objectFromMap, unreachable, warn } from "../shared/util.js"; +import { + info, + objectFromMap, + RenderingIntentFlag, + unreachable, + warn, +} from "../shared/util.js"; import { MurmurHash3_64 } from "../shared/murmurhash3.js"; const INTERNAL = Symbol("INTERNAL"); class OptionalContentGroup { + #userSet = false; + + #renderingIntent = RenderingIntentFlag.DISPLAY; + #visible = true; - constructor(name, intent) { + constructor(renderingIntent, { name, intent, usage }) { + this.#renderingIntent = renderingIntent; + this.name = name; this.intent = intent; + this.usage = usage; } /** * @type {boolean} */ get visible() { + if (this.#userSet) { + return this.#visible; + } + const { print, view } = this.usage; + + if (this.#renderingIntent & RenderingIntentFlag.DISPLAY) { + if (view?.viewState) { + return this.#visible && view.viewState === "ON"; + } + } else if (this.#renderingIntent & RenderingIntentFlag.PRINT) { + if (print?.printState) { + return this.#visible && print.printState === "ON"; + } + } return this.#visible; } /** * @ignore */ - _setVisible(internal, visible) { + _setVisible(internal, visible, userSet = false) { if (internal !== INTERNAL) { unreachable("Internal method `_setVisible` called."); } + this.#userSet = userSet; this.#visible = visible; } } @@ -53,7 +81,9 @@ class OptionalContentConfig { #order = null; - constructor(data) { + constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) { + this.renderingIntent = renderingIntent; + this.name = null; this.creator = null; @@ -66,7 +96,7 @@ class OptionalContentConfig { for (const group of data.groups) { this.#groups.set( group.id, - new OptionalContentGroup(group.name, group.intent) + new OptionalContentGroup(renderingIntent, group) ); } @@ -202,7 +232,7 @@ class OptionalContentConfig { warn(`Optional content group not found: ${id}`); return; } - this.#groups.get(id)._setVisible(INTERNAL, !!visible); + this.#groups.get(id)._setVisible(INTERNAL, !!visible, /* userSet = */ true); this.#cachedGetHash = null; } diff --git a/test/driver.js b/test/driver.js index 7d1fd6e099f3a1..5bbd5a2157f615 100644 --- a/test/driver.js +++ b/test/driver.js @@ -684,7 +684,9 @@ class Driver { } task.pdfDoc = doc; - task.optionalContentConfigPromise = doc.getOptionalContentConfig(); + task.optionalContentConfigPromise = doc.getOptionalContentConfig( + /* intent = */ task.print ? "print" : "display" + ); if (task.optionalContent) { const entries = Object.entries(task.optionalContent), diff --git a/web/app.js b/web/app.js index e9d40b0e249142..157dbf41f30012 100644 --- a/web/app.js +++ b/web/app.js @@ -1801,7 +1801,6 @@ const PDFViewerApplication = { pagesOverview: this.pdfViewer.getPagesOverview(), printContainer: this.appConfig.printContainer, printResolution: AppOptions.get("printResolution"), - optionalContentConfigPromise: this.pdfViewer.optionalContentConfigPromise, printAnnotationStoragePromise: this._printAnnotationStoragePromise, }); this.forceRendering(); diff --git a/web/firefox_print_service.js b/web/firefox_print_service.js index 8241ed64dd6a27..a66962bdf87b19 100644 --- a/web/firefox_print_service.js +++ b/web/firefox_print_service.js @@ -119,15 +119,15 @@ class FirefoxPrintService { pagesOverview, printContainer, printResolution, - optionalContentConfigPromise = null, printAnnotationStoragePromise = null, }) { this.pdfDocument = pdfDocument; this.pagesOverview = pagesOverview; this.printContainer = printContainer; this._printResolution = printResolution || 150; - this._optionalContentConfigPromise = - optionalContentConfigPromise || pdfDocument.getOptionalContentConfig(); + this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig( + /* intent = */ "print" + ); this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve(); } diff --git a/web/pdf_layer_viewer.js b/web/pdf_layer_viewer.js index d139de9430b524..d55dcff118de0b 100644 --- a/web/pdf_layer_viewer.js +++ b/web/pdf_layer_viewer.js @@ -182,7 +182,7 @@ class PDFLayerViewer extends BaseTreeViewer { } const pdfDocument = this._pdfDocument; const optionalContentConfig = await (promise || - pdfDocument.getOptionalContentConfig()); + pdfDocument.getOptionalContentConfig(/* intent = */ "display")); if (pdfDocument !== this._pdfDocument) { return; // The document was closed while the optional content resolved. diff --git a/web/pdf_print_service.js b/web/pdf_print_service.js index cc4b330a629c59..18a3b3d4094911 100644 --- a/web/pdf_print_service.js +++ b/web/pdf_print_service.js @@ -13,7 +13,12 @@ * limitations under the License. */ -import { AnnotationMode, PixelsPerInch, shadow } from "pdfjs-lib"; +import { + AnnotationMode, + PixelsPerInch, + RenderingCancelledException, + shadow, +} from "pdfjs-lib"; import { getXfaHtmlForPrinting } from "./print_utils.js"; let activeService = null; @@ -58,7 +63,14 @@ function renderPage( optionalContentConfigPromise, printAnnotationStorage, }; - return pdfPage.render(renderContext).promise; + const renderTask = pdfPage.render(renderContext); + + return renderTask.promise.catch(reason => { + if (!(reason instanceof RenderingCancelledException)) { + console.error(reason); + } + throw reason; + }); }); } @@ -68,15 +80,15 @@ class PDFPrintService { pagesOverview, printContainer, printResolution, - optionalContentConfigPromise = null, printAnnotationStoragePromise = null, }) { this.pdfDocument = pdfDocument; this.pagesOverview = pagesOverview; this.printContainer = printContainer; this._printResolution = printResolution || 150; - this._optionalContentConfigPromise = - optionalContentConfigPromise || pdfDocument.getOptionalContentConfig(); + this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig( + /* intent = */ "print" + ); this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve(); this.currentPage = -1; diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index a16d84c4f36516..3f31a2d0eb7ea4 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -189,7 +189,9 @@ class PDFThumbnailViewer { return; } const firstPagePromise = pdfDocument.getPage(1); - const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig(); + const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig( + /* intent = */ "display" + ); firstPagePromise .then(firstPdfPage => { diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 4f88bcb1d5860e..f918ddcebbcbef 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -781,7 +781,9 @@ class PDFViewer { const pagesCount = pdfDocument.numPages; const firstPagePromise = pdfDocument.getPage(1); // Rendering (potentially) depends on this, hence fetching it immediately. - const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig(); + const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig( + /* intent = */ "display" + ); const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve(); @@ -1822,7 +1824,9 @@ class PDFViewer { console.error("optionalContentConfigPromise: Not initialized yet."); // Prevent issues if the getter is accessed *before* the `onePageRendered` // promise has resolved; won't (normally) happen in the default viewer. - return this.pdfDocument.getOptionalContentConfig(); + return this.pdfDocument.getOptionalContentConfig( + /* intent = */ "display" + ); } return this._optionalContentConfigPromise; }