Merge pull request #13243 from janpe2/ocg-ve

Implement visibility expressions for optional content
This commit is contained in:
Tim van der Meij 2021-04-14 20:42:49 +02:00 committed by GitHub
commit ae48d07582
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 10 deletions

View File

@ -1306,6 +1306,43 @@ class PartialEvaluator {
throw new FormatError(`Unknown PatternName: ${patternName}`); throw new FormatError(`Unknown PatternName: ${patternName}`);
} }
_parseVisibilityExpression(array, nestingCounter, currentResult) {
const MAX_NESTING = 10;
if (++nestingCounter > MAX_NESTING) {
warn("Visibility expression is too deeply nested");
return;
}
const length = array.length;
const operator = this.xref.fetchIfRef(array[0]);
if (length < 2 || !isName(operator)) {
warn("Invalid visibility expression");
return;
}
switch (operator.name) {
case "And":
case "Or":
case "Not":
currentResult.push(operator.name);
break;
default:
warn(`Invalid operator ${operator.name} in visibility expression`);
return;
}
for (let i = 1; i < length; i++) {
const raw = array[i];
const object = this.xref.fetchIfRef(raw);
if (Array.isArray(object)) {
const nestedResult = [];
currentResult.push(nestedResult);
// Recursively parse a subarray.
this._parseVisibilityExpression(object, nestingCounter, nestedResult);
} else if (isRef(raw)) {
// Reference to an OCG dictionary.
currentResult.push(raw.toString());
}
}
}
async parseMarkedContentProps(contentProperties, resources) { async parseMarkedContentProps(contentProperties, resources) {
let optionalContent; let optionalContent;
if (isName(contentProperties)) { if (isName(contentProperties)) {
@ -1324,6 +1361,18 @@ class PartialEvaluator {
id: optionalContent.objId, id: optionalContent.objId,
}; };
} else if (optionalContentType === "OCMD") { } else if (optionalContentType === "OCMD") {
const expression = optionalContent.get("VE");
if (Array.isArray(expression)) {
const result = [];
this._parseVisibilityExpression(expression, 0, result);
if (result.length > 0) {
return {
type: "OCMD",
expression: result,
};
}
}
const optionalContentGroups = optionalContent.get("OCGs"); const optionalContentGroups = optionalContent.get("OCGs");
if ( if (
Array.isArray(optionalContentGroups) || Array.isArray(optionalContentGroups) ||
@ -1339,19 +1388,13 @@ class PartialEvaluator {
groupIds.push(optionalContentGroups.objId); groupIds.push(optionalContentGroups.objId);
} }
let expression = null;
if (optionalContent.get("VE")) {
// TODO support visibility expression.
expression = true;
}
return { return {
type: optionalContentType, type: optionalContentType,
ids: groupIds, ids: groupIds,
policy: isName(optionalContent.get("P")) policy: isName(optionalContent.get("P"))
? optionalContent.get("P").name ? optionalContent.get("P").name
: null, : null,
expression, expression: null,
}; };
} else if (isRef(optionalContentGroups)) { } else if (isRef(optionalContentGroups)) {
return { return {

View File

@ -57,6 +57,43 @@ class OptionalContentConfig {
} }
} }
_evaluateVisibilityExpression(array) {
const length = array.length;
if (length < 2) {
return true;
}
const operator = array[0];
for (let i = 1; i < length; i++) {
const element = array[i];
let state;
if (Array.isArray(element)) {
state = this._evaluateVisibilityExpression(element);
} else if (this._groups.has(element)) {
state = this._groups.get(element).visible;
} else {
warn(`Optional content group not found: ${element}`);
return true;
}
switch (operator) {
case "And":
if (!state) {
return false;
}
break;
case "Or":
if (state) {
return true;
}
break;
case "Not":
return !state;
default:
return true;
}
}
return operator === "And";
}
isVisible(group) { isVisible(group) {
if (group.type === "OCG") { if (group.type === "OCG") {
if (!this._groups.has(group.id)) { if (!this._groups.has(group.id)) {
@ -65,10 +102,9 @@ class OptionalContentConfig {
} }
return this._groups.get(group.id).visible; return this._groups.get(group.id).visible;
} else if (group.type === "OCMD") { } else if (group.type === "OCMD") {
// Per the spec, the expression should be preferred if available. Until // Per the spec, the expression should be preferred if available.
// we implement this, just fallback to using the group policy for now.
if (group.expression) { if (group.expression) {
warn("Visibility expression not supported yet."); return this._evaluateVisibilityExpression(group.expression);
} }
if (!group.policy || group.policy === "AnyOn") { if (!group.policy || group.policy === "AnyOn") {
// Default // Default

View File

@ -354,6 +354,7 @@
!issue2128r.pdf !issue2128r.pdf
!issue5540.pdf !issue5540.pdf
!issue5549.pdf !issue5549.pdf
!visibility_expressions.pdf
!issue5475.pdf !issue5475.pdf
!issue10519_reduced.pdf !issue10519_reduced.pdf
!annotation-border-styles.pdf !annotation-border-styles.pdf

Binary file not shown.

View File

@ -2635,6 +2635,12 @@
"link": false, "link": false,
"type": "eq" "type": "eq"
}, },
{ "id": "visibility_expressions",
"file": "pdfs/visibility_expressions.pdf",
"md5": "bc530d90984ddaa2cc7e0cd53fc2cf34",
"rounds": 1,
"type": "eq"
},
{ "id": "issue7580-text", { "id": "issue7580-text",
"file": "pdfs/issue7580.pdf", "file": "pdfs/issue7580.pdf",
"md5": "44dd5a9b4373fcab9890cf567722a766", "md5": "44dd5a9b4373fcab9890cf567722a766",