diff --git a/src/core/catalog.js b/src/core/catalog.js index 9dd20e0e73be2..c8aafcee1812e 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -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) { @@ -517,6 +517,7 @@ class Catalog { print: null, view: null, }, + rbGroups: [], }; const name = group.get("Name"); @@ -565,7 +566,7 @@ class Catalog { return obj; } - #readOptionalContentConfig(config, contentGroupRefs) { + #readOptionalContentConfig(config, groupRefCache) { function parseOnOff(refs) { const onParsed = []; if (Array.isArray(refs)) { @@ -573,7 +574,7 @@ class Catalog { if (!(value instanceof Ref)) { continue; } - if (contentGroupRefs.has(value)) { + if (groupRefCache.has(value)) { onParsed.push(value.toString()); } } @@ -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()); @@ -605,11 +606,11 @@ class Catalog { return order; } const hiddenGroups = []; - for (const groupRef of contentGroupRefs) { - if (parsedOrderRefs.has(groupRef)) { + for (const item of groupRefCache.items()) { + if (parsedOrderRefs.has(item[0])) { continue; } - hiddenGroups.push(groupRef.toString()); + hiddenGroups.push(item[0].toString()); } if (hiddenGroups.length) { order.push({ name: null, order: hiddenGroups }); @@ -642,6 +643,32 @@ class Catalog { parsedOrderRefs = new RefSet(), MAX_NESTED_LEVELS = 10; + // Parse RBGroups entry. + (rbGroups => { + if (!Array.isArray(rbGroups)) { + return null; + } + + 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.add(ref.toString()); + // Keep a record of which RB groups the current OCG belongs to. + groupRefCache.get(ref).rbGroups.push(parsedRbGroup); + } + } + } + + return null; + })(config.get("RBGroups")); + return { name: typeof config.get("Name") === "string" diff --git a/src/display/optional_content_config.js b/src/display/optional_content_config.js index 366da221230e9..77c833b8dfa93 100644 --- a/src/display/optional_content_config.js +++ b/src/display/optional_content_config.js @@ -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; } /** @@ -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 radiobutton state relationships are to be preserved. + if (visible && preserveRB && group.rbGroups.length) { + for (const rbGrp of group.rbGroups) { + 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; @@ -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; } }