diff --git a/src/core/obj.js b/src/core/obj.js index 9139a7bf610400..d110094224d7cc 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -315,6 +315,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")) @@ -327,6 +388,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 571c2449482a36..6ee04e733b41f8 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -773,9 +773,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(); @@ -957,10 +957,9 @@ class PDFDocumentProxy { * @property {AnnotationStorage} [annotationStorage] - Storage for annotation * data in forms. * @property {Promise} [optionalContentConfigPromise] - - * A promise that should resolve with an {@link OptionalContentConfig} - * created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`, - * the configuration will be fetched automatically with the default visibility - * states set. + * A promise that resolves with an {@link OptionalContentConfig} created from + * `PDFDocumentProxy.getOptionalContentConfig`. If `null`, the configuration + * will be fetched automatically with the default visibility states set. */ /** diff --git a/src/display/optional_content_config.js b/src/display/optional_content_config.js index 16308623d12b13..5e14c1284b5924 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,