Skip to content

Commit

Permalink
Optional Content (OC) radiobutton (RB) groups implemented. Resolves #…
Browse files Browse the repository at this point in the history
…18823.

The code parses the /RBGroups entry in the OC configuration dict and adds the property `rbGroups' to instances of the OptionalContentGroup class. rbGroups takes an array of Sets, where each Set instance represents an RB group the OptionalContentGroup instance is a member of. Such a Set instance contains all OCG ids within the corresponding RB group. RB groups an OCG is associated with are processed when its visibility is set to true, as required by the PDF spec.
  • Loading branch information
agrahn committed Oct 15, 2024
1 parent e1f9fa4 commit 441efe4
Show file tree
Hide file tree
Showing 5 changed files with 78 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 groupRefCache = new RefSetCache();
// Ensure all the optional content groups are valid.
for (const groupRef of groupsData) {
if (!(groupRef instanceof Ref) || groupRefs.has(groupRef)) {
if (!(groupRef instanceof Ref) || groupRefCache.has(groupRef)) {
continue;
}
groupRefs.put(groupRef);

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

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

#readOptionalContentConfig(config, contentGroupRefs) {
#readOptionalContentConfig(config, groupRefCache) {
function parseOnOff(refs) {
const onParsed = [];
if (Array.isArray(refs)) {
for (const value of refs) {
if (!(value instanceof Ref)) {
continue;
}
if (contentGroupRefs.has(value)) {
if (groupRefCache.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 && groupRefCache.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 groupRefCache.items()) {
if (parsedOrderRefs.has(groupRef)) {
continue;
}
Expand Down Expand Up @@ -638,10 +639,39 @@ class Catalog {
return { name: stringToPDFString(nestedName), order: nestedOrder };
}

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

for (const value of rbGroups) {
const rbGroup = xref.fetchIfRef(value);
if (!Array.isArray(rbGroup) || !rbGroup.length) {
continue;
}

const parsedRbGroup = new Set();

for (const ref of rbGroup) {
if (
ref instanceof Ref &&
groupRefCache.has(ref) &&
!parsedRbGroup.has(ref.toString())
) {
parsedRbGroup.add(ref.toString());
// Keep a record of which RB groups the current OCG belongs to.
groupRefCache.get(ref).rbGroups.push(parsedRbGroup);
}
}
}
}

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, rbGroups }) {
this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY);
this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);

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

/**
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 the visibility is about to be set to `true` and the group belongs to
// any radiobutton groups, hide all other OCGs in these radiobutton groups,
// provided that radiobutton state relationships are to be preserved.
if (preserveRB && visible && group.rbGroups.length) {
for (const rbGroup of group.rbGroups) {
for (const otherId of rbGroup) {
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
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@
!issue15690.pdf
!bug1802888.pdf
!issue15759.pdf
!issue18823.pdf
!issue15753.pdf
!issue15789.pdf
!fields_order.pdf
Expand Down
Binary file added test/pdfs/issue18823.pdf
Binary file not shown.
17 changes: 17 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6875,6 +6875,23 @@
"7R": false
}
},
{
"id": "issue18823-default",
"file": "pdfs/issue18823.pdf",
"md5": "f5246c476516c96df106ced0c5839da3",
"rounds": 1,
"type": "eq"
},
{
"id": "issue18823-group-three",
"file": "pdfs/issue18823.pdf",
"md5": "f5246c476516c96df106ced0c5839da3",
"rounds": 1,
"type": "eq",
"optionalContent": {
"15R": true
}
},
{
"id": "issue2829",
"file": "pdfs/issue2829.pdf",
Expand Down

0 comments on commit 441efe4

Please sign in to comment.