Skip to content

Commit

Permalink
[api-minor] Implement basic support for OptionalContent Usage dicts…
Browse files Browse the repository at this point in the history
… (issue 5764, bug 1826783)
  • Loading branch information
Snuffleupagus committed Feb 27, 2024
1 parent 06aef89 commit 002ea3f
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 63 deletions.
75 changes: 61 additions & 14 deletions src/core/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,20 +445,10 @@ 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 = this.#readOptionalContentConfig(defaultConfig, groupRefs);
config.groups = groups;
} catch (ex) {
if (ex instanceof MissingDataException) {
Expand All @@ -469,7 +459,64 @@ class Catalog {
return shadow(this, "optionalContentConfig", config);
}

_readOptionalContentConfig(config, contentGroupRefs) {
#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 (!Array.isArray(intent)) {
intent = [intent];
}
if (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 = [];
if (Array.isArray(refs)) {
Expand Down
55 changes: 35 additions & 20 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -949,12 +949,26 @@ class PDFDocumentProxy {
}

/**
* @typedef {Object} GetOptionalContentConfigParameters
* @property {string} [intent] - Determines the optional content groups that
* are visible by default; valid values are:
* - 'display' (viewable groups).
* - 'print' (printable groups).
* - 'any' (all groups).
* The default value is 'display'.
*/

/**
* @param {GetOptionalContentConfigParameters} [params] - Optional content
* config parameters.
* @returns {Promise<OptionalContentConfig>} 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);
}

/**
Expand Down Expand Up @@ -1340,17 +1354,14 @@ class PDFPageProxy {
}

/**
* @param {GetAnnotationsParameters} params - Annotation parameters.
* @param {GetAnnotationsParameters} [params] - Annotation parameters.
* @returns {Promise<Array<any>>} A promise that is resolved with an
* {Array} of the annotation objects.
*/
getAnnotations({ intent = "display" } = {}) {
const intentArgs = this._transport.getRenderingIntent(intent);
const { renderingIntent } = this._transport.getRenderingIntent(intent);

return this._transport.getAnnotations(
this._pageIndex,
intentArgs.renderingIntent
);
return this._transport.getAnnotations(this._pageIndex, renderingIntent);
}

/**
Expand Down Expand Up @@ -1411,20 +1422,20 @@ class PDFPageProxy {
annotationMode,
printAnnotationStorage
);
const { renderingIntent, cacheKey } = intentArgs;
// If there was a pending destroy, cancel it so no cleanup happens during
// this call to render...
this.#pendingCleanup = false;
// ... and ensure that a delayed cleanup is always aborted.
this.#abortDelayedCleanup();

if (!optionalContentConfigPromise) {
optionalContentConfigPromise = this._transport.getOptionalContentConfig();
}
optionalContentConfigPromise ||=
this._transport.getOptionalContentConfig(renderingIntent);

let intentState = this._intentStates.get(intentArgs.cacheKey);
let intentState = this._intentStates.get(cacheKey);
if (!intentState) {
intentState = Object.create(null);
this._intentStates.set(intentArgs.cacheKey, intentState);
this._intentStates.set(cacheKey, intentState);
}

// Ensure that a pending `streamReader` cancel timeout is always aborted.
Expand All @@ -1433,9 +1444,7 @@ class PDFPageProxy {
intentState.streamReaderCancelTimeout = null;
}

const intentPrint = !!(
intentArgs.renderingIntent & RenderingIntentFlag.PRINT
);
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.
Expand Down Expand Up @@ -1512,6 +1521,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,
Expand Down Expand Up @@ -2994,10 +3009,10 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetOutline", null);
}

getOptionalContentConfig() {
return this.messageHandler
.sendWithPromise("GetOptionalContentConfig", null)
.then(results => new OptionalContentConfig(results));
getOptionalContentConfig(renderingIntent) {
return this.#cacheSimpleMethod("GetOptionalContentConfig").then(
data => new OptionalContentConfig(data, renderingIntent)
);
}

getPermissions() {
Expand Down
46 changes: 39 additions & 7 deletions src/display/optional_content_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,63 @@
* 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 {
#isDisplay = false;

#isPrint = false;

#userSet = false;

#visible = true;

constructor(name, intent) {
constructor(renderingIntent, { name, intent, usage }) {
this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY);
this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);

this.name = name;
this.intent = intent;
this.usage = usage;
}

/**
* @type {boolean}
*/
get visible() {
return this.#visible;
if (this.#userSet) {
return this.#visible;
}
if (!this.#visible) {
return false;
}
const { print, view } = this.usage;

if (this.#isDisplay) {
return view?.viewState !== "OFF";
} else if (this.#isPrint) {
return print?.printState !== "OFF";
}
return true;
}

/**
* @ignore
*/
_setVisible(internal, visible) {
_setVisible(internal, visible, userSet = false) {
if (internal !== INTERNAL) {
unreachable("Internal method `_setVisible` called.");
}
this.#userSet = userSet;
this.#visible = visible;
}
}
Expand All @@ -53,7 +83,9 @@ class OptionalContentConfig {

#order = null;

constructor(data) {
constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) {
this.renderingIntent = renderingIntent;

this.name = null;
this.creator = null;

Expand All @@ -66,7 +98,7 @@ class OptionalContentConfig {
for (const group of data.groups) {
this.#groups.set(
group.id,
new OptionalContentGroup(group.name, group.intent)
new OptionalContentGroup(renderingIntent, group)
);
}

Expand Down Expand Up @@ -202,7 +234,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;
}
Expand Down
4 changes: 3 additions & 1 deletion test/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/bug1826783.pdf.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://bugzilla.mozilla.org/attachment.cgi?id=9327375
17 changes: 17 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4016,6 +4016,23 @@
"lastPage": 5,
"type": "eq"
},
{
"id": "bug1826783-display",
"file": "pdfs/bug1826783.pdf",
"md5": "93e706efee15dd7b32d32d66f15a3ea2",
"rounds": 1,
"link": true,
"type": "eq"
},
{
"id": "bug1826783-print",
"file": "pdfs/bug1826783.pdf",
"md5": "93e706efee15dd7b32d32d66f15a3ea2",
"rounds": 1,
"link": true,
"type": "eq",
"print": true
},
{
"id": "issue8586",
"file": "pdfs/issue8586.pdf",
Expand Down
1 change: 0 additions & 1 deletion web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1845,7 +1845,6 @@ const PDFViewerApplication = {
pagesOverview: this.pdfViewer.getPagesOverview(),
printContainer: this.appConfig.printContainer,
printResolution: AppOptions.get("printResolution"),
optionalContentConfigPromise: this.pdfViewer.optionalContentConfigPromise,
printAnnotationStoragePromise: this._printAnnotationStoragePromise,
});
this.forceRendering();
Expand Down
6 changes: 3 additions & 3 deletions web/firefox_print_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
10 changes: 2 additions & 8 deletions web/firefoxcom.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,8 @@ if (PDFJSDev.test("GECKOVIEW")) {
const hasWillPrint =
pdfViewer.enableScripting &&
!!(await pdfDocument.getJSActions())?.WillPrint;
const hasUnchangedOptionalContent = (
await pdfViewer.optionalContentConfigPromise
).hasInitialVisibility;

result =
hasUnchangedAnnotations &&
!hasWillPrint &&
hasUnchangedOptionalContent;

result = hasUnchangedAnnotations && !hasWillPrint;
} catch {
console.warn("Unable to check if the document can be downloaded.");
}
Expand Down
2 changes: 1 addition & 1 deletion web/pdf_layer_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 002ea3f

Please sign in to comment.