: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
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`;
if (this.backgroundColor) {
str = `${getPdfColor(this.backgroundColor, true)} ${rect} f `;
const borderWidth = this.borderStyle.width || 1;
str += `${borderWidth} w ${getPdfColor(this.borderColor, false)} ${rect} S `;
async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
if (renderForms && !(this instanceof SignatureWidgetAnnotation) && !this.data.noHTML && !this.data.hasOwnCanvas) {
opList: new OperatorList(),
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 OperatorList();
if (!this._defaultAppearance || content === null) {
const isUsingOwnCanvas = !!(this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY);
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);
optionalContent = await evaluator.parseMarkedContentProps(this.oc, null);
if (optionalContent !== undefined) {
opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
opList.addOp(OPS.beginAnnotation, [this.data.id, this.data.rect, transform, this.getRotationMatrix(annotationStorage), isUsingOwnCanvas]);
const stream = new StringStream(content);
await evaluator.getOperatorList({
resources: this._fieldResources.mergedResources,
opList.addOp(OPS.endAnnotation, []);
if (optionalContent !== undefined) {
opList.addOp(OPS.endMarkedContent, []);
separateCanvas: isUsingOwnCanvas
const mk = new Dict(null);
mk.set("BC", getPdfColorArray(this.borderColor));
if (this.backgroundColor) {
mk.set("BG", getPdfColorArray(this.backgroundColor));
return mk.size > 0 ? mk : null;
amendSavedDict(annotationStorage, dict) {}
async save(evaluator, task, annotationStorage) {
const storageEntry = annotationStorage?.get(this.data.id);
let value = storageEntry?.value,
rotation = storageEntry?.rotation;
if (value === this.data.fieldValue || value === undefined) {
if (!this._hasValueFromXFA && rotation === undefined) {
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])) {
if (rotation === undefined) {
rotation = this.rotation;
if (!this._needAppearances) {
appearance = await this._getAppearance(evaluator, task, RenderingIntentFlag.SAVE, annotationStorage);
if (appearance === null) {
let needAppearances = false;
if (appearance?.needAppearances) {
const originalDict = xref.fetchIfRef(this.ref);
if (!(originalDict instanceof Dict)) {
const dict = new Dict(xref);
for (const key of originalDict.getKeys()) {
dict.set(key, originalDict.getRaw(key));
path: this.data.fieldName,
const encoder = val => isAscii(val) ? val : stringToUTF16String(val, true);
dict.set("V", Array.isArray(value) ? value.map(encoder) : encoder(value));
this.amendSavedDict(annotationStorage, dict);
const maybeMK = this._getMKDict(rotation);
if (appearance !== null) {
const newRef = xref.getNewTemporaryRef();
const AP = new Dict(xref);
const resources = this._getSaveFieldResources(xref);
const appearanceStream = new StringStream(appearance);
const appearanceDict = appearanceStream.dict = new Dict(xref);
appearanceDict.set("Subtype", 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 !== IDENTITY_MATRIX) {
appearanceDict.set("Matrix", rotationMatrix);
await writeObject(newRef, appearanceStream, buffer, xref);
dict.set("M", `D:${getModificationDate()}`);
await writeObject(this.ref, dict, buffer, xref);
changes[0].data = buffer.join("");
async _getAppearance(evaluator, task, intent, annotationStorage) {
const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD);
const storageEntry = annotationStorage?.get(this.data.id);
value = storageEntry.formattedValue || storageEntry.value;
rotation = storageEntry.rotation;
if (rotation === undefined && value === undefined && !this._needAppearances) {
if (!this._hasValueFromXFA || this.appearance) {
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
if (value === undefined) {
value = this.data.fieldValue;
return `/Tx BMC q ${colors}Q EMC`;
if (Array.isArray(value) && value.length === 1) {
assert(typeof value === "string", "Expected `value` to be a string.");
const option = this.data.options.find(({
}) => value === exportValue);
value = option?.displayValue || value;
return `/Tx BMC q ${colors}Q EMC`;
if (rotation === undefined) {
rotation = this.rotation;
if (this.data.multiLine) {
lines = value.split(/\r\n?|\n/).map(line => line.normalize("NFC"));
lineCount = lines.length;
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 = 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;
let encodingError = false;
for (const line of lines) {
const encodedString = font.encodeString(line);
if (encodedString.length > 1) {
encodedLines.push(encodedString.join(""));
if (encodingError && intent & RenderingIntentFlag.SAVE) {
if (encodingError && this._isOffscreenCanvasSupported) {
const fontFamily = this.data.comb ? "monospace" : "sans-serif";
const fakeUnicodeFont = new 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));
this._fieldResources.mergedResources.set("Font", newFont);
const fontName = fakeUnicodeFont.fontName.name;
font = await WidgetAnnotation._getFontData(evaluator, task, {
for (let i = 0, ii = encodedLines.length; i < ii; i++) {
encodedLines[i] = 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;
if (!this._isOffscreenCanvasSupported) {
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;
descent = BASELINE_FACTOR * lineHeight;
descent = Math.max(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);
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 ${numberToString(defaultHPadding)} ${numberToString(bottomPadding)} Tm (${escapeString(encodedLines[0])}) Tj` + " ET Q EMC";
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 OperatorList();
await evaluator.handleSetFont(resources, [fontName && 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) {
} = this.data.defaultAppearanceData;
let lineHeight = (fontSize || 12) * LINE_FACTOR,
numberOfLines = Math.round(height / lineHeight);
const roundWithTwoDigits = x => Math.floor(x * 100) / 100;
const textWidth = this._getTextWidth(text, font);
fontSize = roundWithTwoDigits(Math.min(height / LINE_FACTOR, textWidth > width ? width / textWidth : Infinity));
const lines = text.split(/\r\n?|\n/);
for (const line of lines) {
const encoded = font.encodeString(line).join("");
const glyphs = font.charsToGlyphs(encoded);
const positions = font.getCharPositions(encoded);
const isTooBig = fsize => {
for (const cache of cachedLines) {
const chunks = this._splitLine(null, font, fsize, width, cache);
totalHeight += chunks.length * fsize;
if (totalHeight > height) {
numberOfLines = Math.max(numberOfLines, lineCount);
lineHeight = height / numberOfLines;
fontSize = roundWithTwoDigits(lineHeight / LINE_FACTOR);
if (isTooBig(fontSize)) {
} = this.data.defaultAppearanceData;
this._defaultAppearance = createDefaultAppearance({
return [this._defaultAppearance, fontSize, height / numberOfLines];
_renderText(text, font, fontSize, totalWidth, alignment, prevInfo, hPadding, vPadding) {
const width = this._getTextWidth(text, font) * fontSize;
shift = (totalWidth - width) / 2;
} else if (alignment === 2) {
const width = this._getTextWidth(text, font) * fontSize;
shift = totalWidth - width - hPadding;
const shiftStr = numberToString(shift - prevInfo.shift);
vPadding = numberToString(vPadding);
return `${shiftStr} ${vPadding} Td (${escapeString(text)}) Tj`;
_getSaveFieldResources(xref) {
} = this._fieldResources;
const fontName = this.data.defaultAppearanceData?.fontName;
return localResources || Dict.empty;
for (const resources of [localResources, appearanceResources]) {
if (resources instanceof Dict) {
const localFont = resources.get("Font");
if (localFont instanceof Dict && localFont.has(fontName)) {
if (acroFormResources instanceof Dict) {
const acroFormFont = acroFormResources.get("Font");
if (acroFormFont instanceof Dict && acroFormFont.has(fontName)) {
const subFontDict = new Dict(xref);
subFontDict.set(fontName, acroFormFont.getRaw(fontName));
const subResourcesDict = new Dict(xref);
subResourcesDict.set("Font", subFontDict);
dictArray: [subResourcesDict, localResources],
return localResources || Dict.empty;
class TextWidgetAnnotation extends WidgetAnnotation {
this.data.hasOwnCanvas = this.data.readOnly && !this.data.noHTML;
const dict = params.dict;
if (typeof this.data.fieldValue !== "string") {
this.data.fieldValue = "";
let alignment = getInheritableProperty({
if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) {
this.data.textAlignment = alignment;
let maximumLength = getInheritableProperty({
if (!Number.isInteger(maximumLength) || maximumLength < 0) {
this.data.maxLen = maximumLength;
this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) && !this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) && !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) && !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && this.data.maxLen !== 0;
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
return !!this.appearance && !this._needAppearances;
_getCombAppearance(defaultAppearance, font, text, fontSize, width, height, hPadding, vPadding, descent, lineHeight, annotationStorage) {
const combWidth = width / this.data.maxLen;
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
const positions = font.getCharPositions(text);
for (const [start, end] of positions) {
buf.push(`(${escapeString(text.substring(start, end))}) Tj`);
const renderedComb = buf.join(` ${numberToString(combWidth)} 0 Td `);
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${numberToString(hPadding)} ${numberToString(vPadding + descent)} Tm ${renderedComb}` + " ET Q EMC";
_getMultilineAppearance(defaultAppearance, lines, font, fontSize, width, height, alignment, hPadding, vPadding, descent, lineHeight, annotationStorage) {
const totalWidth = width - 2 * hPadding;
for (let i = 0, ii = lines.length; i < ii; i++) {
const chunks = this._splitLine(line, font, fontSize, totalWidth);
for (let j = 0, jj = chunks.length; j < jj; j++) {
const vShift = i === 0 && j === 0 ? -vPadding - (lineHeight - descent) : -lineHeight;
buf.push(this._renderText(chunk, font, fontSize, width, alignment, prevInfo, hPadding, vShift));
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
const renderedText = buf.join("\n");
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 ${numberToString(height)} Tm ${renderedText}` + " ET Q EMC";
_splitLine(line, font, fontSize, width, cache = {}) {
line = cache.line || line;
const glyphs = cache.glyphs || font.charsToGlyphs(line);
if (glyphs.length <= 1) {
const positions = cache.positions || font.getCharPositions(line);
const scale = fontSize / 1000;
let lastSpacePosInStringStart = -1,