1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-22 16:18:08 +02:00

[api-minor] Add basic support for the SetOCGState action (issue 15372)

Note that this patch implements the `SetOCGState`-handling in `PDFLinkService`, rather than as a new method in `OptionalContentConfig`[1], since this action is nothing but a series of `setVisibility`-calls and that it seems quite uncommon in real-world PDF documents.

The new functionality also required some tweaks in the `PDFLayerViewer`, to ensure that the `layersView` in the sidebar is updated correctly when the optional-content visibility changes from "outside" of `PDFLayerViewer`.

---
[1] We can obviously move this code into `OptionalContentConfig` instead, if deemed necessary, but for an initial implementation I figured that doing it this way might be acceptable.
This commit is contained in:
Jonas Jenwald 2022-08-31 17:50:28 +02:00
parent e9bdbe4574
commit cc4baa2fe9
10 changed files with 185 additions and 31 deletions

View file

@ -329,6 +329,7 @@ class Catalog {
url: data.url,
unsafeUrl: data.unsafeUrl,
newWindow: data.newWindow,
setOCGState: data.setOCGState,
title: stringToPDFString(title),
color: rgbColor,
count: Number.isInteger(count) ? count : undefined,
@ -1533,6 +1534,38 @@ class Catalog {
}
break;
case "SetOCGState":
const state = action.get("State");
const preserveRB = action.get("PreserveRB");
if (!Array.isArray(state) || state.length === 0) {
break;
}
const stateArr = [];
for (const elem of state) {
if (elem instanceof Name) {
switch (elem.name) {
case "ON":
case "OFF":
case "Toggle":
stateArr.push(elem.name);
break;
}
} else if (elem instanceof Ref) {
stateArr.push(elem.toString());
}
}
if (stateArr.length !== state.length) {
break; // Some of the original entries are not valid.
}
resultObj.setOCGState = {
state: stateArr,
preserveRB: typeof preserveRB === "boolean" ? preserveRB : true,
};
break;
case "JavaScript":
const jsAction = action.get("JS");
let js;

View file

@ -597,6 +597,9 @@ class LinkAnnotationElement extends AnnotationElement {
} else if (data.action) {
this._bindNamedAction(link, data.action);
isBound = true;
} else if (data.setOCGState) {
this.#bindSetOCGState(link, data.setOCGState);
isBound = true;
} else if (data.dest) {
this._bindLink(link, data.dest);
isBound = true;
@ -678,6 +681,20 @@ class LinkAnnotationElement extends AnnotationElement {
link.className = "internalLink";
}
/**
* Bind SetOCGState actions to the link element.
* @param {Object} link
* @param {Object} action
*/
#bindSetOCGState(link, action) {
link.href = this.linkService.getAnchorUrl("");
link.onclick = () => {
this.linkService.executeSetOCGState(action);
return false;
};
link.className = "internalLink";
}
/**
* Bind JS actions to the link element.
*

View file

@ -14,6 +14,7 @@
*/
import { objectFromMap, unreachable, warn } from "../shared/util.js";
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
const INTERNAL = Symbol("INTERNAL");
@ -44,11 +45,11 @@ class OptionalContentGroup {
}
class OptionalContentConfig {
#cachedHasInitialVisibility = true;
#cachedGetHash = null;
#groups = new Map();
#initialVisibility = null;
#initialHash = null;
#order = null;
@ -84,10 +85,7 @@ class OptionalContentConfig {
}
// The following code must always run *last* in the constructor.
this.#initialVisibility = new Map();
for (const [id, group] of this.#groups) {
this.#initialVisibility.set(id, group.visible);
}
this.#initialHash = this.getHash();
}
#evaluateVisibilityExpression(array) {
@ -206,20 +204,11 @@ class OptionalContentConfig {
}
this.#groups.get(id)._setVisible(INTERNAL, !!visible);
this.#cachedHasInitialVisibility = null;
this.#cachedGetHash = null;
}
get hasInitialVisibility() {
if (this.#cachedHasInitialVisibility !== null) {
return this.#cachedHasInitialVisibility;
}
for (const [id, group] of this.#groups) {
const visible = this.#initialVisibility.get(id);
if (group.visible !== visible) {
return (this.#cachedHasInitialVisibility = false);
}
}
return (this.#cachedHasInitialVisibility = true);
return this.getHash() === this.#initialHash;
}
getOrder() {
@ -239,6 +228,18 @@ class OptionalContentConfig {
getGroup(id) {
return this.#groups.get(id) || null;
}
getHash() {
if (this.#cachedGetHash !== null) {
return this.#cachedGetHash;
}
const hash = new MurmurHash3_64();
for (const [id, group] of this.#groups) {
hash.update(`${id}:${group.visible}`);
}
return (this.#cachedGetHash = hash.hexdigest());
}
}
export { OptionalContentConfig };