Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add form printing support #12076

Closed
wants to merge 14 commits into from
197 changes: 171 additions & 26 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,21 @@ class AnnotationFactory {
* instance.
*/
static create(xref, ref, pdfManager, idFactory) {
return pdfManager.ensure(this, "_create", [
xref,
ref,
pdfManager,
idFactory,
]);
return pdfManager.ensureDoc("acroForm").then(acroForm => {
return pdfManager.ensure(this, "_create", [
xref,
ref,
pdfManager,
idFactory,
acroForm,
]);
});
}

/**
* @private
*/
static _create(xref, ref, pdfManager, idFactory) {
static _create(xref, ref, pdfManager, idFactory, acroForm) {
const dict = xref.fetchIfRef(ref);
if (!isDict(dict)) {
return undefined;
Expand All @@ -78,6 +81,7 @@ class AnnotationFactory {
subtype,
id,
pdfManager,
acroForm,
calixteman marked this conversation as resolved.
Show resolved Hide resolved
};

switch (subtype) {
Expand Down Expand Up @@ -215,6 +219,7 @@ function getTransformMatrix(rect, bbox, matrix) {

const xRatio = (rect[2] - rect[0]) / (maxX - minX);
const yRatio = (rect[3] - rect[1]) / (maxY - minY);

calixteman marked this conversation as resolved.
Show resolved Hide resolved
return [
xRatio,
0,
Expand Down Expand Up @@ -471,6 +476,7 @@ class Annotation {
*/
setAppearance(dict) {
this.appearance = null;
this.checkAppearance = null;
calixteman marked this conversation as resolved.
Show resolved Hide resolved

const appearanceStates = dict.get("AP");
if (!isDict(appearanceStates)) {
Expand All @@ -493,6 +499,7 @@ class Annotation {
if (!isName(as) || !normalAppearanceState.has(as.name)) {
return;
}

this.appearance = normalAppearanceState.get(as.name);
}

Expand All @@ -509,13 +516,14 @@ class Annotation {
});
}

getOperatorList(evaluator, task, renderForms) {
getOperatorList(evaluator, task, renderForms, annotationStorage) {
if (!this.appearance) {
return Promise.resolve(new OperatorList());
}

const data = this.data;
const appearanceDict = this.appearance.dict;
const appearance = this.appearance;
const appearanceDict = appearance.dict;
const resourcesPromise = this.loadResources([
"ExtGState",
"ColorSpace",
Expand All @@ -533,14 +541,14 @@ class Annotation {
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
return evaluator
.getOperatorList({
stream: this.appearance,
stream: appearance,
task,
resources,
operatorList: opList,
})
.then(() => {
opList.addOp(OPS.endAnnotation, []);
this.appearance.reset();
appearance.reset();
return opList;
});
});
Expand Down Expand Up @@ -799,7 +807,9 @@ class WidgetAnnotation extends Annotation {
const fieldType = getInheritableProperty({ dict, key: "FT" });
data.fieldType = isName(fieldType) ? fieldType.name : null;
this.fieldResources =
getInheritableProperty({ dict, key: "DR" }) || Dict.empty;
getInheritableProperty({ dict, key: "DR" }) ||
params.acroForm.get("DR") ||
Dict.empty;

data.fieldFlags = getInheritableProperty({ dict, key: "Ff" });
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
Expand Down Expand Up @@ -877,13 +887,18 @@ class WidgetAnnotation extends Annotation {
return !!(this.data.fieldFlags & flag);
}

getOperatorList(evaluator, task, renderForms) {
getOperatorList(evaluator, task, renderForms, annotationStorage) {
// Do not render form elements on the canvas when interactive forms are
// enabled. The display layer is responsible for rendering them instead.
if (renderForms) {
return Promise.resolve(new OperatorList());
}
return super.getOperatorList(evaluator, task, renderForms);
return super.getOperatorList(
evaluator,
task,
renderForms,
annotationStorage
);
}
}

Expand Down Expand Up @@ -920,20 +935,98 @@ class TextWidgetAnnotation extends WidgetAnnotation {
this.data.maxLen !== null;
}

getOperatorList(evaluator, task, renderForms) {
if (renderForms || this.appearance) {
return super.getOperatorList(evaluator, task, renderForms);
}
getOperatorList(evaluator, task, renderForms, annotationStorage) {
const self = this;
calixteman marked this conversation as resolved.
Show resolved Hide resolved
return this.getAppearance(evaluator, task, annotationStorage).then(
content => {
if (renderForms || (self.appearance && content === "")) {
return super.getOperatorList(
evaluator,
task,
renderForms,
annotationStorage
);
}

const operatorList = new OperatorList();
const operatorList = new OperatorList();

// Even if there is an appearance stream, ignore it. Self is the
calixteman marked this conversation as resolved.
Show resolved Hide resolved
// behaviour used by Adobe Reader.
if (!self.data.defaultAppearance || content === "") {
return Promise.resolve(operatorList);
calixteman marked this conversation as resolved.
Show resolved Hide resolved
}

const matrix = [1, 0, 0, 1, 0, 0];
const bbox = [
0,
0,
self.data.rect[2] - self.data.rect[0],
self.data.rect[3] - self.data.rect[1],
];

const transform = getTransformMatrix(self.data.rect, bbox, matrix);
operatorList.addOp(OPS.beginAnnotation, [
self.data.rect,
transform,
matrix,
]);

const stream = new Stream(stringToBytes(content));
calixteman marked this conversation as resolved.
Show resolved Hide resolved
return evaluator
.getOperatorList({
stream,
task,
resources: self.fieldResources,
operatorList,
})
.then(function () {
operatorList.addOp(OPS.endAnnotation, []);
return operatorList;
});
}
);
}

getAppearance(evaluator, task, annotationStorage) {
calixteman marked this conversation as resolved.
Show resolved Hide resolved
// TODO: handle password: we mustn't write it in clear
calixteman marked this conversation as resolved.
Show resolved Hide resolved
if (!annotationStorage) {
return Promise.resolve("");
calixteman marked this conversation as resolved.
Show resolved Hide resolved
}
const value = annotationStorage[this.data.id] || "";
if (value === "") {
return Promise.resolve("");
}
const x = 2;
calixteman marked this conversation as resolved.
Show resolved Hide resolved
const y = 2;
if (this.data.comb) {
const width = this.data.rect[2] - this.data.rect[0];
const combWidth = (width / this.data.maxLen).toFixed(2);
let buf = `/Tx BMC q BT ${this.data.defaultAppearance} 1 0 0 1 ${x} ${y} Tm`;
let first = true;
for (const c of value) {
calixteman marked this conversation as resolved.
Show resolved Hide resolved
if (first) {
buf += ` (${c}) Tj`;
first = false;
} else {
buf += ` ${combWidth} 0 Td (${c}) Tj`;
}
}
buf += " ET Q EMC";
return Promise.resolve(buf);
}

// Even if there is an appearance stream, ignore it. This is the
// behaviour used by Adobe Reader.
if (!this.data.defaultAppearance) {
return Promise.resolve(operatorList);
const defaultAppearance = this.data.defaultAppearance;
const app = `/Tx BMC q BT ${defaultAppearance} 1 0 0 1 ${x} ${y} Tm (${value}) Tj ET Q EMC`;
calixteman marked this conversation as resolved.
Show resolved Hide resolved
const alignment = this.data.textAlignment;
if (alignment === 0 || alignment > 2) {
// Left alignment: nothing to do
return Promise.resolve(app);
}

const stream = new Stream(stringToBytes(this.data.defaultAppearance));
// We need to get the width of the text in order to align it correctly
const operatorList = new OperatorList();
const stream = new Stream(stringToBytes(app));
calixteman marked this conversation as resolved.
Show resolved Hide resolved
const totalWidth = this.data.rect[2] - this.data.rect[0];
return evaluator
.getOperatorList({
stream,
Expand All @@ -942,15 +1035,34 @@ class TextWidgetAnnotation extends WidgetAnnotation {
operatorList,
})
.then(function () {
return operatorList;
const fns = operatorList.fnArray;
const args = operatorList.argsArray;
const fontSize = args[fns.findIndex(f => f === OPS.setFont)][1];
const glyphs = args[fns.findIndex(f => f === OPS.showText)][0];

const scale = fontSize / 1000;
let width = 0;
for (const g of glyphs) {
calixteman marked this conversation as resolved.
Show resolved Hide resolved
width += g.width * scale;
}

let shift;
if (alignment === 1) {
// Center
shift = (totalWidth - width) / 2;
} else {
// Right
shift = totalWidth - width - 2;
}

return `/Tx BMC q BT ${defaultAppearance} 1 0 0 1 ${shift} ${y} Tm (${value}) Tj ET Q EMC`;
});
}
}

class ButtonWidgetAnnotation extends WidgetAnnotation {
constructor(params) {
super(params);

this.data.checkBox =
!this.hasFieldFlag(AnnotationFieldFlag.RADIO) &&
!this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
Expand All @@ -970,6 +1082,31 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
}
}

getOperatorList(evaluator, task, renderForms, annotationStorage) {
if (annotationStorage) {
const value = annotationStorage[this.data.id] || false;
if (value && this.checkedAppearance) {
const savedAppearance = this.appearance;
this.appearance = this.checkedAppearance;
const opL = super.getOperatorList(
calixteman marked this conversation as resolved.
Show resolved Hide resolved
evaluator,
task,
renderForms,
annotationStorage
);
this.appearance = savedAppearance;
return opL;
}
return Promise.resolve(new OperatorList());
}
return super.getOperatorList(
evaluator,
task,
renderForms,
annotationStorage
);
}

_processCheckBox(params) {
if (isName(this.data.fieldValue)) {
this.data.fieldValue = this.data.fieldValue.name;
Expand All @@ -993,6 +1130,13 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {

this.data.exportValue =
exportValues[0] === "Off" ? exportValues[1] : exportValues[0];

const normalAppearanceState = customAppearance.get("N");
if (!isDict(normalAppearanceState)) {
return;
}

this.checkedAppearance = normalAppearanceState.get(this.data.exportValue);
}

_processRadioButton(params) {
Expand Down Expand Up @@ -1023,6 +1167,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
break;
}
}
this.checkedAppearance = normalAppearanceState.get(this.data.buttonValue);
}

_processPushButton(params) {
Expand Down
16 changes: 14 additions & 2 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,14 @@ class Page {
});
}

getOperatorList({ handler, sink, task, intent, renderInteractiveForms }) {
getOperatorList({
handler,
sink,
task,
intent,
renderInteractiveForms,
annotationStorage,
}) {
const contentStreamPromise = this.pdfManager.ensure(
this,
"getContentStream"
Expand Down Expand Up @@ -302,7 +309,12 @@ class Page {
if (isAnnotationRenderable(annotation, intent)) {
opListPromises.push(
annotation
.getOperatorList(partialEvaluator, task, renderInteractiveForms)
.getOperatorList(
partialEvaluator,
task,
renderInteractiveForms,
annotationStorage
)
.catch(function (reason) {
warn(
"getOperatorList - ignoring annotation data during " +
Expand Down
2 changes: 2 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ class WorkerMessageHandler {
"GetOperatorList",
function wphSetupRenderPage(data, sink) {
var pageIndex = data.pageIndex;
const annotationStorage = data.annotationStorage;
pdfManager.getPage(pageIndex).then(function (page) {
var task = new WorkerTask(`GetOperatorList: page ${pageIndex}`);
startWorkerTask(task);
Expand All @@ -532,6 +533,7 @@ class WorkerMessageHandler {
task,
intent: data.intent,
renderInteractiveForms: data.renderInteractiveForms,
annotationStorage,
})
.then(
function (operatorListInfo) {
Expand Down
Loading