Skip to content

Commit

Permalink
Ensure that all necessary /Font resources are included when saving a …
Browse files Browse the repository at this point in the history
…`WidgetAnnotation`-instance (issue 12294)

This patch contains a possible approach for fixing issue 12294, which compared to other PRs is purposely limited to the affected `WidgetAnnotation` code.

As mentioned elsewhere, considering that we're (at least for now) trying to fix *one specific* case, I think that we should avoid modifying the `Dict` primitive[1] and/or avoid a solution that (indirectly) modifies an existing `Dict`-instance[2].
This patch simply fixes the issue at hand, since that seems easiest for now, and I'd suggest that we worry about a more general approach if/when that actually becomes necessary.

Hence the solution implemented here, for `WidgetAnnotation`, is to simply use a combination of the local *and* AcroForm /DR resources during OperatorList-parsing to ensure that things work correctly regardless of where a particular /Font resource is found.
For saving of form-data, on the other hand, we want to avoid increasing the file-size unnecessarily and need to be smarter than just merging all of the available resources. To achive this, a new `WidgetAnnotation._getSaveFieldResources` method will when necessary produce a combined resources `Dict` with only the minimum amount of data from the AcroForm /DR resources included.

---
[1] You want to avoid anything that could cause the general `Dict` implementation to become slower, or more complex, just for handling an edge-case in my opinion.

[2] If an existing `Dict`-instance is modified unexpectedly, that could very easily lead to problems elsewhere since e.g. `Dict`-instances created during parsing are not expected to be changed.
  • Loading branch information
Snuffleupagus committed Sep 10, 2020
1 parent 865de9a commit 70edbb2
Showing 1 changed file with 68 additions and 15 deletions.
83 changes: 68 additions & 15 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -885,10 +885,18 @@ class WidgetAnnotation extends Annotation {
"";
const fieldType = getInheritableProperty({ dict, key: "FT" });
data.fieldType = isName(fieldType) ? fieldType.name : null;
this.fieldResources =
getInheritableProperty({ dict, key: "DR" }) ||
params.acroForm.get("DR") ||
Dict.empty;

const localResources = getInheritableProperty({ dict, key: "DR" });
const acroFormResources = params.acroForm.get("DR");
this._fieldResources = {
localResources,
acroFormResources,
mergedResources: Dict.merge({
xref: params.xref,
dictArray: [localResources, acroFormResources],
mergeSubDicts: true,
}),
};

data.fieldFlags = getInheritableProperty({ dict, key: "Ff" });
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
Expand Down Expand Up @@ -1043,7 +1051,7 @@ class WidgetAnnotation extends Annotation {
.getOperatorList({
stream,
task,
resources: this.fieldResources,
resources: this._fieldResources.mergedResources,
operatorList,
})
.then(function () {
Expand All @@ -1067,8 +1075,9 @@ class WidgetAnnotation extends Annotation {
if (appearance === null) {
return null;
}
const { xref } = evaluator;

const dict = evaluator.xref.fetchIfRef(this.ref);
const dict = xref.fetchIfRef(this.ref);
if (!isDict(dict)) {
return null;
}
Expand All @@ -1086,11 +1095,11 @@ class WidgetAnnotation extends Annotation {
value,
};

const newRef = evaluator.xref.getNewRef();
const AP = new Dict(evaluator.xref);
const newRef = xref.getNewRef();
const AP = new Dict(xref);
AP.set("N", newRef);

const encrypt = evaluator.xref.encrypt;
const encrypt = xref.encrypt;
let originalTransform = null;
let newTransform = null;
if (encrypt) {
Expand All @@ -1106,10 +1115,10 @@ class WidgetAnnotation extends Annotation {
dict.set("AP", AP);
dict.set("M", `D:${getModificationDate()}`);

const appearanceDict = new Dict(evaluator.xref);
const appearanceDict = new Dict(xref);
appearanceDict.set("Length", appearance.length);
appearanceDict.set("Subtype", Name.get("Form"));
appearanceDict.set("Resources", this.fieldResources);
appearanceDict.set("Resources", this._getSaveFieldResources(xref));
appearanceDict.set("BBox", bbox);

const bufferOriginal = [`${this.ref.num} ${this.ref.gen} obj\n`];
Expand All @@ -1132,6 +1141,8 @@ class WidgetAnnotation extends Annotation {
}

async _getAppearance(evaluator, task, annotationStorage) {
this._fontName = null;

const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD);
if (!annotationStorage || isPassword) {
return null;
Expand All @@ -1148,9 +1159,8 @@ class WidgetAnnotation extends Annotation {

const fontInfo = await this._getFontData(evaluator, task);
const [font, fontName] = fontInfo;
let fontSize = fontInfo[2];

fontSize = this._computeFontSize(font, fontName, fontSize, totalHeight);
const fontSize = this._computeFontSize(...fontInfo, totalHeight);
this._fontName = fontName;

let descent = font.descent;
if (isNaN(descent)) {
Expand Down Expand Up @@ -1226,7 +1236,7 @@ class WidgetAnnotation extends Annotation {
await evaluator.getOperatorList({
stream: new StringStream(this.data.defaultAppearance),
task,
resources: this.fieldResources,
resources: this._fieldResources.mergedResources,
operatorList,
initialState,
});
Expand Down Expand Up @@ -1280,6 +1290,49 @@ class WidgetAnnotation extends Annotation {

return `${shift} ${vPadding} Td (${escapeString(text)}) Tj`;
}

/**
* @private
*/
_getSaveFieldResources(xref) {
if (
typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")
) {
assert(
this._fontName !== undefined,
"Expected `_getAppearance()` to have been called."
);
}
const { localResources, acroFormResources } = this._fieldResources;

if (!this._fontName) {
return localResources || Dict.empty;
}
if (localResources instanceof Dict) {
const localFont = localResources.get("Font");
if (localFont instanceof Dict && localFont.has(this._fontName)) {
return localResources;
}
}
if (acroFormResources instanceof Dict) {
const acroFormFont = acroFormResources.get("Font");
if (acroFormFont instanceof Dict && acroFormFont.has(this._fontName)) {
const subFontDict = new Dict(xref);
subFontDict.set(this._fontName, acroFormFont.getRaw(this._fontName));

const subResourcesDict = new Dict(xref);
subResourcesDict.set("Font", subFontDict);

return Dict.merge({
xref,
dictArray: [subResourcesDict, localResources],
mergeSubDicts: true,
});
}
}
return localResources || Dict.empty;
}
}

class TextWidgetAnnotation extends WidgetAnnotation {
Expand Down

0 comments on commit 70edbb2

Please sign in to comment.