Skip to content

Commit

Permalink
implementing optional content radiobutton groups
Browse files Browse the repository at this point in the history
  • Loading branch information
agrahn committed Sep 30, 2024
1 parent a7e1bf6 commit b53a353
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 15 deletions.
50 changes: 40 additions & 10 deletions src/core/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,17 @@ class Catalog {
return shadow(this, "optionalContentConfig", null);
}
const groups = [];
const groupRefs = new RefSet();
const groupsMap = new Map();
// Ensure all the optional content groups are valid.
for (const groupRef of groupsData) {
if (!(groupRef instanceof Ref) || groupRefs.has(groupRef)) {
if (!(groupRef instanceof Ref) || groupsMap.has(groupRef)) {
continue;
}
groupRefs.put(groupRef);

groups.push(this.#readOptionalContentGroup(groupRef));
const group = this.#readOptionalContentGroup(groupRef);
groups.push(group);
groupsMap.set(groupRef, group);
}
config = this.#readOptionalContentConfig(defaultConfig, groupRefs);
config = this.#readOptionalContentConfig(defaultConfig, groupsMap);
config.groups = groups;
} catch (ex) {
if (ex instanceof MissingDataException) {
Expand All @@ -517,6 +517,7 @@ class Catalog {
print: null,
view: null,
},
myRbGroups: [],
};

const name = group.get("Name");
Expand Down Expand Up @@ -565,15 +566,15 @@ class Catalog {
return obj;
}

#readOptionalContentConfig(config, contentGroupRefs) {
#readOptionalContentConfig(config, contentGroupsMap) {
function parseOnOff(refs) {
const onParsed = [];
if (Array.isArray(refs)) {
for (const value of refs) {
if (!(value instanceof Ref)) {
continue;
}
if (contentGroupRefs.has(value)) {
if (contentGroupsMap.has(value)) {
onParsed.push(value.toString());
}
}
Expand All @@ -588,7 +589,7 @@ class Catalog {
const order = [];

for (const value of refs) {
if (value instanceof Ref && contentGroupRefs.has(value)) {
if (value instanceof Ref && contentGroupsMap.has(value)) {
parsedOrderRefs.put(value); // Handle "hidden" groups, see below.

order.push(value.toString());
Expand All @@ -605,7 +606,7 @@ class Catalog {
return order;
}
const hiddenGroups = [];
for (const groupRef of contentGroupRefs) {
for (const groupRef of contentGroupsMap.keys()) {
if (parsedOrderRefs.has(groupRef)) {
continue;
}
Expand Down Expand Up @@ -638,10 +639,39 @@ class Catalog {
return { name: stringToPDFString(nestedName), order: nestedOrder };
}

function parseRBGroups(rbgrps) {
if (!Array.isArray(rbgrps)) {
return null;
}

// iterate over RB groups (literal arrays or refs thereof)
for (const value of rbgrps) {
const rbGroup = value instanceof Ref ? xref.fetchIfRef(value) : value;
// ignore wrong (non-array) refs and empty arrays
if (!Array.isArray(rbGroup) || !rbGroup.length) {
continue;
}

const parsedRbGroup = new Set();

for (const ref of rbGroup) {
if (ref instanceof Ref && contentGroupsMap.has(ref)) {
parsedRbGroup.add(ref.toString());
// keep a record of which RB groups the current ocg belongs to
contentGroupsMap.get(ref).myRbGroups.push(parsedRbGroup);
}
}
}

return null;
}

const xref = this.xref,
parsedOrderRefs = new RefSet(),
MAX_NESTED_LEVELS = 10;

parseRBGroups(config.get("RBGroups"));

return {
name:
typeof config.get("Name") === "string"
Expand Down
25 changes: 20 additions & 5 deletions src/display/optional_content_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ class OptionalContentGroup {

#visible = true;

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

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

/**
Expand Down Expand Up @@ -229,12 +230,26 @@ class OptionalContentConfig {
return true;
}

setVisibility(id, visible = true) {
setVisibility(id, visible = true, preserveRB = true) {
const group = this.#groups.get(id);
if (!group) {
warn(`Optional content group not found: ${id}`);
return;
}

// if my visibility is about to be set to `true' and if I belong to one or
// more radiobutton groups, hide all other OCGs in these radiobutton groups,
// provided that radio-button state relationships are to be preserved
if (visible && group.myRbGroups.length && preserveRB) {
for (const rbGrp of group.myRbGroups) {
for (const otherId of rbGrp) {
if (otherId !== id) {
this.#groups.get(otherId)._setVisible(INTERNAL, false, true);
}
}
}
}

group._setVisible(INTERNAL, !!visible, /* userSet = */ true);

this.#cachedGetHash = null;
Expand All @@ -258,13 +273,13 @@ class OptionalContentConfig {
}
switch (operator) {
case "ON":
group._setVisible(INTERNAL, true);
this.setVisibility(elem, true, preserveRB);
break;
case "OFF":
group._setVisible(INTERNAL, false);
this.setVisibility(elem, false, preserveRB);
break;
case "Toggle":
group._setVisible(INTERNAL, !group.visible);
this.setVisibility(elem, !group.visible, preserveRB);
break;
}
}
Expand Down
7 changes: 7 additions & 0 deletions web/pdf_layer_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ class PDFLayerViewer extends BaseTreeViewer {
source: this,
promise: Promise.resolve(this._optionalContentConfig),
});

// update the sidebarView (other groups state may have changed
// if radio button groups are involved)
this.render({
optionalContentConfig: this._optionalContentConfig,
pdfDocument: this._pdfDocument,
});
};

element.onclick = evt => {
Expand Down

0 comments on commit b53a353

Please sign in to comment.