: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
lastSpacePosInStringEnd = -1,
for (let i = 0, ii = glyphs.length; i < ii; i++) {
const [start, end] = positions[i];
const glyphWidth = glyph.width * scale;
if (glyph.unicode === " ") {
if (currentWidth + glyphWidth > width) {
chunks.push(line.substring(startChunk, start));
currentWidth = glyphWidth;
lastSpacePosInStringStart = -1;
currentWidth += glyphWidth;
lastSpacePosInStringStart = start;
lastSpacePosInStringEnd = end;
} else if (currentWidth + glyphWidth > width) {
if (lastSpacePosInStringStart !== -1) {
chunks.push(line.substring(startChunk, lastSpacePosInStringEnd));
startChunk = lastSpacePosInStringEnd;
lastSpacePosInStringStart = -1;
chunks.push(line.substring(startChunk, start));
currentWidth = glyphWidth;
currentWidth += glyphWidth;
if (startChunk < line.length) {
chunks.push(line.substring(startChunk, line.length));
value: this.data.fieldValue,
defaultValue: this.data.defaultFieldValue || "",
multiline: this.data.multiLine,
password: this.hasFieldFlag(AnnotationFieldFlag.PASSWORD),
charLimit: this.data.maxLen,
editable: !this.data.readOnly,
hidden: this.data.hidden,
name: this.data.fieldName,
actions: this.data.actions,
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
class ButtonWidgetAnnotation extends WidgetAnnotation {
this.checkedAppearance = null;
this.uncheckedAppearance = null;
this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
this.data.pushButton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
this.data.isTooltipOnly = false;
if (this.data.checkBox) {
this._processCheckBox(params);
} else if (this.data.radioButton) {
this._processRadioButton(params);
} else if (this.data.pushButton) {
this.data.hasOwnCanvas = true;
this.data.noHTML = false;
this._processPushButton(params);
warn("Invalid field flags for button widget annotation");
async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
if (this.data.pushButton) {
return super.getOperatorList(evaluator, task, intent, false, annotationStorage);
const storageEntry = annotationStorage.get(this.data.id);
value = storageEntry ? storageEntry.value : null;
rotation = storageEntry ? storageEntry.rotation : null;
if (value === null && this.appearance) {
return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
if (value === null || value === undefined) {
value = this.data.checkBox ? this.data.fieldValue === this.data.exportValue : this.data.fieldValue === this.data.buttonValue;
const appearance = value ? this.checkedAppearance : this.uncheckedAppearance;
const savedAppearance = this.appearance;
const savedMatrix = lookupMatrix(appearance.dict.getArray("Matrix"), IDENTITY_MATRIX);
appearance.dict.set("Matrix", this.getRotationMatrix(annotationStorage));
this.appearance = appearance;
const operatorList = super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
this.appearance = savedAppearance;
appearance.dict.set("Matrix", savedMatrix);
opList: new OperatorList(),
async save(evaluator, task, annotationStorage) {
if (this.data.checkBox) {
return this._saveCheckbox(evaluator, task, annotationStorage);
if (this.data.radioButton) {
return this._saveRadioButton(evaluator, task, annotationStorage);
async _saveCheckbox(evaluator, task, annotationStorage) {
if (!annotationStorage) {
const storageEntry = annotationStorage.get(this.data.id);
let rotation = storageEntry?.rotation,
value = storageEntry?.value;
if (rotation === undefined) {
if (value === undefined) {
const defaultValue = this.data.fieldValue === this.data.exportValue;
if (defaultValue === value) {
const dict = evaluator.xref.fetchIfRef(this.ref);
if (!(dict instanceof Dict)) {
if (rotation === undefined) {
rotation = this.rotation;
if (value === undefined) {
value = this.data.fieldValue === this.data.exportValue;
path: this.data.fieldName,
value: value ? this.data.exportValue : ""
const name = Name.get(value ? this.data.exportValue : "Off");
dict.set("M", `D:${getModificationDate()}`);
const maybeMK = this._getMKDict(rotation);
await writeObject(this.ref, dict, buffer, evaluator.xref);
async _saveRadioButton(evaluator, task, annotationStorage) {
if (!annotationStorage) {
const storageEntry = annotationStorage.get(this.data.id);
let rotation = storageEntry?.rotation,
value = storageEntry?.value;
if (rotation === undefined) {
if (value === undefined) {
const defaultValue = this.data.fieldValue === this.data.buttonValue;
if (defaultValue === value) {
const dict = evaluator.xref.fetchIfRef(this.ref);
if (!(dict instanceof Dict)) {
if (value === undefined) {
value = this.data.fieldValue === this.data.buttonValue;
if (rotation === undefined) {
rotation = this.rotation;
path: this.data.fieldName,
value: value ? this.data.buttonValue : ""
const name = Name.get(value ? this.data.buttonValue : "Off");
if (this.parent instanceof Ref) {
const parent = evaluator.xref.fetch(this.parent);
await writeObject(this.parent, parent, buffer, evaluator.xref);
parentData = buffer.join("");
} else if (this.parent instanceof Dict) {
this.parent.set("V", name);
dict.set("M", `D:${getModificationDate()}`);
const maybeMK = this._getMKDict(rotation);
await writeObject(this.ref, dict, buffer, evaluator.xref);
_getDefaultCheckedAppearance(params, type) {
const width = this.data.rect[2] - this.data.rect[0];
const height = this.data.rect[3] - this.data.rect[1];
const bbox = [0, 0, width, height];
const fontSize = Math.min(width, height) * FONT_RATIO;
} else if (type === "disc") {
unreachable(`_getDefaultCheckedAppearance - unsupported type: ${type}`);
const xShift = numberToString((width - metrics.width) / 2);
const yShift = numberToString((height - metrics.height) / 2);
const appearance = `q BT /PdfJsZaDb ${fontSize} Tf 0 g ${xShift} ${yShift} Td (${char}) Tj ET Q`;
const appearanceStreamDict = new Dict(params.xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", bbox);
appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]);
appearanceStreamDict.set("Length", appearance.length);
const resources = new Dict(params.xref);
const font = new Dict(params.xref);
font.set("PdfJsZaDb", this.fallbackFontDict);
resources.set("Font", font);
appearanceStreamDict.set("Resources", resources);
this.checkedAppearance = new StringStream(appearance);
this.checkedAppearance.dict = appearanceStreamDict;
this._streams.push(this.checkedAppearance);
_processCheckBox(params) {
const customAppearance = params.dict.get("AP");
if (!(customAppearance instanceof Dict)) {
const normalAppearance = customAppearance.get("N");
if (!(normalAppearance instanceof Dict)) {
const asValue = this._decodeFormValue(params.dict.get("AS"));
if (typeof asValue === "string") {
this.data.fieldValue = asValue;
const yes = this.data.fieldValue !== null && this.data.fieldValue !== "Off" ? this.data.fieldValue : "Yes";
const exportValues = normalAppearance.getKeys();
if (exportValues.length === 0) {
exportValues.push("Off", yes);
} else if (exportValues.length === 1) {
if (exportValues[0] === "Off") {
exportValues.unshift("Off");
} else if (exportValues.includes(yes)) {
exportValues.push("Off", yes);
const otherYes = exportValues.find(v => v !== "Off");
exportValues.push("Off", otherYes);
if (!exportValues.includes(this.data.fieldValue)) {
this.data.fieldValue = "Off";
this.data.exportValue = exportValues[1];
const checkedAppearance = normalAppearance.get(this.data.exportValue);
this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null;
const uncheckedAppearance = normalAppearance.get("Off");
this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null;
if (this.checkedAppearance) {
this._streams.push(this.checkedAppearance);
this._getDefaultCheckedAppearance(params, "check");
if (this.uncheckedAppearance) {
this._streams.push(this.uncheckedAppearance);
this._fallbackFontDict = this.fallbackFontDict;
if (this.data.defaultFieldValue === null) {
this.data.defaultFieldValue = "Off";
_processRadioButton(params) {
this.data.buttonValue = null;
const fieldParent = params.dict.get("Parent");
if (fieldParent instanceof Dict) {
this.parent = params.dict.getRaw("Parent");
const fieldParentValue = fieldParent.get("V");
if (fieldParentValue instanceof Name) {
this.data.fieldValue = this._decodeFormValue(fieldParentValue);
const appearanceStates = params.dict.get("AP");
if (!(appearanceStates instanceof Dict)) {
const normalAppearance = appearanceStates.get("N");
if (!(normalAppearance instanceof Dict)) {
for (const key of normalAppearance.getKeys()) {
this.data.buttonValue = this._decodeFormValue(key);
const checkedAppearance = normalAppearance.get(this.data.buttonValue);
this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null;
const uncheckedAppearance = normalAppearance.get("Off");
this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null;
if (this.checkedAppearance) {
this._streams.push(this.checkedAppearance);
this._getDefaultCheckedAppearance(params, "disc");
if (this.uncheckedAppearance) {
this._streams.push(this.uncheckedAppearance);
this._fallbackFontDict = this.fallbackFontDict;
if (this.data.defaultFieldValue === null) {
this.data.defaultFieldValue = "Off";
_processPushButton(params) {
if (!dict.has("A") && !dict.has("AA") && !this.data.alternativeText) {
warn("Push buttons without action dictionaries are not supported");
this.data.isTooltipOnly = !dict.has("A") && !dict.has("AA");
Catalog.parseDestDictionary({
docBaseUrl: annotationGlobals.baseUrl,
docAttachments: annotationGlobals.attachments
if (this.data.checkBox) {
exportValues = this.data.exportValue;
} else if (this.data.radioButton) {
exportValues = this.data.buttonValue;
value: this.data.fieldValue || "Off",
defaultValue: this.data.defaultFieldValue,
editable: !this.data.readOnly,
name: this.data.fieldName,
hidden: this.data.hidden,
actions: this.data.actions,
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
dict.set("BaseFont", Name.get("ZapfDingbats"));
dict.set("Type", Name.get("FallbackType"));
dict.set("Subtype", Name.get("FallbackType"));
dict.set("Encoding", Name.get("ZapfDingbatsEncoding"));
return shadow(this, "fallbackFontDict", dict);
class ChoiceWidgetAnnotation extends WidgetAnnotation {
this.indices = dict.getArray("I");
this.hasIndices = Array.isArray(this.indices) && this.indices.length > 0;
const options = getInheritableProperty({
if (Array.isArray(options)) {
for (let i = 0, ii = options.length; i < ii; i++) {
const option = xref.fetchIfRef(options[i]);
const isOptionArray = Array.isArray(option);
exportValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[0]) : option),
displayValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[1]) : option)
if (typeof this.data.fieldValue === "string") {
this.data.fieldValue = [this.data.fieldValue];
} else if (!this.data.fieldValue) {
this.data.fieldValue = [];
this.data.fieldValue = [];
const ii = this.data.options.length;
for (const i of this.indices) {
if (Number.isInteger(i) && i >= 0 && i < ii) {
this.data.fieldValue.push(this.data.options[i].exportValue);
this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
const type = this.data.combo ? "combobox" : "listbox";
const value = this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null;
defaultValue: this.data.defaultFieldValue,
editable: !this.data.readOnly,
name: this.data.fieldName,
numItems: this.data.fieldValue.length,
multipleSelection: this.data.multiSelect,
hidden: this.data.hidden,
actions: this.data.actions,
items: this.data.options,
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
amendSavedDict(annotationStorage, dict) {
let values = annotationStorage?.get(this.data.id)?.value;
if (!Array.isArray(values)) {