diff --git a/src/core/obj.js b/src/core/obj.js index f4b9d2e85..0e1ffbcb6 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -355,6 +355,67 @@ class Catalog { return onParsed; } + function parseOrder(refs, nestedLevels = 0) { + if (!Array.isArray(refs)) { + return null; + } + const order = []; + + for (const value of refs) { + if (isRef(value) && contentGroupRefs.includes(value)) { + parsedOrderRefs.put(value); // Handle "hidden" groups, see below. + + order.push(value.toString()); + continue; + } + // Handle nested /Order arrays (see e.g. issue 9462 and bug 1240641). + const nestedOrder = parseNestedOrder(value, nestedLevels); + if (nestedOrder) { + order.push(nestedOrder); + } + } + + if (nestedLevels > 0) { + return order; + } + const hiddenGroups = []; + for (const groupRef of contentGroupRefs) { + if (parsedOrderRefs.has(groupRef)) { + continue; + } + hiddenGroups.push(groupRef.toString()); + } + if (hiddenGroups.length) { + order.push({ name: null, order: hiddenGroups }); + } + + return order; + } + + function parseNestedOrder(ref, nestedLevels) { + if (++nestedLevels > MAX_NESTED_LEVELS) { + warn("parseNestedOrder - reached MAX_NESTED_LEVELS."); + return null; + } + const value = xref.fetchIfRef(ref); + if (!Array.isArray(value)) { + return null; + } + const nestedName = xref.fetchIfRef(value[0]); + if (typeof nestedName !== "string") { + return null; + } + const nestedOrder = parseOrder(value.slice(1), nestedLevels); + if (!nestedOrder || !nestedOrder.length) { + return null; + } + return { name: stringToPDFString(nestedName), order: nestedOrder }; + } + + const xref = this.xref, + parsedOrderRefs = new RefSet(), + MAX_NESTED_LEVELS = 10; + return { name: isString(config.get("Name")) ? stringToPDFString(config.get("Name")) @@ -367,6 +428,8 @@ class Catalog { : null, on: parseOnOff(config.get("ON")), off: parseOnOff(config.get("OFF")), + order: parseOrder(config.get("Order")), + groups: null, }; } diff --git a/src/display/api.js b/src/display/api.js index f9a295dcc..bb7b184a6 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -778,9 +778,9 @@ class PDFDocumentProxy { } /** - * @returns {Promise} A promise that is resolved - * with an {@link OptionalContentConfig} that has all the optional content - * groups, or `null` if the document does not have any. + * @returns {Promise} 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(); diff --git a/src/display/optional_content_config.js b/src/display/optional_content_config.js index 16308623d..5e14c1284 100644 --- a/src/display/optional_content_config.js +++ b/src/display/optional_content_config.js @@ -26,6 +26,7 @@ class OptionalContentConfig { constructor(data) { this.name = null; this.creator = null; + this._order = null; this.groups = new Map(); if (data === null) { @@ -33,6 +34,7 @@ class OptionalContentConfig { } this.name = data.name; this.creator = data.creator; + this._order = data.order; for (const group of data.groups) { this.groups.set( group.id,