pdf-lib
Version:
Create and modify PDF files with JavaScript
384 lines • 17.5 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertFieldAppearanceOptions = void 0;
var tslib_1 = require("tslib");
var PDFDocument_1 = tslib_1.__importDefault(require("../PDFDocument"));
var colors_1 = require("../colors");
var rotations_1 = require("../rotations");
var core_1 = require("../../core");
var utils_1 = require("../../utils");
var image_1 = require("../image");
var operations_1 = require("../operations");
exports.assertFieldAppearanceOptions = function (options) {
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.x, 'options.x', ['number']);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.y, 'options.y', ['number']);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.width, 'options.width', ['number']);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.height, 'options.height', ['number']);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.textColor, 'options.textColor', [
[Object, 'Color'],
]);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.backgroundColor, 'options.backgroundColor', [
[Object, 'Color'],
]);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.borderColor, 'options.borderColor', [
[Object, 'Color'],
]);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.borderWidth, 'options.borderWidth', ['number']);
utils_1.assertOrUndefined(options === null || options === void 0 ? void 0 : options.rotate, 'options.rotate', [[Object, 'Rotation']]);
};
/**
* Represents a field of a [[PDFForm]].
*
* This class is effectively abstract. All fields in a [[PDFForm]] will
* actually be an instance of a subclass of this class.
*
* Note that each field in a PDF is represented by a single field object.
* However, a given field object may be rendered at multiple locations within
* the document (across one or more pages). The rendering of a field is
* controlled by its widgets. Each widget causes its field to be displayed at a
* particular location in the document.
*
* Most of the time each field in a PDF has only a single widget, and thus is
* only rendered once. However, if a field is rendered multiple times, it will
* have multiple widgets - one for each location it is rendered.
*
* This abstraction of field objects and widgets is defined in the PDF
* specification and dictates how PDF files store fields and where they are
* to be rendered.
*/
var PDFField = /** @class */ (function () {
function PDFField(acroField, ref, doc) {
utils_1.assertIs(acroField, 'acroField', [[core_1.PDFAcroTerminal, 'PDFAcroTerminal']]);
utils_1.assertIs(ref, 'ref', [[core_1.PDFRef, 'PDFRef']]);
utils_1.assertIs(doc, 'doc', [[PDFDocument_1.default, 'PDFDocument']]);
this.acroField = acroField;
this.ref = ref;
this.doc = doc;
}
/**
* Get the fully qualified name of this field. For example:
* ```js
* const fields = form.getFields()
* fields.forEach(field => {
* const name = field.getName()
* console.log('Field name:', name)
* })
* ```
* Note that PDF fields are structured as a tree. Each field is the
* descendent of a series of ancestor nodes all the way up to the form node,
* which is always the root of the tree. Each node in the tree (except for
* the form node) has a partial name. Partial names can be composed of any
* unicode characters except a period (`.`). The fully qualified name of a
* field is composed of the partial names of all its ancestors joined
* with periods. This means that splitting the fully qualified name on
* periods and taking the last element of the resulting array will give you
* the partial name of a specific field.
* @returns The fully qualified name of this field.
*/
PDFField.prototype.getName = function () {
var _a;
return (_a = this.acroField.getFullyQualifiedName()) !== null && _a !== void 0 ? _a : '';
};
/**
* Returns `true` if this field is read only. This means that PDF readers
* will not allow users to interact with the field or change its value. See
* [[PDFField.enableReadOnly]] and [[PDFField.disableReadOnly]].
* For example:
* ```js
* const field = form.getField('some.field')
* if (field.isReadOnly()) console.log('Read only is enabled')
* ```
* @returns Whether or not this is a read only field.
*/
PDFField.prototype.isReadOnly = function () {
return this.acroField.hasFlag(core_1.AcroFieldFlags.ReadOnly);
};
/**
* Prevent PDF readers from allowing users to interact with this field or
* change its value. The field will not respond to mouse or keyboard input.
* For example:
* ```js
* const field = form.getField('some.field')
* field.enableReadOnly()
* ```
* Useful for fields whose values are computed, imported from a database, or
* prefilled by software before being displayed to the user.
*/
PDFField.prototype.enableReadOnly = function () {
this.acroField.setFlagTo(core_1.AcroFieldFlags.ReadOnly, true);
};
/**
* Allow users to interact with this field and change its value in PDF
* readers via mouse and keyboard input. For example:
* ```js
* const field = form.getField('some.field')
* field.disableReadOnly()
* ```
*/
PDFField.prototype.disableReadOnly = function () {
this.acroField.setFlagTo(core_1.AcroFieldFlags.ReadOnly, false);
};
/**
* Returns `true` if this field must have a value when the form is submitted.
* See [[PDFField.enableRequired]] and [[PDFField.disableRequired]].
* For example:
* ```js
* const field = form.getField('some.field')
* if (field.isRequired()) console.log('Field is required')
* ```
* @returns Whether or not this field is required.
*/
PDFField.prototype.isRequired = function () {
return this.acroField.hasFlag(core_1.AcroFieldFlags.Required);
};
/**
* Require this field to have a value when the form is submitted.
* For example:
* ```js
* const field = form.getField('some.field')
* field.enableRequired()
* ```
*/
PDFField.prototype.enableRequired = function () {
this.acroField.setFlagTo(core_1.AcroFieldFlags.Required, true);
};
/**
* Do not require this field to have a value when the form is submitted.
* For example:
* ```js
* const field = form.getField('some.field')
* field.disableRequired()
* ```
*/
PDFField.prototype.disableRequired = function () {
this.acroField.setFlagTo(core_1.AcroFieldFlags.Required, false);
};
/**
* Returns `true` if this field's value should be exported when the form is
* submitted. See [[PDFField.enableExporting]] and
* [[PDFField.disableExporting]].
* For example:
* ```js
* const field = form.getField('some.field')
* if (field.isExported()) console.log('Exporting is enabled')
* ```
* @returns Whether or not this field's value should be exported.
*/
PDFField.prototype.isExported = function () {
return !this.acroField.hasFlag(core_1.AcroFieldFlags.NoExport);
};
/**
* Indicate that this field's value should be exported when the form is
* submitted in a PDF reader. For example:
* ```js
* const field = form.getField('some.field')
* field.enableExporting()
* ```
*/
PDFField.prototype.enableExporting = function () {
this.acroField.setFlagTo(core_1.AcroFieldFlags.NoExport, false);
};
/**
* Indicate that this field's value should **not** be exported when the form
* is submitted in a PDF reader. For example:
* ```js
* const field = form.getField('some.field')
* field.disableExporting()
* ```
*/
PDFField.prototype.disableExporting = function () {
this.acroField.setFlagTo(core_1.AcroFieldFlags.NoExport, true);
};
/** @ignore */
PDFField.prototype.needsAppearancesUpdate = function () {
throw new core_1.MethodNotImplementedError(this.constructor.name, 'needsAppearancesUpdate');
};
/** @ignore */
PDFField.prototype.defaultUpdateAppearances = function (_font) {
throw new core_1.MethodNotImplementedError(this.constructor.name, 'defaultUpdateAppearances');
};
PDFField.prototype.markAsDirty = function () {
this.doc.getForm().markFieldAsDirty(this.ref);
};
PDFField.prototype.markAsClean = function () {
this.doc.getForm().markFieldAsClean(this.ref);
};
PDFField.prototype.isDirty = function () {
return this.doc.getForm().fieldIsDirty(this.ref);
};
PDFField.prototype.createWidget = function (options) {
var _a;
var textColor = options.textColor;
var backgroundColor = options.backgroundColor;
var borderColor = options.borderColor;
var borderWidth = options.borderWidth;
var degreesAngle = rotations_1.toDegrees(options.rotate);
var caption = options.caption;
var x = options.x;
var y = options.y;
var width = options.width + borderWidth;
var height = options.height + borderWidth;
var hidden = Boolean(options.hidden);
var pageRef = options.page;
utils_1.assertMultiple(degreesAngle, 'degreesAngle', 90);
// Create a widget for this field
var widget = core_1.PDFWidgetAnnotation.create(this.doc.context, this.ref);
// Set widget properties
var rect = rotations_1.rotateRectangle({ x: x, y: y, width: width, height: height }, borderWidth, degreesAngle);
widget.setRectangle(rect);
if (pageRef)
widget.setP(pageRef);
var ac = widget.getOrCreateAppearanceCharacteristics();
if (backgroundColor) {
ac.setBackgroundColor(colors_1.colorToComponents(backgroundColor));
}
ac.setRotation(degreesAngle);
if (caption)
ac.setCaptions({ normal: caption });
if (borderColor)
ac.setBorderColor(colors_1.colorToComponents(borderColor));
var bs = widget.getOrCreateBorderStyle();
if (borderWidth !== undefined)
bs.setWidth(borderWidth);
widget.setFlagTo(core_1.AnnotationFlags.Print, true);
widget.setFlagTo(core_1.AnnotationFlags.Hidden, hidden);
widget.setFlagTo(core_1.AnnotationFlags.Invisible, false);
// Set acrofield properties
if (textColor) {
var da = (_a = this.acroField.getDefaultAppearance()) !== null && _a !== void 0 ? _a : '';
var newDa = da + '\n' + colors_1.setFillingColor(textColor).toString();
this.acroField.setDefaultAppearance(newDa);
}
return widget;
};
PDFField.prototype.updateWidgetAppearanceWithFont = function (widget, font, _a) {
var normal = _a.normal, rollover = _a.rollover, down = _a.down;
this.updateWidgetAppearances(widget, {
normal: this.createAppearanceStream(widget, normal, font),
rollover: rollover && this.createAppearanceStream(widget, rollover, font),
down: down && this.createAppearanceStream(widget, down, font),
});
};
PDFField.prototype.updateOnOffWidgetAppearance = function (widget, onValue, _a) {
var normal = _a.normal, rollover = _a.rollover, down = _a.down;
this.updateWidgetAppearances(widget, {
normal: this.createAppearanceDict(widget, normal, onValue),
rollover: rollover && this.createAppearanceDict(widget, rollover, onValue),
down: down && this.createAppearanceDict(widget, down, onValue),
});
};
PDFField.prototype.updateWidgetAppearances = function (widget, _a) {
var normal = _a.normal, rollover = _a.rollover, down = _a.down;
widget.setNormalAppearance(normal);
if (rollover) {
widget.setRolloverAppearance(rollover);
}
else {
widget.removeRolloverAppearance();
}
if (down) {
widget.setDownAppearance(down);
}
else {
widget.removeDownAppearance();
}
};
// // TODO: Do we need to do this...?
// private foo(font: PDFFont, dict: PDFDict) {
// if (!dict.lookup(PDFName.of('DR'))) {
// dict.set(PDFName.of('DR'), dict.context.obj({}));
// }
// const DR = dict.lookup(PDFName.of('DR'), PDFDict);
// if (!DR.lookup(PDFName.of('Font'))) {
// DR.set(PDFName.of('Font'), dict.context.obj({}));
// }
// const Font = DR.lookup(PDFName.of('Font'), PDFDict);
// Font.set(PDFName.of(font.name), font.ref);
// }
PDFField.prototype.createAppearanceStream = function (widget, appearance, font) {
var _a;
var context = this.acroField.dict.context;
var _b = widget.getRectangle(), width = _b.width, height = _b.height;
// TODO: Do we need to do this...?
// if (font) {
// this.foo(font, widget.dict);
// this.foo(font, this.doc.getForm().acroForm.dict);
// }
// END TODO
var Resources = font && { Font: (_a = {}, _a[font.name] = font.ref, _a) };
var stream = context.formXObject(appearance, {
Resources: Resources,
BBox: context.obj([0, 0, width, height]),
Matrix: context.obj([1, 0, 0, 1, 0, 0]),
});
var streamRef = context.register(stream);
return streamRef;
};
/**
* Create a FormXObject of the supplied image and add it to context.
* The FormXObject size is calculated based on the widget (including
* the alignment).
* @param widget The widget that should display the image.
* @param alignment The alignment of the image.
* @param image The image that should be displayed.
* @returns The ref for the FormXObject that was added to the context.
*/
PDFField.prototype.createImageAppearanceStream = function (widget, image, alignment) {
// NOTE: This implementation doesn't handle image borders.
// NOTE: Acrobat seems to resize the image (maybe even skewing its aspect
// ratio) to fit perfectly within the widget's rectangle. This method
// does not currently do that. Should there be an option for that?
var _a;
var _b;
var context = this.acroField.dict.context;
var rectangle = widget.getRectangle();
var ap = widget.getAppearanceCharacteristics();
var bs = widget.getBorderStyle();
var borderWidth = (_b = bs === null || bs === void 0 ? void 0 : bs.getWidth()) !== null && _b !== void 0 ? _b : 0;
var rotation = rotations_1.reduceRotation(ap === null || ap === void 0 ? void 0 : ap.getRotation());
var rotate = operations_1.rotateInPlace(tslib_1.__assign(tslib_1.__assign({}, rectangle), { rotation: rotation }));
var adj = rotations_1.adjustDimsForRotation(rectangle, rotation);
var imageDims = image.scaleToFit(adj.width - borderWidth * 2, adj.height - borderWidth * 2);
// Support borders on images and maybe other properties
var options = {
x: borderWidth,
y: borderWidth,
width: imageDims.width,
height: imageDims.height,
//
rotate: rotations_1.degrees(0),
xSkew: rotations_1.degrees(0),
ySkew: rotations_1.degrees(0),
};
if (alignment === image_1.ImageAlignment.Center) {
options.x += (adj.width - borderWidth * 2) / 2 - imageDims.width / 2;
options.y += (adj.height - borderWidth * 2) / 2 - imageDims.height / 2;
}
else if (alignment === image_1.ImageAlignment.Right) {
options.x = adj.width - borderWidth - imageDims.width;
options.y = adj.height - borderWidth - imageDims.height;
}
var imageName = this.doc.context.addRandomSuffix('Image', 10);
var appearance = tslib_1.__spreadArrays(rotate, operations_1.drawImage(imageName, options));
////////////
var Resources = { XObject: (_a = {}, _a[imageName] = image.ref, _a) };
var stream = context.formXObject(appearance, {
Resources: Resources,
BBox: context.obj([0, 0, rectangle.width, rectangle.height]),
Matrix: context.obj([1, 0, 0, 1, 0, 0]),
});
return context.register(stream);
};
PDFField.prototype.createAppearanceDict = function (widget, appearance, onValue) {
var context = this.acroField.dict.context;
var onStreamRef = this.createAppearanceStream(widget, appearance.on);
var offStreamRef = this.createAppearanceStream(widget, appearance.off);
var appearanceDict = context.obj({});
appearanceDict.set(onValue, onStreamRef);
appearanceDict.set(core_1.PDFName.of('Off'), offStreamRef);
return appearanceDict;
};
return PDFField;
}());
exports.default = PDFField;
//# sourceMappingURL=PDFField.js.map
;