Skip to content

Commit

Permalink
Include the /Order array, if available, when parsing the Optional C…
Browse files Browse the repository at this point in the history
…ontent configuration

The `/Order` array is used to improve the display of Optional Content groups in PDF viewers, and it allows a PDF document to e.g. specify that Optional Content groups should be displayed as a (collapsable) tree-structure rather than as just a list.

Note that not all available Optional Content groups must be present in the `/Order` array, and PDF viewers will often (by default) hide those toggles in the UI.
To allow us to improve the UX around toggling of Optional Content groups, in the default viewer, these hidden-by-default groups are thus appended to the parsed `/Order` array under a *custom* nesting level (with `name == null`).

Finally, the patch also slightly improves the `OptionalContentConfig` related JSDoc-comments in the API.
  • Loading branch information
Snuffleupagus committed Aug 6, 2020
1 parent 5aabe13 commit 076037e
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 7 deletions.
63 changes: 63 additions & 0 deletions src/core/obj.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -327,6 +388,8 @@ class Catalog {
: null,
on: parseOnOff(config.get("ON")),
off: parseOnOff(config.get("OFF")),
order: parseOrder(config.get("Order")),
groups: null,
};
}

Expand Down
13 changes: 6 additions & 7 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -773,9 +773,9 @@ class PDFDocumentProxy {
}

/**
* @returns {Promise<OptionalContentConfig | null>} 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<OptionalContentConfig>} 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();
Expand Down Expand Up @@ -957,10 +957,9 @@ class PDFDocumentProxy {
* @property {AnnotationStorage} [annotationStorage] - Storage for annotation
* data in forms.
* @property {Promise<OptionalContentConfig>} [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.
*/

/**
Expand Down
2 changes: 2 additions & 0 deletions src/display/optional_content_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ class OptionalContentConfig {
constructor(data) {
this.name = null;
this.creator = null;
this._order = null;
this.groups = new Map();

if (data === null) {
return;
}
this.name = data.name;
this.creator = data.creator;
this._order = data.order;
for (const group of data.groups) {
this.groups.set(
group.id,
Expand Down

0 comments on commit 076037e

Please sign in to comment.