Add (basic) support for Optional Content in Annotations

Given that Annotations can also have an `OC`-entry, we need to take that into account when generating their operatorLists.

Note that in order to simplify the patch the `getOperatorList`-methods, for the Annotation-classes, were converted to be `async`.
This commit is contained in:
Jonas Jenwald 2022-06-24 14:39:08 +02:00
parent 3fab4af949
commit c48dc251e0
2 changed files with 134 additions and 76 deletions

View File

@ -447,6 +447,7 @@ class Annotation {
this.setColor(dict.getArray("C")); this.setColor(dict.getArray("C"));
this.setBorderStyle(dict); this.setBorderStyle(dict);
this.setAppearance(dict); this.setAppearance(dict);
this.setOptionalContent(dict);
const MK = dict.get("MK"); const MK = dict.get("MK");
this.setBorderAndBackgroundColors(MK); this.setBorderAndBackgroundColors(MK);
@ -842,6 +843,17 @@ class Annotation {
this.appearance = normalAppearanceState.get(as.name); this.appearance = normalAppearanceState.get(as.name);
} }
setOptionalContent(dict) {
this.oc = null;
const oc = dict.get("OC");
if (oc instanceof Name) {
warn("setOptionalContent: Support for /Name-entry is not implemented.");
} else if (oc instanceof Dict) {
this.oc = oc;
}
}
loadResources(keys, appearance) { loadResources(keys, appearance) {
return appearance.dict.getAsync("Resources").then(resources => { return appearance.dict.getAsync("Resources").then(resources => {
if (!resources) { if (!resources) {
@ -855,21 +867,27 @@ class Annotation {
}); });
} }
getOperatorList(evaluator, task, intent, renderForms, annotationStorage) { async getOperatorList(
evaluator,
task,
intent,
renderForms,
annotationStorage
) {
const data = this.data; const data = this.data;
let appearance = this.appearance; let appearance = this.appearance;
const isUsingOwnCanvas = const isUsingOwnCanvas =
this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY; this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY;
if (!appearance) { if (!appearance) {
if (!isUsingOwnCanvas) { if (!isUsingOwnCanvas) {
return Promise.resolve(new OperatorList()); return new OperatorList();
} }
appearance = new StringStream(""); appearance = new StringStream("");
appearance.dict = new Dict(); appearance.dict = new Dict();
} }
const appearanceDict = appearance.dict; const appearanceDict = appearance.dict;
const resourcesPromise = this.loadResources( const resources = await this.loadResources(
["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], ["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"],
appearance appearance
); );
@ -877,30 +895,41 @@ class Annotation {
const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0]; const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0];
const transform = getTransformMatrix(data.rect, bbox, matrix); const transform = getTransformMatrix(data.rect, bbox, matrix);
return resourcesPromise.then(resources => { const opList = new OperatorList();
const opList = new OperatorList();
opList.addOp(OPS.beginAnnotation, [
data.id,
data.rect,
transform,
matrix,
isUsingOwnCanvas,
]);
return evaluator let optionalContent;
.getOperatorList({ if (this.oc) {
stream: appearance, optionalContent = await evaluator.parseMarkedContentProps(
task, this.oc,
resources, /* resources = */ null
operatorList: opList, );
fallbackFontDict: this._fallbackFontDict, }
}) if (optionalContent !== undefined) {
.then(() => { opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
opList.addOp(OPS.endAnnotation, []); }
this.reset();
return opList; opList.addOp(OPS.beginAnnotation, [
}); data.id,
data.rect,
transform,
matrix,
isUsingOwnCanvas,
]);
await evaluator.getOperatorList({
stream: appearance,
task,
resources,
operatorList: opList,
fallbackFontDict: this._fallbackFontDict,
}); });
opList.addOp(OPS.endAnnotation, []);
if (optionalContent !== undefined) {
opList.addOp(OPS.endMarkedContent, []);
}
this.reset();
return opList;
} }
async save(evaluator, task, annotationStorage) { async save(evaluator, task, annotationStorage) {
@ -1575,11 +1604,17 @@ class WidgetAnnotation extends Annotation {
return str; return str;
} }
getOperatorList(evaluator, task, intent, renderForms, annotationStorage) { async getOperatorList(
evaluator,
task,
intent,
renderForms,
annotationStorage
) {
// Do not render form elements on the canvas when interactive forms are // Do not render form elements on the canvas when interactive forms are
// enabled. The display layer is responsible for rendering them instead. // enabled. The display layer is responsible for rendering them instead.
if (renderForms && !(this instanceof SignatureWidgetAnnotation)) { if (renderForms && !(this instanceof SignatureWidgetAnnotation)) {
return Promise.resolve(new OperatorList()); return new OperatorList();
} }
if (!this._hasText) { if (!this._hasText) {
@ -1592,56 +1627,69 @@ class WidgetAnnotation extends Annotation {
); );
} }
return this._getAppearance(evaluator, task, annotationStorage).then( const content = await this._getAppearance(
content => { evaluator,
if (this.appearance && content === null) { task,
return super.getOperatorList( annotationStorage
evaluator,
task,
intent,
renderForms,
annotationStorage
);
}
const operatorList = new OperatorList();
// Even if there is an appearance stream, ignore it. This is the
// behaviour used by Adobe Reader.
if (!this._defaultAppearance || content === null) {
return operatorList;
}
const matrix = [1, 0, 0, 1, 0, 0];
const bbox = [
0,
0,
this.data.rect[2] - this.data.rect[0],
this.data.rect[3] - this.data.rect[1],
];
const transform = getTransformMatrix(this.data.rect, bbox, matrix);
operatorList.addOp(OPS.beginAnnotation, [
this.data.id,
this.data.rect,
transform,
this.getRotationMatrix(annotationStorage),
]);
const stream = new StringStream(content);
return evaluator
.getOperatorList({
stream,
task,
resources: this._fieldResources.mergedResources,
operatorList,
})
.then(function () {
operatorList.addOp(OPS.endAnnotation, []);
return operatorList;
});
}
); );
if (this.appearance && content === null) {
return super.getOperatorList(
evaluator,
task,
intent,
renderForms,
annotationStorage
);
}
const operatorList = new OperatorList();
// Even if there is an appearance stream, ignore it. This is the
// behaviour used by Adobe Reader.
if (!this._defaultAppearance || content === null) {
return operatorList;
}
const matrix = [1, 0, 0, 1, 0, 0];
const bbox = [
0,
0,
this.data.rect[2] - this.data.rect[0],
this.data.rect[3] - this.data.rect[1],
];
const transform = getTransformMatrix(this.data.rect, bbox, matrix);
let optionalContent;
if (this.oc) {
optionalContent = await evaluator.parseMarkedContentProps(
this.oc,
/* resources = */ null
);
}
if (optionalContent !== undefined) {
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
operatorList.addOp(OPS.beginAnnotation, [
this.data.id,
this.data.rect,
transform,
this.getRotationMatrix(annotationStorage),
]);
const stream = new StringStream(content);
await evaluator.getOperatorList({
stream,
task,
resources: this._fieldResources.mergedResources,
operatorList,
});
operatorList.addOp(OPS.endAnnotation, []);
if (optionalContent !== undefined) {
operatorList.addOp(OPS.endMarkedContent, []);
}
return operatorList;
} }
_getMKDict(rotation) { _getMKDict(rotation) {

View File

@ -6561,6 +6561,16 @@
} }
} }
}, },
{ "id": "bug1737260-oc",
"file": "pdfs/bug1737260.pdf",
"md5": "8bd4f810d30972764b07ae141a4afbc4",
"rounds": 1,
"link": true,
"type": "eq",
"optionalContent": {
"191R": false
}
},
{ "id": "bug1737260", { "id": "bug1737260",
"file": "pdfs/bug1737260.pdf", "file": "pdfs/bug1737260.pdf",
"md5": "8bd4f810d30972764b07ae141a4afbc4", "md5": "8bd4f810d30972764b07ae141a4afbc4",