pdfjs-dist
Version:
Generic build of Mozilla's PDF.js library.
1,416 lines (1,414 loc) • 102 kB
JavaScript
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2022 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @licend The above is the entire license notice for the
* JavaScript code in this page
*/
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PopupAnnotation = exports.MarkupAnnotation = exports.AnnotationFactory = exports.AnnotationBorderStyle = exports.Annotation = void 0;
exports.getQuadPoints = getQuadPoints;
var _util = require("../shared/util.js");
var _core_utils = require("./core_utils.js");
var _default_appearance = require("./default_appearance.js");
var _primitives = require("./primitives.js");
var _writer = require("./writer.js");
var _base_stream = require("./base_stream.js");
var _bidi = require("./bidi.js");
var _catalog = require("./catalog.js");
var _colorspace = require("./colorspace.js");
var _file_spec = require("./file_spec.js");
var _object_loader = require("./object_loader.js");
var _operator_list = require("./operator_list.js");
var _stream = require("./stream.js");
var _factory = require("./xfa/factory.js");
class AnnotationFactory {
static create(xref, ref, pdfManager, idFactory, collectFields) {
return Promise.all([pdfManager.ensureCatalog("acroForm"), pdfManager.ensureCatalog("baseUrl"), pdfManager.ensureCatalog("attachments"), pdfManager.ensureDoc("xfaDatasets"), collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1]).then(([acroForm, baseUrl, attachments, xfaDatasets, pageIndex]) => pdfManager.ensure(this, "_create", [xref, ref, pdfManager, idFactory, acroForm, attachments, xfaDatasets, collectFields, pageIndex]));
}
static _create(xref, ref, pdfManager, idFactory, acroForm, attachments = null, xfaDatasets, collectFields, pageIndex = -1) {
const dict = xref.fetchIfRef(ref);
if (!(dict instanceof _primitives.Dict)) {
return undefined;
}
const id = ref instanceof _primitives.Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
let subtype = dict.get("Subtype");
subtype = subtype instanceof _primitives.Name ? subtype.name : null;
const acroFormDict = acroForm instanceof _primitives.Dict ? acroForm : _primitives.Dict.empty;
const parameters = {
xref,
ref,
dict,
subtype,
id,
pdfManager,
acroForm: acroFormDict,
attachments,
xfaDatasets,
collectFields,
needAppearances: !collectFields && acroFormDict.get("NeedAppearances") === true,
pageIndex,
isOffscreenCanvasSupported: _util.FeatureTest.isOffscreenCanvasSupported && pdfManager.evaluatorOptions.isOffscreenCanvasSupported
};
switch (subtype) {
case "Link":
return new LinkAnnotation(parameters);
case "Text":
return new TextAnnotation(parameters);
case "Widget":
let fieldType = (0, _core_utils.getInheritableProperty)({
dict,
key: "FT"
});
fieldType = fieldType instanceof _primitives.Name ? fieldType.name : null;
switch (fieldType) {
case "Tx":
return new TextWidgetAnnotation(parameters);
case "Btn":
return new ButtonWidgetAnnotation(parameters);
case "Ch":
return new ChoiceWidgetAnnotation(parameters);
case "Sig":
return new SignatureWidgetAnnotation(parameters);
}
(0, _util.warn)(`Unimplemented widget field type "${fieldType}", ` + "falling back to base field type.");
return new WidgetAnnotation(parameters);
case "Popup":
return new PopupAnnotation(parameters);
case "FreeText":
return new FreeTextAnnotation(parameters);
case "Line":
return new LineAnnotation(parameters);
case "Square":
return new SquareAnnotation(parameters);
case "Circle":
return new CircleAnnotation(parameters);
case "PolyLine":
return new PolylineAnnotation(parameters);
case "Polygon":
return new PolygonAnnotation(parameters);
case "Caret":
return new CaretAnnotation(parameters);
case "Ink":
return new InkAnnotation(parameters);
case "Highlight":
return new HighlightAnnotation(parameters);
case "Underline":
return new UnderlineAnnotation(parameters);
case "Squiggly":
return new SquigglyAnnotation(parameters);
case "StrikeOut":
return new StrikeOutAnnotation(parameters);
case "Stamp":
return new StampAnnotation(parameters);
case "FileAttachment":
return new FileAttachmentAnnotation(parameters);
default:
if (!collectFields) {
if (!subtype) {
(0, _util.warn)("Annotation is missing the required /Subtype.");
} else {
(0, _util.warn)(`Unimplemented annotation type "${subtype}", ` + "falling back to base annotation.");
}
}
return new Annotation(parameters);
}
}
static async _getPageIndex(xref, ref, pdfManager) {
try {
const annotDict = await xref.fetchIfRefAsync(ref);
if (!(annotDict instanceof _primitives.Dict)) {
return -1;
}
const pageRef = annotDict.getRaw("P");
if (!(pageRef instanceof _primitives.Ref)) {
return -1;
}
const pageIndex = await pdfManager.ensureCatalog("getPageIndex", [pageRef]);
return pageIndex;
} catch (ex) {
(0, _util.warn)(`_getPageIndex: "${ex}".`);
return -1;
}
}
static async saveNewAnnotations(evaluator, task, annotations) {
const xref = evaluator.xref;
let baseFontRef;
const dependencies = [];
const promises = [];
for (const annotation of annotations) {
switch (annotation.annotationType) {
case _util.AnnotationEditorType.FREETEXT:
if (!baseFontRef) {
const baseFont = new _primitives.Dict(xref);
baseFont.set("BaseFont", _primitives.Name.get("Helvetica"));
baseFont.set("Type", _primitives.Name.get("Font"));
baseFont.set("Subtype", _primitives.Name.get("Type1"));
baseFont.set("Encoding", _primitives.Name.get("WinAnsiEncoding"));
const buffer = [];
baseFontRef = xref.getNewTemporaryRef();
(0, _writer.writeObject)(baseFontRef, baseFont, buffer, null);
dependencies.push({
ref: baseFontRef,
data: buffer.join("")
});
}
promises.push(FreeTextAnnotation.createNewAnnotation(xref, annotation, dependencies, {
evaluator,
task,
baseFontRef
}));
break;
case _util.AnnotationEditorType.INK:
promises.push(InkAnnotation.createNewAnnotation(xref, annotation, dependencies));
}
}
return {
annotations: await Promise.all(promises),
dependencies
};
}
static async printNewAnnotations(evaluator, task, annotations) {
if (!annotations) {
return null;
}
const xref = evaluator.xref;
const promises = [];
const isOffscreenCanvasSupported = _util.FeatureTest.isOffscreenCanvasSupported && evaluator.options.isOffscreenCanvasSupported;
for (const annotation of annotations) {
switch (annotation.annotationType) {
case _util.AnnotationEditorType.FREETEXT:
promises.push(FreeTextAnnotation.createNewPrintAnnotation(xref, annotation, {
evaluator,
task,
isOffscreenCanvasSupported
}));
break;
case _util.AnnotationEditorType.INK:
promises.push(InkAnnotation.createNewPrintAnnotation(xref, annotation, {
isOffscreenCanvasSupported
}));
break;
}
}
return Promise.all(promises);
}
}
exports.AnnotationFactory = AnnotationFactory;
function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) {
if (!Array.isArray(color)) {
return defaultColor;
}
const rgbColor = defaultColor || new Uint8ClampedArray(3);
switch (color.length) {
case 0:
return null;
case 1:
_colorspace.ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
case 3:
_colorspace.ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
case 4:
_colorspace.ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
default:
return defaultColor;
}
}
function getPdfColorArray(color) {
return Array.from(color, c => c / 255);
}
function getQuadPoints(dict, rect) {
const quadPoints = dict.getArray("QuadPoints");
if (!Array.isArray(quadPoints) || quadPoints.length === 0 || quadPoints.length % 8 > 0) {
return null;
}
const quadPointsLists = [];
for (let i = 0, ii = quadPoints.length / 8; i < ii; i++) {
let minX = Infinity,
maxX = -Infinity,
minY = Infinity,
maxY = -Infinity;
for (let j = i * 8, jj = i * 8 + 8; j < jj; j += 2) {
const x = quadPoints[j];
const y = quadPoints[j + 1];
minX = Math.min(x, minX);
maxX = Math.max(x, maxX);
minY = Math.min(y, minY);
maxY = Math.max(y, maxY);
}
if (rect !== null && (minX < rect[0] || maxX > rect[2] || minY < rect[1] || maxY > rect[3])) {
return null;
}
quadPointsLists.push([{
x: minX,
y: maxY
}, {
x: maxX,
y: maxY
}, {
x: minX,
y: minY
}, {
x: maxX,
y: minY
}]);
}
return quadPointsLists;
}
function getTransformMatrix(rect, bbox, matrix) {
const [minX, minY, maxX, maxY] = _util.Util.getAxialAlignedBoundingBox(bbox, matrix);
if (minX === maxX || minY === maxY) {
return [1, 0, 0, 1, rect[0], rect[1]];
}
const xRatio = (rect[2] - rect[0]) / (maxX - minX);
const yRatio = (rect[3] - rect[1]) / (maxY - minY);
return [xRatio, 0, 0, yRatio, rect[0] - minX * xRatio, rect[1] - minY * yRatio];
}
class Annotation {
constructor(params) {
const {
dict,
xref
} = params;
this.setTitle(dict.get("T"));
this.setContents(dict.get("Contents"));
this.setModificationDate(dict.get("M"));
this.setFlags(dict.get("F"));
this.setRectangle(dict.getArray("Rect"));
this.setColor(dict.getArray("C"));
this.setBorderStyle(dict);
this.setAppearance(dict);
this.setOptionalContent(dict);
const MK = dict.get("MK");
this.setBorderAndBackgroundColors(MK);
this.setRotation(MK);
this._streams = [];
if (this.appearance) {
this._streams.push(this.appearance);
}
this.data = {
annotationFlags: this.flags,
borderStyle: this.borderStyle,
color: this.color,
backgroundColor: this.backgroundColor,
borderColor: this.borderColor,
rotation: this.rotation,
contentsObj: this._contents,
hasAppearance: !!this.appearance,
id: params.id,
modificationDate: this.modificationDate,
rect: this.rectangle,
subtype: params.subtype,
hasOwnCanvas: false
};
if (params.collectFields) {
const kids = dict.get("Kids");
if (Array.isArray(kids)) {
const kidIds = [];
for (const kid of kids) {
if (kid instanceof _primitives.Ref) {
kidIds.push(kid.toString());
}
}
if (kidIds.length !== 0) {
this.data.kidIds = kidIds;
}
}
this.data.actions = (0, _core_utils.collectActions)(xref, dict, _util.AnnotationActionEventType);
this.data.fieldName = this._constructFieldName(dict);
this.data.pageIndex = params.pageIndex;
}
this._isOffscreenCanvasSupported = params.isOffscreenCanvasSupported;
this._fallbackFontDict = null;
this._needAppearances = false;
}
_hasFlag(flags, flag) {
return !!(flags & flag);
}
_isViewable(flags) {
return !this._hasFlag(flags, _util.AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, _util.AnnotationFlag.NOVIEW);
}
_isPrintable(flags) {
return this._hasFlag(flags, _util.AnnotationFlag.PRINT) && !this._hasFlag(flags, _util.AnnotationFlag.INVISIBLE);
}
mustBeViewed(annotationStorage) {
const storageEntry = annotationStorage && annotationStorage.get(this.data.id);
if (storageEntry && storageEntry.hidden !== undefined) {
return !storageEntry.hidden;
}
return this.viewable && !this._hasFlag(this.flags, _util.AnnotationFlag.HIDDEN);
}
mustBePrinted(annotationStorage) {
const storageEntry = annotationStorage && annotationStorage.get(this.data.id);
if (storageEntry && storageEntry.print !== undefined) {
return storageEntry.print;
}
return this.printable;
}
get viewable() {
if (this.data.quadPoints === null) {
return false;
}
if (this.flags === 0) {
return true;
}
return this._isViewable(this.flags);
}
get printable() {
if (this.data.quadPoints === null) {
return false;
}
if (this.flags === 0) {
return false;
}
return this._isPrintable(this.flags);
}
_parseStringHelper(data) {
const str = typeof data === "string" ? (0, _util.stringToPDFString)(data) : "";
const dir = str && (0, _bidi.bidi)(str).dir === "rtl" ? "rtl" : "ltr";
return {
str,
dir
};
}
setDefaultAppearance(params) {
const defaultAppearance = (0, _core_utils.getInheritableProperty)({
dict: params.dict,
key: "DA"
}) || params.acroForm.get("DA");
this._defaultAppearance = typeof defaultAppearance === "string" ? defaultAppearance : "";
this.data.defaultAppearanceData = (0, _default_appearance.parseDefaultAppearance)(this._defaultAppearance);
}
setTitle(title) {
this._title = this._parseStringHelper(title);
}
setContents(contents) {
this._contents = this._parseStringHelper(contents);
}
setModificationDate(modificationDate) {
this.modificationDate = typeof modificationDate === "string" ? modificationDate : null;
}
setFlags(flags) {
this.flags = Number.isInteger(flags) && flags > 0 ? flags : 0;
}
hasFlag(flag) {
return this._hasFlag(this.flags, flag);
}
setRectangle(rectangle) {
if (Array.isArray(rectangle) && rectangle.length === 4) {
this.rectangle = _util.Util.normalizeRect(rectangle);
} else {
this.rectangle = [0, 0, 0, 0];
}
}
setColor(color) {
this.color = getRgbColor(color);
}
setLineEndings(lineEndings) {
this.lineEndings = ["None", "None"];
if (Array.isArray(lineEndings) && lineEndings.length === 2) {
for (let i = 0; i < 2; i++) {
const obj = lineEndings[i];
if (obj instanceof _primitives.Name) {
switch (obj.name) {
case "None":
continue;
case "Square":
case "Circle":
case "Diamond":
case "OpenArrow":
case "ClosedArrow":
case "Butt":
case "ROpenArrow":
case "RClosedArrow":
case "Slash":
this.lineEndings[i] = obj.name;
continue;
}
}
(0, _util.warn)(`Ignoring invalid lineEnding: ${obj}`);
}
}
}
setRotation(mk) {
this.rotation = 0;
if (mk instanceof _primitives.Dict) {
let angle = mk.get("R") || 0;
if (Number.isInteger(angle) && angle !== 0) {
angle %= 360;
if (angle < 0) {
angle += 360;
}
if (angle % 90 === 0) {
this.rotation = angle;
}
}
}
}
setBorderAndBackgroundColors(mk) {
if (mk instanceof _primitives.Dict) {
this.borderColor = getRgbColor(mk.getArray("BC"), null);
this.backgroundColor = getRgbColor(mk.getArray("BG"), null);
} else {
this.borderColor = this.backgroundColor = null;
}
}
setBorderStyle(borderStyle) {
this.borderStyle = new AnnotationBorderStyle();
if (!(borderStyle instanceof _primitives.Dict)) {
return;
}
if (borderStyle.has("BS")) {
const dict = borderStyle.get("BS");
const dictType = dict.get("Type");
if (!dictType || (0, _primitives.isName)(dictType, "Border")) {
this.borderStyle.setWidth(dict.get("W"), this.rectangle);
this.borderStyle.setStyle(dict.get("S"));
this.borderStyle.setDashArray(dict.getArray("D"));
}
} else if (borderStyle.has("Border")) {
const array = borderStyle.getArray("Border");
if (Array.isArray(array) && array.length >= 3) {
this.borderStyle.setHorizontalCornerRadius(array[0]);
this.borderStyle.setVerticalCornerRadius(array[1]);
this.borderStyle.setWidth(array[2], this.rectangle);
if (array.length === 4) {
this.borderStyle.setDashArray(array[3], true);
}
}
} else {
this.borderStyle.setWidth(0);
}
}
setAppearance(dict) {
this.appearance = null;
const appearanceStates = dict.get("AP");
if (!(appearanceStates instanceof _primitives.Dict)) {
return;
}
const normalAppearanceState = appearanceStates.get("N");
if (normalAppearanceState instanceof _base_stream.BaseStream) {
this.appearance = normalAppearanceState;
return;
}
if (!(normalAppearanceState instanceof _primitives.Dict)) {
return;
}
const as = dict.get("AS");
if (!(as instanceof _primitives.Name) || !normalAppearanceState.has(as.name)) {
return;
}
this.appearance = normalAppearanceState.get(as.name);
}
setOptionalContent(dict) {
this.oc = null;
const oc = dict.get("OC");
if (oc instanceof _primitives.Name) {
(0, _util.warn)("setOptionalContent: Support for /Name-entry is not implemented.");
} else if (oc instanceof _primitives.Dict) {
this.oc = oc;
}
}
loadResources(keys, appearance) {
return appearance.dict.getAsync("Resources").then(resources => {
if (!resources) {
return undefined;
}
const objectLoader = new _object_loader.ObjectLoader(resources, keys, resources.xref);
return objectLoader.load().then(function () {
return resources;
});
});
}
async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
const data = this.data;
let appearance = this.appearance;
const isUsingOwnCanvas = !!(this.data.hasOwnCanvas && intent & _util.RenderingIntentFlag.DISPLAY);
if (!appearance) {
if (!isUsingOwnCanvas) {
return {
opList: new _operator_list.OperatorList(),
separateForm: false,
separateCanvas: false
};
}
appearance = new _stream.StringStream("");
appearance.dict = new _primitives.Dict();
}
const appearanceDict = appearance.dict;
const resources = await this.loadResources(["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], appearance);
const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1];
const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0];
const transform = getTransformMatrix(data.rect, bbox, matrix);
const opList = new _operator_list.OperatorList();
let optionalContent;
if (this.oc) {
optionalContent = await evaluator.parseMarkedContentProps(this.oc, null);
}
if (optionalContent !== undefined) {
opList.addOp(_util.OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
opList.addOp(_util.OPS.beginAnnotation, [data.id, data.rect, transform, matrix, isUsingOwnCanvas]);
await evaluator.getOperatorList({
stream: appearance,
task,
resources,
operatorList: opList,
fallbackFontDict: this._fallbackFontDict
});
opList.addOp(_util.OPS.endAnnotation, []);
if (optionalContent !== undefined) {
opList.addOp(_util.OPS.endMarkedContent, []);
}
this.reset();
return {
opList,
separateForm: false,
separateCanvas: isUsingOwnCanvas
};
}
async save(evaluator, task, annotationStorage) {
return null;
}
get hasTextContent() {
return false;
}
async extractTextContent(evaluator, task, viewBox) {
if (!this.appearance) {
return;
}
const resources = await this.loadResources(["ExtGState", "Font", "Properties", "XObject"], this.appearance);
const text = [];
const buffer = [];
const sink = {
desiredSize: Math.Infinity,
ready: true,
enqueue(chunk, size) {
for (const item of chunk.items) {
buffer.push(item.str);
if (item.hasEOL) {
text.push(buffer.join(""));
buffer.length = 0;
}
}
}
};
await evaluator.getTextContent({
stream: this.appearance,
task,
resources,
includeMarkedContent: true,
combineTextItems: true,
sink,
viewBox
});
this.reset();
if (buffer.length) {
text.push(buffer.join(""));
}
if (text.length > 0) {
this.data.textContent = text;
}
}
getFieldObject() {
if (this.data.kidIds) {
return {
id: this.data.id,
actions: this.data.actions,
name: this.data.fieldName,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
type: "",
kidIds: this.data.kidIds,
page: this.data.pageIndex,
rotation: this.rotation
};
}
return null;
}
reset() {
for (const stream of this._streams) {
stream.reset();
}
}
_constructFieldName(dict) {
if (!dict.has("T") && !dict.has("Parent")) {
(0, _util.warn)("Unknown field name, falling back to empty field name.");
return "";
}
if (!dict.has("Parent")) {
return (0, _util.stringToPDFString)(dict.get("T"));
}
const fieldName = [];
if (dict.has("T")) {
fieldName.unshift((0, _util.stringToPDFString)(dict.get("T")));
}
let loopDict = dict;
const visited = new _primitives.RefSet();
if (dict.objId) {
visited.put(dict.objId);
}
while (loopDict.has("Parent")) {
loopDict = loopDict.get("Parent");
if (!(loopDict instanceof _primitives.Dict) || loopDict.objId && visited.has(loopDict.objId)) {
break;
}
if (loopDict.objId) {
visited.put(loopDict.objId);
}
if (loopDict.has("T")) {
fieldName.unshift((0, _util.stringToPDFString)(loopDict.get("T")));
}
}
return fieldName.join(".");
}
}
exports.Annotation = Annotation;
class AnnotationBorderStyle {
constructor() {
this.width = 1;
this.style = _util.AnnotationBorderStyleType.SOLID;
this.dashArray = [3];
this.horizontalCornerRadius = 0;
this.verticalCornerRadius = 0;
}
setWidth(width, rect = [0, 0, 0, 0]) {
if (width instanceof _primitives.Name) {
this.width = 0;
return;
}
if (typeof width === "number") {
if (width > 0) {
const maxWidth = (rect[2] - rect[0]) / 2;
const maxHeight = (rect[3] - rect[1]) / 2;
if (maxWidth > 0 && maxHeight > 0 && (width > maxWidth || width > maxHeight)) {
(0, _util.warn)(`AnnotationBorderStyle.setWidth - ignoring width: ${width}`);
width = 1;
}
}
this.width = width;
}
}
setStyle(style) {
if (!(style instanceof _primitives.Name)) {
return;
}
switch (style.name) {
case "S":
this.style = _util.AnnotationBorderStyleType.SOLID;
break;
case "D":
this.style = _util.AnnotationBorderStyleType.DASHED;
break;
case "B":
this.style = _util.AnnotationBorderStyleType.BEVELED;
break;
case "I":
this.style = _util.AnnotationBorderStyleType.INSET;
break;
case "U":
this.style = _util.AnnotationBorderStyleType.UNDERLINE;
break;
default:
break;
}
}
setDashArray(dashArray, forceStyle = false) {
if (Array.isArray(dashArray) && dashArray.length > 0) {
let isValid = true;
let allZeros = true;
for (const element of dashArray) {
const validNumber = +element >= 0;
if (!validNumber) {
isValid = false;
break;
} else if (element > 0) {
allZeros = false;
}
}
if (isValid && !allZeros) {
this.dashArray = dashArray;
if (forceStyle) {
this.setStyle(_primitives.Name.get("D"));
}
} else {
this.width = 0;
}
} else if (dashArray) {
this.width = 0;
}
}
setHorizontalCornerRadius(radius) {
if (Number.isInteger(radius)) {
this.horizontalCornerRadius = radius;
}
}
setVerticalCornerRadius(radius) {
if (Number.isInteger(radius)) {
this.verticalCornerRadius = radius;
}
}
}
exports.AnnotationBorderStyle = AnnotationBorderStyle;
class MarkupAnnotation extends Annotation {
constructor(params) {
super(params);
const {
dict
} = params;
if (dict.has("IRT")) {
const rawIRT = dict.getRaw("IRT");
this.data.inReplyTo = rawIRT instanceof _primitives.Ref ? rawIRT.toString() : null;
const rt = dict.get("RT");
this.data.replyType = rt instanceof _primitives.Name ? rt.name : _util.AnnotationReplyType.REPLY;
}
if (this.data.replyType === _util.AnnotationReplyType.GROUP) {
const parent = dict.get("IRT");
this.setTitle(parent.get("T"));
this.data.titleObj = this._title;
this.setContents(parent.get("Contents"));
this.data.contentsObj = this._contents;
if (!parent.has("CreationDate")) {
this.data.creationDate = null;
} else {
this.setCreationDate(parent.get("CreationDate"));
this.data.creationDate = this.creationDate;
}
if (!parent.has("M")) {
this.data.modificationDate = null;
} else {
this.setModificationDate(parent.get("M"));
this.data.modificationDate = this.modificationDate;
}
this.data.hasPopup = parent.has("Popup");
if (!parent.has("C")) {
this.data.color = null;
} else {
this.setColor(parent.getArray("C"));
this.data.color = this.color;
}
} else {
this.data.titleObj = this._title;
this.setCreationDate(dict.get("CreationDate"));
this.data.creationDate = this.creationDate;
this.data.hasPopup = dict.has("Popup");
if (!dict.has("C")) {
this.data.color = null;
}
}
if (dict.has("RC")) {
this.data.richText = _factory.XFAFactory.getRichTextAsHtml(dict.get("RC"));
}
}
setCreationDate(creationDate) {
this.creationDate = typeof creationDate === "string" ? creationDate : null;
}
_setDefaultAppearance({
xref,
extra,
strokeColor,
fillColor,
blendMode,
strokeAlpha,
fillAlpha,
pointsCallback
}) {
let minX = Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxX = Number.MIN_VALUE;
let maxY = Number.MIN_VALUE;
const buffer = ["q"];
if (extra) {
buffer.push(extra);
}
if (strokeColor) {
buffer.push(`${strokeColor[0]} ${strokeColor[1]} ${strokeColor[2]} RG`);
}
if (fillColor) {
buffer.push(`${fillColor[0]} ${fillColor[1]} ${fillColor[2]} rg`);
}
let pointsArray = this.data.quadPoints;
if (!pointsArray) {
pointsArray = [[{
x: this.rectangle[0],
y: this.rectangle[3]
}, {
x: this.rectangle[2],
y: this.rectangle[3]
}, {
x: this.rectangle[0],
y: this.rectangle[1]
}, {
x: this.rectangle[2],
y: this.rectangle[1]
}]];
}
for (const points of pointsArray) {
const [mX, MX, mY, MY] = pointsCallback(buffer, points);
minX = Math.min(minX, mX);
maxX = Math.max(maxX, MX);
minY = Math.min(minY, mY);
maxY = Math.max(maxY, MY);
}
buffer.push("Q");
const formDict = new _primitives.Dict(xref);
const appearanceStreamDict = new _primitives.Dict(xref);
appearanceStreamDict.set("Subtype", _primitives.Name.get("Form"));
const appearanceStream = new _stream.StringStream(buffer.join(" "));
appearanceStream.dict = appearanceStreamDict;
formDict.set("Fm0", appearanceStream);
const gsDict = new _primitives.Dict(xref);
if (blendMode) {
gsDict.set("BM", _primitives.Name.get(blendMode));
}
if (typeof strokeAlpha === "number") {
gsDict.set("CA", strokeAlpha);
}
if (typeof fillAlpha === "number") {
gsDict.set("ca", fillAlpha);
}
const stateDict = new _primitives.Dict(xref);
stateDict.set("GS0", gsDict);
const resources = new _primitives.Dict(xref);
resources.set("ExtGState", stateDict);
resources.set("XObject", formDict);
const appearanceDict = new _primitives.Dict(xref);
appearanceDict.set("Resources", resources);
const bbox = this.data.rect = [minX, minY, maxX, maxY];
appearanceDict.set("BBox", bbox);
this.appearance = new _stream.StringStream("/GS0 gs /Fm0 Do");
this.appearance.dict = appearanceDict;
this._streams.push(this.appearance, appearanceStream);
}
static async createNewAnnotation(xref, annotation, dependencies, params) {
const annotationRef = xref.getNewTemporaryRef();
const ap = await this.createNewAppearanceStream(annotation, xref, params);
const buffer = [];
let annotationDict;
if (ap) {
const apRef = xref.getNewTemporaryRef();
annotationDict = this.createNewDict(annotation, xref, {
apRef
});
const transform = xref.encrypt ? xref.encrypt.createCipherTransform(apRef.num, apRef.gen) : null;
(0, _writer.writeObject)(apRef, ap, buffer, transform);
dependencies.push({
ref: apRef,
data: buffer.join("")
});
} else {
annotationDict = this.createNewDict(annotation, xref, {});
}
buffer.length = 0;
const transform = xref.encrypt ? xref.encrypt.createCipherTransform(annotationRef.num, annotationRef.gen) : null;
(0, _writer.writeObject)(annotationRef, annotationDict, buffer, transform);
return {
ref: annotationRef,
data: buffer.join("")
};
}
static async createNewPrintAnnotation(xref, annotation, params) {
const ap = await this.createNewAppearanceStream(annotation, xref, params);
const annotationDict = this.createNewDict(annotation, xref, {
ap
});
return new this.prototype.constructor({
dict: annotationDict,
xref,
isOffscreenCanvasSupported: params.isOffscreenCanvasSupported
});
}
}
exports.MarkupAnnotation = MarkupAnnotation;
class WidgetAnnotation extends Annotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
const data = this.data;
this.ref = params.ref;
this._needAppearances = params.needAppearances;
data.annotationType = _util.AnnotationType.WIDGET;
if (data.fieldName === undefined) {
data.fieldName = this._constructFieldName(dict);
}
if (data.actions === undefined) {
data.actions = (0, _core_utils.collectActions)(xref, dict, _util.AnnotationActionEventType);
}
let fieldValue = (0, _core_utils.getInheritableProperty)({
dict,
key: "V",
getArray: true
});
data.fieldValue = this._decodeFormValue(fieldValue);
const defaultFieldValue = (0, _core_utils.getInheritableProperty)({
dict,
key: "DV",
getArray: true
});
data.defaultFieldValue = this._decodeFormValue(defaultFieldValue);
if (fieldValue === undefined && params.xfaDatasets) {
const path = this._title.str;
if (path) {
this._hasValueFromXFA = true;
data.fieldValue = fieldValue = params.xfaDatasets.getValue(path);
}
}
if (fieldValue === undefined && data.defaultFieldValue !== null) {
data.fieldValue = data.defaultFieldValue;
}
data.alternativeText = (0, _util.stringToPDFString)(dict.get("TU") || "");
this.setDefaultAppearance(params);
data.hasAppearance = this._needAppearances && data.fieldValue !== undefined && data.fieldValue !== null || data.hasAppearance;
const fieldType = (0, _core_utils.getInheritableProperty)({
dict,
key: "FT"
});
data.fieldType = fieldType instanceof _primitives.Name ? fieldType.name : null;
const localResources = (0, _core_utils.getInheritableProperty)({
dict,
key: "DR"
});
const acroFormResources = params.acroForm.get("DR");
const appearanceResources = this.appearance && this.appearance.dict.get("Resources");
this._fieldResources = {
localResources,
acroFormResources,
appearanceResources,
mergedResources: _primitives.Dict.merge({
xref,
dictArray: [localResources, appearanceResources, acroFormResources],
mergeSubDicts: true
})
};
data.fieldFlags = (0, _core_utils.getInheritableProperty)({
dict,
key: "Ff"
});
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
data.fieldFlags = 0;
}
data.readOnly = this.hasFieldFlag(_util.AnnotationFieldFlag.READONLY);
data.required = this.hasFieldFlag(_util.AnnotationFieldFlag.REQUIRED);
data.hidden = this._hasFlag(data.annotationFlags, _util.AnnotationFlag.HIDDEN);
}
_decodeFormValue(formValue) {
if (Array.isArray(formValue)) {
return formValue.filter(item => typeof item === "string").map(item => (0, _util.stringToPDFString)(item));
} else if (formValue instanceof _primitives.Name) {
return (0, _util.stringToPDFString)(formValue.name);
} else if (typeof formValue === "string") {
return (0, _util.stringToPDFString)(formValue);
}
return null;
}
hasFieldFlag(flag) {
return !!(this.data.fieldFlags & flag);
}
getRotationMatrix(annotationStorage) {
const storageEntry = annotationStorage ? annotationStorage.get(this.data.id) : undefined;
let rotation = storageEntry && storageEntry.rotation;
if (rotation === undefined) {
rotation = this.rotation;
}
if (rotation === 0) {
return _util.IDENTITY_MATRIX;
}
const width = this.data.rect[2] - this.data.rect[0];
const height = this.data.rect[3] - this.data.rect[1];
return (0, _core_utils.getRotationMatrix)(rotation, width, height);
}
getBorderAndBackgroundAppearances(annotationStorage) {
const storageEntry = annotationStorage ? annotationStorage.get(this.data.id) : undefined;
let rotation = storageEntry && storageEntry.rotation;
if (rotation === undefined) {
rotation = this.rotation;
}
if (!this.backgroundColor && !this.borderColor) {
return "";
}
const width = this.data.rect[2] - this.data.rect[0];
const height = this.data.rect[3] - this.data.rect[1];
const rect = rotation === 0 || rotation === 180 ? `0 0 ${width} ${height} re` : `0 0 ${height} ${width} re`;
let str = "";
if (this.backgroundColor) {
str = `${(0, _default_appearance.getPdfColor)(this.backgroundColor, true)} ${rect} f `;
}
if (this.borderColor) {
const borderWidth = this.borderStyle.width || 1;
str += `${borderWidth} w ${(0, _default_appearance.getPdfColor)(this.borderColor, false)} ${rect} S `;
}
return str;
}
async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
if (renderForms && !(this instanceof SignatureWidgetAnnotation)) {
return {
opList: new _operator_list.OperatorList(),
separateForm: true,
separateCanvas: false
};
}
if (!this._hasText) {
return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
}
const content = await this._getAppearance(evaluator, task, intent, annotationStorage);
if (this.appearance && content === null) {
return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
}
const opList = new _operator_list.OperatorList();
if (!this._defaultAppearance || content === null) {
return {
opList,
separateForm: false,
separateCanvas: false
};
}
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, null);
}
if (optionalContent !== undefined) {
opList.addOp(_util.OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
opList.addOp(_util.OPS.beginAnnotation, [this.data.id, this.data.rect, transform, this.getRotationMatrix(annotationStorage), false]);
const stream = new _stream.StringStream(content);
await evaluator.getOperatorList({
stream,
task,
resources: this._fieldResources.mergedResources,
operatorList: opList
});
opList.addOp(_util.OPS.endAnnotation, []);
if (optionalContent !== undefined) {
opList.addOp(_util.OPS.endMarkedContent, []);
}
return {
opList,
separateForm: false,
separateCanvas: false
};
}
_getMKDict(rotation) {
const mk = new _primitives.Dict(null);
if (rotation) {
mk.set("R", rotation);
}
if (this.borderColor) {
mk.set("BC", getPdfColorArray(this.borderColor));
}
if (this.backgroundColor) {
mk.set("BG", getPdfColorArray(this.backgroundColor));
}
return mk.size > 0 ? mk : null;
}
async save(evaluator, task, annotationStorage) {
const storageEntry = annotationStorage ? annotationStorage.get(this.data.id) : undefined;
let value = storageEntry && storageEntry.value;
let rotation = storageEntry && storageEntry.rotation;
if (value === this.data.fieldValue || value === undefined) {
if (!this._hasValueFromXFA && rotation === undefined) {
return null;
}
value = value || this.data.fieldValue;
}
if (rotation === undefined && !this._hasValueFromXFA && Array.isArray(value) && Array.isArray(this.data.fieldValue) && value.length === this.data.fieldValue.length && value.every((x, i) => x === this.data.fieldValue[i])) {
return null;
}
if (rotation === undefined) {
rotation = this.rotation;
}
let appearance = null;
if (!this._needAppearances) {
appearance = await this._getAppearance(evaluator, task, _util.RenderingIntentFlag.SAVE, annotationStorage);
if (appearance === null) {
return null;
}
} else {}
let needAppearances = false;
if (appearance && appearance.needAppearances) {
needAppearances = true;
appearance = null;
}
const {
xref
} = evaluator;
const originalDict = xref.fetchIfRef(this.ref);
if (!(originalDict instanceof _primitives.Dict)) {
return null;
}
const dict = new _primitives.Dict(xref);
for (const key of originalDict.getKeys()) {
if (key !== "AP") {
dict.set(key, originalDict.getRaw(key));
}
}
const xfa = {
path: (0, _util.stringToPDFString)(dict.get("T") || ""),
value
};
const encoder = val => {
return (0, _core_utils.isAscii)(val) ? val : (0, _core_utils.stringToUTF16String)(val, true);
};
dict.set("V", Array.isArray(value) ? value.map(encoder) : encoder(value));
const maybeMK = this._getMKDict(rotation);
if (maybeMK) {
dict.set("MK", maybeMK);
}
const encrypt = xref.encrypt;
const originalTransform = encrypt ? encrypt.createCipherTransform(this.ref.num, this.ref.gen) : null;
const buffer = [];
const changes = [{
ref: this.ref,
data: "",
xfa,
needAppearances
}];
if (appearance !== null) {
const newRef = xref.getNewTemporaryRef();
const AP = new _primitives.Dict(xref);
dict.set("AP", AP);
AP.set("N", newRef);
let newTransform = null;
if (encrypt) {
newTransform = encrypt.createCipherTransform(newRef.num, newRef.gen);
appearance = newTransform.encryptString(appearance);
}
const resources = this._getSaveFieldResources(xref);
const appearanceStream = new _stream.StringStream(appearance);
const appearanceDict = appearanceStream.dict = new _primitives.Dict(xref);
appearanceDict.set("Length", appearance.length);
appearanceDict.set("Subtype", _primitives.Name.get("Form"));
appearanceDict.set("Resources", resources);
appearanceDict.set("BBox", [0, 0, this.data.rect[2] - this.data.rect[0], this.data.rect[3] - this.data.rect[1]]);
const rotationMatrix = this.getRotationMatrix(annotationStorage);
if (rotationMatrix !== _util.IDENTITY_MATRIX) {
appearanceDict.set("Matrix", rotationMatrix);
}
(0, _writer.writeObject)(newRef, appearanceStream, buffer, newTransform);
changes.push({
ref: newRef,
data: buffer.join(""),
xfa: null,
needAppearances: false
});
buffer.length = 0;
}
dict.set("M", `D:${(0, _util.getModificationDate)()}`);
(0, _writer.writeObject)(this.ref, dict, buffer, originalTransform);
changes[0].data = buffer.join("");
return changes;
}
async _getAppearance(evaluator, task, intent, annotationStorage) {
const isPassword = this.hasFieldFlag(_util.AnnotationFieldFlag.PASSWORD);
if (isPassword) {
return null;
}
const storageEntry = annotationStorage ? annotationStorage.get(this.data.id) : undefined;
let value, rotation;
if (storageEntry) {
value = storageEntry.formattedValue || storageEntry.value;
rotation = storageEntry.rotation;
}
if (rotation === undefined && value === undefined && !this._needAppearances) {
if (!this._hasValueFromXFA || this.appearance) {
return null;
}
}
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
if (value === undefined) {
value = this.data.fieldValue;
if (!value) {
return `/Tx BMC q ${colors}Q EMC`;
}
}
if (Array.isArray(value) && value.length === 1) {
value = value[0];
}
(0, _util.assert)(typeof value === "string", "Expected `value` to be a string.");
value = value.trim();
if (value === "") {
return `/Tx BMC q ${colors}Q EMC`;
}
if (rotation === undefined) {
rotation = this.rotation;
}
let lineCount = -1;
let lines;
if (this.data.multiLine) {
lines = value.split(/\r\n?|\n/).map(line => line.normalize("NFC"));
lineCount = lines.length;
} else {
lines = [value.replace(/\r\n?|\n/, "").normalize("NFC")];
}
const defaultPadding = 1;
const defaultHPadding = 2;
let totalHeight = this.data.rect[3] - this.data.rect[1];
let totalWidth = this.data.rect[2] - this.data.rect[0];
if (rotation === 90 || rotation === 270) {
[totalWidth, totalHeight] = [totalHeight, totalWidth];
}
if (!this._defaultAppearance) {
this.data.defaultAppearanceData = (0, _default_appearance.parseDefaultAppearance)(this._defaultAppearance = "/Helvetica 0 Tf 0 g");
}
let font = await WidgetAnnotation._getFontData(evaluator, task, this.data.defaultAppearanceData, this._fieldResources.mergedResources);
let defaultAppearance, fontSize, lineHeight;
const encodedLines = [];
let encodingError = false;
for (const line of lines) {
const encodedString = font.encodeString(line);
if (encodedString.length > 1) {
encodingError = true;
}
encodedLines.push(encodedString.join(""));
}
if (encodingError && intent & _util.RenderingIntentFlag.SAVE) {
return {
needAppearances: true
};
}
if (encodingError && this._isOffscreenCanvasSupported) {
const fontFamily = this.data.comb ? "monospace" : "sans-serif";
const fakeUnicodeFont = new _default_appearance.FakeUnicodeFont(evaluator.xref, fontFamily);
const resources = fakeUnicodeFont.createFontResources(lines.join(""));
const newFont = resources.getRaw("Font");
if (this._fieldResources.mergedResources.has("Font")) {
const oldFont = this._fieldResources.mergedResources.get("Font");
for (const key of newFont.getKeys()) {
oldFont.set(key, newFont.getRaw(key));
}
} else {
this._fieldResources.mergedResources.set("Font", newFont);
}
const fontName = fakeUnicodeFont.fontName.name;
font = await WidgetAnnotation._getFontData(evaluator, task, {
fontName,
fontSize: 0
}, resources);
for (let i = 0, ii = encodedLines.length; i < ii; i++) {
encodedLines[i] = (0, _core_utils.stringToUTF16String)(lines[i]);
}
const savedDefaultAppearance = Object.assign(Object.create(null), this.data.defaultAppearanceData);
this.data.defaultAppearanceData.fontSize = 0;
this.data.defaultAppearanceData.fontName = fontName;
[defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount);
this.data.defaultAppearanceData = savedDefaultAppearance;
} else {
if (!this._isOffscreenCanvasSupported) {
(0, _util.warn)("_getAppearance: OffscreenCanvas is not supported, annotation may not render correctly.");
}
[defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount);
}
let descent = font.descent;
if (isNaN(descent)) {
descent = _util.BASELINE_FACTOR * lineHeight;
} else {
descent = Math.max(_util.BASELINE_FACTOR * lineHeight, Math.abs(descent) * fontSize);
}
const defaultVPadding = Math.min(Math.floor((totalHeight - fontSize) / 2), defaultPadding);
const alignment = this.data.textAlignment;
if (this.data.multiLine) {
return this._getMultilineAppearance(defaultAppearance, encodedLines, font, fontSize, totalWidth, totalHeight, alignment, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage);
}
if (this.data.comb) {
return this._getCombAppearance(defaultAppearance, font, encodedLines[0], fontSize, totalWidth, totalHeight, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage);
}
const bottomPadding = defaultVPadding + descent;
if (alignment === 0 || alignment > 2) {
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${(0, _core_utils.numberToString)(defaultHPadding)} ${(0, _core_utils.numberToString)(bottomPadding)} Tm (${(0, _core_utils.escapeString)(encodedLines[0])}) Tj` + " ET Q EMC";
}
const prevInfo = {
shift: 0
};
const renderedText = this._renderText(encodedLines[0], font, fontSize, totalWidth, alignment, prevInfo, defaultHPadding, bottomPadding);
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 0 Tm ${renderedText}` + " ET Q EMC";
}
static async _getFontData(evaluator, task, appearanceData, resources) {
const operatorList = new _operator_list.OperatorList();
const initialState = {
font: null,
clone() {
return this;
}
};
const {
fontName,
fontSize
} = appearanceData;
await evaluator.handleSetFont(resources, [fontName && _primitives.Name.get(fontName), fontSize], null, operatorList, task, initialState, null);
return initialState.font;
}
_getTextWidth(text, font) {
return font.charsToGlyphs(text).reduce((width, glyph) => width + glyph.width, 0) / 1000;
}
_computeFontSize(height, width, text, font, lineCount) {
let {
fontSize
} = this.data.defaultAppearanceData;
let lineHeight = (fontSize || 12) * _util.LINE_FACTOR,
numberOfLines = Math.round(height / lineHeight);
if (!fontSize) {
const roundWithTwoDigits = x => Math.floor(x * 100) / 100;
if (lineCount === -1) {
const textWidth = this._getTextWidth(text, font);
fontSize = roundWithTwoDigits(Math.min(height / _util.LINE_FACTOR, textWidth > width ? width / textWidth : Infinity));
numberOfLines = 1;
} else {
const lines = text.split(/\r\n?|\n/);
const cachedLines = [];
for (const line of lines) {
const encoded = font.encodeString(line).join("");
const glyphs = font.charsToGlyphs(encoded);
const positions = font.getCharPositions(encoded);
cachedLines.push({
line: encoded,
glyphs,
positions
});
}
const isTooBig = fsize => {
let totalHeight = 0;
for (const cache of cachedLines) {
const chunks = this._splitLine(null, font, fsize, width, cache);
totalHeight += chunks.length * fsize;
if (totalHeight > height) {
return true;
}
}
return false;
};
numberOfLines = Math.max(numberOfLines, lineCount);
while (true) {
lineHeight = height / numberOfLines;
fontSize = roundWithTwoDigits(lineHeight / _util.LINE_FACTOR);
if (isTooBig(fontSize)) {
numberOfLines++;
continue;
}
break;
}
}
const {