@npoci/pdfform
Version:
Modern PDF form renderer with HTML overlay fields - view, fill, and map PDF forms in the browser
1,330 lines • 51.7 kB
JavaScript
import * as d from "pdfjs-dist";
import { PDFDocument as y } from "pdf-lib";
class p {
constructor() {
this.events = /* @__PURE__ */ new Map();
}
on(e, t) {
return this.events.has(e) || this.events.set(e, []), this.events.get(e).push(t), this;
}
off(e, t) {
const i = this.events.get(e);
if (i) {
const s = i.indexOf(t);
s !== -1 && i.splice(s, 1);
}
return this;
}
emit(e, ...t) {
const i = this.events.get(e);
i && i.forEach((s) => {
try {
s(...t);
} catch (r) {
console.error(`Error in event listener for "${e}":`, r);
}
});
}
once(e, t) {
const i = (...s) => {
this.off(e, i), t(...s);
};
return this.on(e, i);
}
removeAllListeners(e) {
return e ? this.events.delete(e) : this.events.clear(), this;
}
listenerCount(e) {
const t = this.events.get(e);
return t ? t.length : 0;
}
}
class w {
constructor() {
this.parsers = [], this.unparsedFields = [];
}
static getInstance() {
return this.instance || (this.instance = new w()), this.instance;
}
registerParser(e, t = "first") {
return t === "first" ? this.parsers.unshift(e) : this.parsers.length > 0 ? this.parsers.splice(-1, 0, e) : this.parsers.push(e), this;
}
parseField(e) {
for (const i of this.parsers)
try {
const s = i.parse(e);
if (s)
return console.log(`Field "${s.key}" parsed by ${i.constructor.name}`), s;
} catch (s) {
console.warn(`Parser ${i.constructor.name} threw error:`, s);
}
const t = {
key: e.getName(),
type: "unknown",
originalType: e.constructor.name,
position: this.getBasicPosition(e),
rawData: {
constructorName: e.constructor.name,
methods: this.getAvailableMethods(e),
flags: this.getFieldFlags(e)
},
reason: "No parser could identify this field type"
};
return this.unparsedFields.push(t), console.warn(`Field "${t.key}" could not be parsed. Type: ${t.originalType}`), t;
}
getUnparsedFields() {
return [...this.unparsedFields];
}
clearUnparsedFields() {
this.unparsedFields = [];
}
getBasicPosition(e) {
try {
const t = e.acroField.getWidgets();
if (t && t.length > 0) {
const i = t[0].getRectangle();
return {
x: i.x,
y: i.y,
width: i.width,
height: i.height,
page: 1
};
}
} catch {
console.warn("Could not extract position for unparsed field");
}
return { x: 0, y: 0, width: 0, height: 0, page: 1 };
}
getAvailableMethods(e) {
const t = [];
for (const i in e)
typeof e[i] == "function" && t.push(i);
return t;
}
getFieldFlags(e) {
try {
return e.acroField.getFlags() || 0;
} catch {
return 0;
}
}
}
class h {
getPosition(e) {
const t = e.acroField.getWidgets();
if (!t || t.length === 0)
return { x: 0, y: 0, width: 0, height: 0, page: 1 };
const i = t[0], s = i.getRectangle();
let r = 1;
try {
const a = i.P();
a && e.acroField.doc && (r = e.acroField.doc.getPages().findIndex((c) => c.ref === a) + 1);
} catch {
console.warn("Could not determine page number for field", e.getName());
}
const n = this.getPageHeight(e, r);
return {
x: s.x,
y: n - (s.y + s.height),
width: s.width,
height: s.height,
page: r
};
}
getPageHeight(e, t) {
var i;
try {
const s = (i = e.acroField.doc) == null ? void 0 : i.getPages();
if (s && s[t - 1])
return s[t - 1].getHeight();
} catch {
console.warn("Could not get page height");
}
return 792;
}
getBasicInfo(e) {
return {
key: e.getName(),
position: this.getPosition(e),
defaultValue: this.extractDefaultValue(e)
};
}
extractDefaultValue(e) {
try {
if (e.getText) return e.getText();
if (e.isChecked) return e.isChecked();
if (e.getSelected) return e.getSelected();
if (e.getValue) return e.getValue();
} catch {
console.warn("Could not extract default value for field", e.getName());
}
return null;
}
hasMethod(e, t) {
return typeof e[t] == "function";
}
getFieldFlags(e) {
try {
return e.acroField.getFlags() || 0;
} catch {
return 0;
}
}
}
class b extends h {
canHandle(e) {
return e.constructor.name.includes("PDFTextField") || this.hasMethod(e, "setText") || this.hasMethod(e, "getText");
}
parse(e) {
if (!this.canHandle(e)) return null;
const t = this.getFieldFlags(e);
return {
...this.getBasicInfo(e),
type: "text",
originalType: e.constructor.name,
maxLength: this.getMaxLength(e),
multiline: !!(t & 4096),
password: !!(t & 8192),
comb: !!(t & 16777216),
defaultValue: this.getText(e)
};
}
getText(e) {
try {
if (e.getText) return e.getText() || "";
} catch {
}
return "";
}
getMaxLength(e) {
try {
if (e.getMaxLength) {
const t = e.getMaxLength();
return t > 0 ? t : void 0;
}
} catch {
}
}
}
class E extends h {
canHandle(e) {
if (!e.constructor.name.includes("PDFTextField")) return !1;
const t = e.getName().toLowerCase();
return t.includes("email") || t.includes("e-mail") || t.includes("mail") || t.includes("correo");
}
parse(e) {
return this.canHandle(e) ? {
...this.getBasicInfo(e),
type: "email",
detectedType: "email",
originalType: e.constructor.name,
pattern: "^[^@]+@[^@]+\\.[^@]+$",
inputType: "email",
defaultValue: this.getText(e)
} : null;
}
getText(e) {
try {
if (e.getText) return e.getText() || "";
} catch {
}
return "";
}
}
class M extends h {
canHandle(e) {
const t = e.getName().toLowerCase(), s = this.getPosition(e).height > 60;
return e.constructor.name.includes("PDFSignature") || t.includes("signature") || t.includes("sign") || t.includes("firma") || e.constructor.name.includes("PDFTextField") && s;
}
parse(e) {
return this.canHandle(e) ? {
...this.getBasicInfo(e),
type: "signature",
detectedType: "signature",
originalType: e.constructor.name,
detectedFrom: this.getDetectionReason(e),
requiresDrawing: !0,
acceptsUpload: !0
} : null;
}
getDetectionReason(e) {
if (e.constructor.name.includes("PDFSignature"))
return "PDFSignature field type";
const t = e.getName().toLowerCase();
return t.includes("signature") || t.includes("sign") ? "Field name contains signature" : "Large text field detected as signature";
}
}
class C extends h {
canHandle(e) {
const t = e.constructor.name, i = this.getFieldFlags(e), s = t.includes("PDFButton") || t.includes("PDFCheckBox") || t.includes("Checkbox"), r = !(i & 32768);
return s && r || this.hasMethod(e, "check") || this.hasMethod(e, "isChecked");
}
parse(e) {
return this.canHandle(e) ? {
...this.getBasicInfo(e),
type: "checkbox",
originalType: e.constructor.name,
checked: this.isChecked(e),
defaultValue: this.isChecked(e)
} : null;
}
isChecked(e) {
var t;
try {
if (e.isChecked) return e.isChecked();
const i = (t = e.acroField) == null ? void 0 : t.getWidgets();
if (i && i.length > 0) {
const s = i[0].getAppearanceState();
return s && s.toString() !== "/Off";
}
} catch {
console.warn("Could not determine checkbox state");
}
return !1;
}
}
class R extends h {
canHandle(e) {
return e.constructor.name.includes("PDFDropdown") || e.constructor.name.includes("PDFComboBox") || e.constructor.name.includes("PDFChoice") || this.hasMethod(e, "getOptions") || this.hasMethod(e, "select");
}
parse(e) {
if (!this.canHandle(e)) return null;
const t = this.getFieldFlags(e), i = !!(t & 131072), s = !!(t & 2097152), r = this.extractOptions(e), n = this.getSelectedValue(e), a = {
...this.getBasicInfo(e),
originalType: e.constructor.name,
options: r,
value: n,
defaultValue: n
};
return i || !s ? {
...a,
type: "dropdown",
multiSelect: !1,
value: Array.isArray(n) ? n[0] : n
} : {
...a,
type: "listbox",
multiSelect: s
};
}
extractOptions(e) {
var i, s;
const t = [];
try {
if (e.getOptions)
return e.getOptions().map((a) => ({
value: a,
display: a
}));
const r = (s = (i = e.acroField) == null ? void 0 : i.dict) == null ? void 0 : s.get("Opt");
if (r) {
const n = r.asArray();
n && n.forEach((a) => {
if (typeof a == "string")
t.push({ value: a, display: a });
else if (a.asArray) {
const o = a.asArray();
o && o.length >= 2 && t.push({
value: o[0].toString(),
display: o[1].toString()
});
}
});
}
} catch {
console.warn("Could not extract options for dropdown", e.getName());
}
return t;
}
getSelectedValue(e) {
try {
if (e.getSelected) {
const t = e.getSelected();
return Array.isArray(t) ? t.length > 0 ? t : void 0 : t || void 0;
}
} catch {
console.warn("Could not get selected value");
}
}
}
class V extends h {
canHandle(e) {
const t = e.constructor.name, s = !!(this.getFieldFlags(e) & 32768);
return t.includes("PDFRadioGroup") || t.includes("PDFRadio") || s || this.hasMethod(e, "getOptions");
}
parse(e) {
return this.canHandle(e) ? {
...this.getBasicInfo(e),
type: "radio",
originalType: e.constructor.name,
options: this.extractOptions(e),
value: this.getSelectedValue(e),
defaultValue: this.getSelectedValue(e)
} : null;
}
extractOptions(e) {
var i;
const t = [];
try {
if (e.getOptions)
return e.getOptions().map((n) => ({
value: n,
display: n
}));
const s = (i = e.acroField) == null ? void 0 : i.getWidgets();
if (s) {
const r = /* @__PURE__ */ new Set();
s.forEach((n) => {
try {
const a = n.getAppearanceStates();
a && a.forEach((o) => {
o !== "Off" && r.add(o);
});
} catch {
}
}), r.forEach((n) => {
t.push({ value: n, display: n });
});
}
} catch {
console.warn("Could not extract radio options", e.getName());
}
return t;
}
getSelectedValue(e) {
var t;
try {
if (e.getSelected)
return e.getSelected() || void 0;
const i = (t = e.acroField) == null ? void 0 : t.getWidgets();
if (i)
for (const s of i) {
const r = s.getAppearanceState();
if (r && r.toString() !== "/Off")
return r.toString().replace("/", "");
}
} catch {
console.warn("Could not get selected radio value");
}
}
}
class P {
constructor() {
this.fieldDefinitions = /* @__PURE__ */ new Map(), this.parserChain = w.getInstance(), this.initializeParsers();
}
initializeParsers() {
this.parserChain.registerParser(new M(), "first").registerParser(new E(), "first").registerParser(new C()).registerParser(new V()).registerParser(new R()).registerParser(new b());
}
setFieldDefinitions(e) {
this.fieldDefinitions.clear(), e.forEach((t) => {
this.fieldDefinitions.set(t.key, t);
});
}
async discoverFields(e) {
this.parserChain.clearUnparsedFields();
try {
const t = await y.load(e), s = t.getForm().getFields();
s.forEach((n) => {
n.acroField && (n.acroField.doc = t);
});
const r = [];
for (const n of s) {
const a = this.parserChain.parseField(n), o = this.fieldDefinitions.get(a.key), c = {
...a,
definition: o
};
o && o.pattern && a.type === "text" && o.pattern.includes("@") && (c.detectedType = "email"), r.push(c);
}
return {
fields: r,
unparsedFields: this.parserChain.getUnparsedFields()
};
} catch (t) {
throw console.error("Error discovering fields:", t), new Error(`Failed to discover fields: ${t}`);
}
}
getFieldMappings(e) {
const t = {};
return e.forEach((i) => {
var r;
const s = ((r = i.definition) == null ? void 0 : r.name) || i.key;
t[s] = {
originalKey: i.key,
type: i.detectedType || i.type,
value: i.defaultValue
};
}), t;
}
}
const S = {
highlightColor: "#ffffcc",
highlightColorFocus: "#ffffaa"
};
class f {
constructor(e) {
this.config = e || S;
}
getState(e, t) {
var r, n, a, o;
const i = this.getValue(t), s = this.validateValue(e, i);
return {
key: e.key,
name: (r = e.definition) == null ? void 0 : r.name,
value: i,
type: e.type,
detectedType: e.detectedType,
valid: s.valid,
error: s.error,
required: (n = e.definition) == null ? void 0 : n.required,
metadata: {
title: (a = e.definition) == null ? void 0 : a.title,
placeholder: (o = e.definition) == null ? void 0 : o.placeholder
}
};
}
validateValue(e, t) {
if (!e.definition)
return { valid: !0 };
if (e.definition.pattern && typeof t == "string" && t)
try {
if (!new RegExp(e.definition.pattern).test(t))
return {
valid: !1,
error: e.definition.errorMessage || "Invalid format"
};
} catch {
console.error("Invalid regex pattern:", e.definition.pattern);
}
if (e.definition.validator) {
const i = e.definition.validator(t);
if (i)
return { valid: !1, error: i };
}
return { valid: !0 };
}
createBaseElement(e, t) {
const i = document.createElement(e);
return i.style.width = "100%", i.style.height = "100%", i.style.border = "none", i.style.padding = "0", i.style.margin = "0", i.style.outline = "none", i.style.fontFamily = "Arial, sans-serif", i.style.background = this.config.highlightColor, t.definition && (t.definition.placeholder && "placeholder" in i && (i.placeholder = t.definition.placeholder), t.definition.required && (i.style.borderLeft = "3px solid #ff9800"), t.definition.title && (i.title = t.definition.title)), i;
}
applyValidation(e, t, i) {
t.definition && e.addEventListener("input", (s) => {
const n = s.target.value;
let a = !0, o = "";
if (t.definition.pattern && (new RegExp(t.definition.pattern).test(n) || (a = !1, o = t.definition.errorMessage || "Invalid format")), t.definition.validator) {
const c = t.definition.validator(n);
c && (a = !1, o = c);
}
!a && n ? (e.style.borderColor = "red", e.style.borderWidth = "2px", e.style.borderStyle = "solid", e.title = o) : (e.style.borderColor = "", e.style.borderWidth = "", e.style.borderStyle = "", e.title = t.definition.title || ""), i(n);
});
}
}
class T extends f {
validateValue(e, t) {
var s;
const i = super.validateValue(e, t);
return i.valid ? (s = e.definition) != null && s.required && (!t || typeof t == "string" && !t.trim()) ? {
valid: !1,
error: "This field is required"
} : { valid: !0 } : i;
}
createField(e, t, i) {
const s = this.createBaseElement("input", e);
if (s.type = "text", s.style.fontSize = t.fontSize + "px", s.style.lineHeight = "1", s.style.verticalAlign = "top", e.defaultValue && (s.value = e.defaultValue), e.multiline) {
const r = this.createBaseElement("textarea", e);
return r.style.fontSize = t.fontSize + "px", r.style.resize = "none", r.style.lineHeight = "1.2", e.defaultValue && (r.value = e.defaultValue), r.addEventListener("input", () => i(r.value)), this.applyValidation(r, e, i), r.addEventListener("focus", () => {
r.style.background = this.config.highlightColorFocus;
}), r.addEventListener("blur", () => {
r.style.background = this.config.highlightColor;
}), r;
}
return e.password && (s.type = "password"), e.maxLength && (s.maxLength = e.maxLength), (e.detectedType === "email" || e.type === "email") && (s.type = "email"), s.addEventListener("input", () => i(s.value)), this.applyValidation(s, e, i), s.addEventListener("focus", () => {
s.style.background = this.config.highlightColorFocus;
}), s.addEventListener("blur", () => {
s.style.background = this.config.highlightColor;
}), s;
}
getValue(e) {
return e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement ? e.value : "";
}
setValue(e, t) {
(e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) && (e.value = String(t || ""));
}
}
class L extends f {
createField(e, t, i) {
var r;
const s = document.createElement("input");
return s.type = "checkbox", s.style.width = "100%", s.style.height = "100%", s.style.cursor = "pointer", s.style.margin = "0", s.style.padding = "0", s.style.accentColor = this.config.highlightColor, s.style.outline = `3px solid ${this.config.highlightColor}`, s.style.outlineOffset = "-3px", s.style.verticalAlign = "top", (e.defaultValue === !0 || e.checked) && (s.checked = !0), s.addEventListener("change", () => {
i(s.checked);
}), (r = e.definition) != null && r.title && (s.title = e.definition.title), s;
}
getValue(e) {
return e instanceof HTMLInputElement ? e.checked : !1;
}
setValue(e, t) {
e instanceof HTMLInputElement && (e.checked = !!t);
}
}
class B extends f {
createField(e, t, i) {
var n;
const s = this.createBaseElement("select", e);
if (s.style.fontSize = t.fontSize + "px", s.style.lineHeight = "1", s.style.verticalAlign = "top", s.style.cursor = "pointer", !((n = e.definition) != null && n.required)) {
const a = document.createElement("option");
a.value = "", a.textContent = "-- Select --", s.appendChild(a);
}
return (e.options || []).forEach((a) => {
const o = document.createElement("option");
o.value = a.value, o.textContent = a.display, s.appendChild(o);
}), e.defaultValue && (s.value = e.defaultValue), e.multiSelect && (s.multiple = !0, s.style.height = "100%"), s.addEventListener("change", () => {
if (s.multiple) {
const a = Array.from(s.selectedOptions).map((o) => o.value);
i(a);
} else
i(s.value);
}), s.addEventListener("focus", () => {
s.style.background = this.config.highlightColorFocus;
}), s.addEventListener("blur", () => {
s.style.background = this.config.highlightColor;
}), s;
}
getValue(e) {
return e instanceof HTMLSelectElement ? e.multiple ? Array.from(e.selectedOptions).map((t) => t.value) : e.value : "";
}
setValue(e, t) {
e instanceof HTMLSelectElement && (e.multiple && Array.isArray(t) ? (Array.from(e.options).forEach((i) => i.selected = !1), t.forEach((i) => {
const s = e.querySelector(`option[value="${i}"]`);
s && (s.selected = !0);
})) : e.value = String(t || ""));
}
}
class W extends f {
constructor() {
super(...arguments), this.inputs = [];
}
createField(e, t, i) {
const s = document.createElement("div");
s.style.display = "flex", s.style.width = "100%", s.style.height = "100%", s.style.gap = "2px";
const r = e.maxLength || 9, n = (t.width - (r - 1) * 2) / r;
for (let a = 0; a < r; a++) {
const o = document.createElement("input");
o.type = "text", o.maxLength = 1, o.style.width = n + "px", o.style.height = "100%", o.style.textAlign = "center", o.style.border = "none", o.style.background = this.config.highlightColor, o.style.fontSize = t.fontSize + "px", o.style.fontFamily = "monospace", o.style.padding = "0", o.style.margin = "0", o.style.outline = "none", o.style.boxSizing = "border-box", o.addEventListener("input", (c) => {
c.target.value.length === 1 && a < r - 1 && this.inputs[a + 1].focus(), this.updateValue(i);
}), o.addEventListener("keydown", (c) => {
c.key === "Backspace" && !o.value && a > 0 && (c.preventDefault(), this.inputs[a - 1].focus(), this.inputs[a - 1].value = "", this.updateValue(i));
}), o.addEventListener("paste", (c) => {
var k;
c.preventDefault();
const x = (((k = c.clipboardData) == null ? void 0 : k.getData("text")) || "").replace(/[^a-zA-Z0-9]/g, "").split("");
x.forEach((m, g) => {
a + g < this.inputs.length && (this.inputs[a + g].value = m);
});
const D = this.inputs.findIndex((m, g) => g >= a && !m.value);
D !== -1 ? this.inputs[D].focus() : this.inputs[Math.min(a + x.length, this.inputs.length - 1)].focus(), this.updateValue(i);
}), o.addEventListener("focus", () => {
o.style.background = "rgba(255, 255, 0, 0.5)";
}), o.addEventListener("blur", () => {
o.style.background = this.config.highlightColor;
}), this.inputs.push(o), s.appendChild(o);
}
return e.defaultValue && String(e.defaultValue).split("").forEach((o, c) => {
c < this.inputs.length && (this.inputs[c].value = o);
}), s;
}
updateValue(e) {
const t = this.inputs.map((i) => i.value).join("");
e(t);
}
getValue(e) {
return this.inputs.map((t) => t.value).join("");
}
setValue(e, t) {
const i = String(t || "").split("");
this.inputs.forEach((s, r) => {
s.value = i[r] || "";
});
}
}
class O {
constructor(e) {
this.globalRenderers = /* @__PURE__ */ new Map(), this.fieldOverrides = /* @__PURE__ */ new Map(), this.config = { ...S, ...e }, this.registerDefaults();
}
registerDefaults() {
const e = new T(this.config), t = new L(this.config), i = new B(this.config);
this.globalRenderers.set("text", e), this.globalRenderers.set("email", e), this.globalRenderers.set("password", e), this.globalRenderers.set("textarea", e), this.globalRenderers.set("signature", e), this.globalRenderers.set("checkbox", t), this.globalRenderers.set("radio", i), this.globalRenderers.set("dropdown", i), this.globalRenderers.set("listbox", i);
}
registerGlobalRenderer(e, t) {
this.globalRenderers.set(e, t);
}
registerFieldOverride(e, t) {
this.fieldOverrides.set(e, t);
}
getRenderer(e) {
return this.fieldOverrides.has(e.key) ? this.fieldOverrides.get(e.key) : e.type === "text" && e.comb ? new W(this.config) : e.detectedType && this.globalRenderers.has(e.detectedType) ? this.globalRenderers.get(e.detectedType) : this.globalRenderers.has(e.type) ? this.globalRenderers.get(e.type) : (console.warn(`No renderer found for field "${e.key}" of type "${e.type}", using text renderer`), this.globalRenderers.get("text"));
}
}
class A extends p {
constructor() {
super(...arguments), this.fieldValues = /* @__PURE__ */ new Map(), this.fieldMappings = /* @__PURE__ */ new Map(), this.fields = [];
}
setFields(e) {
this.fields = e, this.fieldMappings.clear(), e.forEach((t) => {
var s;
const i = ((s = t.definition) == null ? void 0 : s.name) || t.key;
this.fieldMappings.set(i, t.key), t.defaultValue !== void 0 && t.defaultValue !== null && this.fieldValues.set(t.key, t.defaultValue);
});
}
setValue(e, t, i = !0) {
const s = this.fieldValues.get(e);
this.fieldValues.set(e, t);
const r = this.fields.find((c) => c.key === e);
if (!r) {
console.warn(`Field not found: ${e}`);
return;
}
if (!i)
return;
const n = {
...r,
value: t
}, a = this.validateField(r, t), o = {
field: n,
validationState: a,
oldValue: s,
newValue: t,
timestamp: Date.now()
};
this.emit("fieldChange", o);
}
getValue(e) {
return this.fieldValues.get(e);
}
getValues() {
const e = {};
for (const [t, i] of this.fieldMappings.entries()) {
const s = this.fieldValues.get(i);
s !== void 0 && (e[t] = s);
}
return e;
}
getRawValues() {
const e = {};
return this.fieldValues.forEach((t, i) => {
e[i] = t;
}), e;
}
setValues(e) {
Object.entries(e).forEach(([t, i]) => {
if (this.fieldMappings.has(t)) {
const s = this.fieldMappings.get(t);
this.setValue(s, i, !1);
} else
this.setValue(t, i, !1);
});
}
reset() {
this.fields.forEach((e) => {
e.defaultValue !== void 0 && e.defaultValue !== null ? this.setValue(e.key, e.defaultValue, !1) : this.setValue(e.key, null, !1);
});
}
getFieldByKey(e) {
return this.fields.find((t) => t.key === e);
}
getFieldByName(e) {
const t = this.fieldMappings.get(e);
if (t)
return this.getFieldByKey(t);
}
getFields() {
return this.fields;
}
validateField(e, t) {
if (!e.definition)
return { valid: !0 };
if (e.definition.pattern && typeof t == "string")
try {
if (!new RegExp(e.definition.pattern).test(t))
return {
valid: !1,
error: e.definition.errorMessage || "Invalid format"
};
} catch {
console.error("Invalid regex pattern:", e.definition.pattern);
}
if (e.definition.validator) {
const i = e.definition.validator(t);
if (i)
return { valid: !1, error: i };
}
return e.definition.required && e.type === "text" && (!t || typeof t == "string" && !t.trim()) ? {
valid: !1,
error: "This field is required"
} : { valid: !0 };
}
}
function v(l, e) {
const t = document.createElement(l);
return e && Object.entries(e).forEach(([i, s]) => {
i === "style" && typeof s == "object" ? F(t, s) : i === "className" ? t.className = s : i === "textContent" ? t.textContent = s : i.startsWith("on") || t.setAttribute(i, s);
}), t;
}
function F(l, e) {
Object.entries(e).forEach(([t, i]) => {
i !== void 0 && (l.style[t] = i);
});
}
function $(l) {
const e = Math.floor(l * 0.6);
return Math.min(Math.max(e, 8), 16);
}
class H {
constructor(e, t) {
this.registry = e, this.stateManager = t, this.container = null, this.scale = 1, this.currentPage = 1, this.fieldElements = /* @__PURE__ */ new Map();
}
setContainer(e) {
this.container = e;
}
setScale(e) {
this.scale = e;
}
setCurrentPage(e) {
this.currentPage = e, this.renderFields();
}
renderFields() {
if (!this.container) return;
this.fieldElements.forEach((i) => i.remove()), this.fieldElements.clear(), this.stateManager.getFields().filter((i) => i.position.page === this.currentPage).forEach((i) => {
const s = this.createFieldElement(i);
s && this.container && (this.container.appendChild(s), this.fieldElements.set(i.key, s));
});
}
createFieldElement(e) {
if (!e.position) return null;
const t = v("div", {
className: "pdf-form-field",
style: {
position: "absolute",
left: `${e.position.x * this.scale}px`,
top: `${e.position.y * this.scale}px`,
width: `${e.position.width * this.scale}px`,
height: `${e.position.height * this.scale}px`,
zIndex: "10"
}
}), i = this.registry.getRenderer(e), s = {
width: e.position.width * this.scale,
height: e.position.height * this.scale,
fontSize: $(e.position.height * this.scale)
}, r = i.createField(
e,
s,
(a) => this.stateManager.setValue(e.key, a)
), n = this.stateManager.getValue(e.key);
return n != null && i.setValue(r, n), t.appendChild(r), t;
}
updateFieldValues() {
this.fieldElements.forEach((e, t) => {
const i = this.stateManager.getFieldByKey(t);
if (!i) return;
const s = e.firstElementChild;
if (!s) return;
const r = this.registry.getRenderer(i), n = this.stateManager.getValue(t);
n != null && r.setValue(s, n);
});
}
destroy() {
this.fieldElements.forEach((e) => e.remove()), this.fieldElements.clear();
}
}
class U extends p {
constructor(e) {
var t;
super(), this.options = e, this.pdfDoc = null, this.pdfArrayBuffer = null, this.currentPage = 1, this.totalPages = 0, this.scale = 1, this.renderTask = null, this.pageWrapper = null, this.canvas = null, this.fields = [], this.unparsedFields = [], this.pdfPageWidth = 0, this.pdfPageHeight = 0, e.workerSrc && !((t = d.GlobalWorkerOptions) != null && t.workerSrc) && (d.GlobalWorkerOptions.workerSrc = e.workerSrc), this.container = e.container, this.pdfUrl = e.pdfUrl, this.scale = e.scale || 1, this.currentPage = e.page || 1, this.discoveryService = new P(), this.rendererRegistry = new O({
highlightColor: e.highlightColor,
highlightColorFocus: e.highlightColorFocus
}), this.stateManager = new A(), this.overlayRenderer = new H(this.rendererRegistry, this.stateManager), e.fieldDefinitions && this.discoveryService.setFieldDefinitions(e.fieldDefinitions), e.fieldRenderers && Object.entries(e.fieldRenderers).forEach(([i, s]) => {
this.rendererRegistry.registerGlobalRenderer(i, s);
}), e.fieldOverrides && Object.entries(e.fieldOverrides).forEach(([i, s]) => {
this.rendererRegistry.registerFieldOverride(i, s);
}), this.stateManager.on("fieldChange", (i) => {
this.emit("fieldChange", i.field, i.validationState);
});
}
async render() {
try {
F(this.container, {
position: "relative",
width: "100%",
display: "flex",
justifyContent: "center"
}), await this.loadPDF();
} catch (e) {
this.handleError(e);
}
}
async loadPDF() {
try {
const e = await fetch(this.pdfUrl);
this.pdfArrayBuffer = await e.arrayBuffer(), this.options.preprocessor && (this.pdfArrayBuffer = await this.options.preprocessor(this.pdfArrayBuffer));
const { fields: t, unparsedFields: i } = await this.discoveryService.discoverFields(this.pdfArrayBuffer);
this.fields = t, this.unparsedFields = i, this.stateManager.setFields(t), this.emit("fieldsDiscovered", t), this.options.onFieldsDiscovered && this.options.onFieldsDiscovered(t, i);
const s = d.getDocument(this.pdfArrayBuffer.slice(0));
this.pdfDoc = await s.promise, this.totalPages = this.pdfDoc.numPages, this.emit("ready", t), this.emit("pageCountChange", this.totalPages);
const n = (await this.pdfDoc.getPage(1)).getViewport({ scale: 1 });
this.pdfPageWidth = n.width, this.pdfPageHeight = n.height, await this.renderPage(this.currentPage);
} catch (e) {
throw new Error(`Failed to load PDF: ${e}`);
}
}
calculateScale() {
const e = this.container.clientWidth, t = Math.min(e, 900);
this.scale = Math.min(t / this.pdfPageWidth, 1.5);
}
cancelRender() {
this.renderTask && (this.renderTask.cancel(), this.renderTask = null);
}
async renderPage(e) {
if (!this.pdfDoc) return;
this.cancelRender();
const t = await this.pdfDoc.getPage(e);
this.calculateScale();
const i = t.getViewport({ scale: this.scale });
this.pageWrapper || (this.pageWrapper = v("div", {
style: {
position: "relative",
background: "white",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
margin: "0 auto"
}
}), this.container.appendChild(this.pageWrapper)), F(this.pageWrapper, {
width: `${i.width}px`,
height: `${i.height}px`
}), this.canvas || (this.canvas = v("canvas", {
style: { display: "block" }
}), this.pageWrapper.appendChild(this.canvas));
const s = this.canvas.getContext("2d"), r = window.devicePixelRatio || 1;
this.canvas.width = Math.floor(i.width * r), this.canvas.height = Math.floor(i.height * r), this.canvas.style.width = `${Math.floor(i.width)}px`, this.canvas.style.height = `${Math.floor(i.height)}px`;
const a = {
canvasContext: s,
viewport: i,
transform: r !== 1 ? [r, 0, 0, r, 0, 0] : void 0,
intent: "display"
};
this.renderTask = t.render(a);
try {
await this.renderTask.promise, this.overlayRenderer.setContainer(this.pageWrapper), this.overlayRenderer.setScale(this.scale), this.overlayRenderer.setCurrentPage(e), this.overlayRenderer.renderFields(), this.emit("pageChange", e);
} catch {
}
}
handleError(e) {
console.error("PDFFormRenderer Error:", e), this.emit("error", e);
}
// Public API
setPage(e) {
e < 1 || e > this.totalPages || (this.currentPage = e, this.renderPage(e));
}
getCurrentPage() {
return this.currentPage;
}
getTotalPages() {
return this.totalPages;
}
getFieldValues() {
return this.stateManager.getValues();
}
getRawFieldValues() {
return this.stateManager.getRawValues();
}
setFieldValues(e) {
this.stateManager.setValues(e), this.overlayRenderer.updateFieldValues();
}
getUnparsedFields() {
return [...this.unparsedFields];
}
getFieldStates() {
const e = {};
return this.fields.forEach((t) => {
var n;
const i = this.stateManager.getValue(t.key), s = this.stateManager.validateField(t, i), r = ((n = t.definition) == null ? void 0 : n.name) || t.key;
e[r] = s;
}), e;
}
destroy() {
this.cancelRender(), this.overlayRenderer.destroy(), this.stateManager.removeAllListeners(), this.removeAllListeners(), this.pdfDoc && (this.pdfDoc.destroy(), this.pdfDoc = null), this.container.innerHTML = "";
}
}
class z {
async fillFromArrayBuffer(e, t) {
const i = await y.load(e);
return this.fillDocument(i, t);
}
async fillFromBytes(e, t) {
const i = await y.load(e);
return this.fillDocument(i, t);
}
async fillFromUrl(e, t) {
const s = await (await fetch(e)).arrayBuffer();
return this.fillFromArrayBuffer(s, t);
}
async fillDocument(e, t) {
const i = e.getForm();
return Object.entries(t).forEach(([s, r]) => {
try {
const n = i.getField(s);
if (n.constructor.name.includes("PDFTextField"))
n.setText(String(r || ""));
else if (n.constructor.name.includes("PDFCheckBox")) {
const a = n;
r ? a.check() : a.uncheck();
} else n.constructor.name.includes("PDFRadioGroup") ? r && n.select(r) : n.constructor.name.includes("PDFDropdown") && r && n.select(r);
} catch (n) {
console.error(`Error setting field ${s}:`, n);
}
}), e.save();
}
}
class q extends p {
constructor(e, t) {
super(), this.clickZones = /* @__PURE__ */ new Map(), this.container = e, this.mappingStateManager = t;
}
renderZones(e, t, i) {
this.clearZones(), e.filter((r) => r.position.page === t).forEach((r) => {
const n = this.createClickZone(r, i);
n && (this.container.appendChild(n), this.clickZones.set(r.key, n));
});
}
createClickZone(e, t) {
const i = document.createElement("div");
i.className = "pdfform-field-zone";
const s = this.mappingStateManager.getFieldDefinition(e.key), r = s !== void 0;
if (i.classList.add(r ? "mapped" : "unmapped"), i.style.position = "absolute", i.style.left = `${e.position.x * t}px`, i.style.top = `${e.position.y * t}px`, i.style.width = `${e.position.width * t}px`, i.style.height = `${e.position.height * t}px`, i.style.zIndex = "100", i.title = r ? `${s.name} (${e.key})` : `Unmapped: ${e.key}`, r && s.name) {
const n = document.createElement("span");
n.className = "pdfform-field-label", n.textContent = s.name, i.appendChild(n);
}
return i.addEventListener("click", (n) => {
n.stopPropagation(), this.emit("fieldClicked", e);
}), i;
}
updateFieldStates() {
this.clickZones.forEach((e, t) => {
const i = this.mappingStateManager.getFieldDefinition(t), s = i !== void 0;
e.classList.remove("mapped", "unmapped"), e.classList.add(s ? "mapped" : "unmapped"), e.title = s ? `${i.name} (${t})` : `Unmapped: ${t}`;
const r = e.querySelector(".pdfform-field-label");
if (r && r.remove(), s && i.name) {
const n = document.createElement("span");
n.className = "pdfform-field-label", n.textContent = i.name, e.appendChild(n);
}
});
}
clearZones() {
this.clickZones.forEach((e) => e.remove()), this.clickZones.clear();
}
destroy() {
this.clearZones();
}
}
class I extends p {
constructor() {
super(...arguments), this.fieldDefinitions = /* @__PURE__ */ new Map(), this.fields = [];
}
setFields(e) {
this.fields = e, this.emitStats();
}
setFieldDefinition(e, t) {
for (const [i, s] of this.fieldDefinitions.entries())
if (i !== e && s.name === t.name)
throw new Error(`Field name "${t.name}" is already used by another field`);
this.fieldDefinitions.set(e, t), this.emitStats(e, t);
}
getFieldDefinition(e) {
return this.fieldDefinitions.get(e);
}
removeFieldDefinition(e) {
this.fieldDefinitions.delete(e), this.emitStats(e);
}
getAllDefinitions() {
return Array.from(this.fieldDefinitions.values());
}
getMappingStats() {
return {
total: this.fields.length,
mapped: this.fieldDefinitions.size
};
}
clearMappings() {
this.fieldDefinitions.clear(), this.emitStats();
}
importDefinitions(e) {
const t = /* @__PURE__ */ new Set();
e.forEach((i) => {
if (i.name) {
if (t.has(i.name))
throw new Error(`Duplicate field name "${i.name}" in imported definitions`);
t.add(i.name);
}
}), this.fieldDefinitions.clear(), e.forEach((i) => {
i.key && i.name && this.fieldDefinitions.set(i.key, i);
}), this.emitStats();
}
emitStats(e, t) {
const i = {
total: this.fields.length,
mapped: this.fieldDefinitions.size,
fieldKey: e,
definition: t
};
this.emit("mappingChanged", i);
}
}
class Z {
constructor() {
this.modal = null, this.form = null, this.currentField = null, this.onSave = null, this.onRemove = null, this.createModal();
}
createModal() {
this.modal = document.createElement("div"), this.modal.className = "pdfform-modal", this.modal.innerHTML = `
<div class="pdfform-modal-content">
<div class="pdfform-modal-header">
<h2>Define Field Properties</h2>
<button type="button" class="pdfform-modal-close">×</button>
</div>
<form class="pdfform-modal-form">
<div class="pdfform-form-group">
<label>PDF Field Key:</label>
<input type="text" name="key" readonly class="pdfform-input-readonly">
</div>
<div class="pdfform-form-group">
<label>Property Name <span class="pdfform-required">*</span>:</label>
<input type="text" name="name" required placeholder="e.g., email, firstName" class="pdfform-input">
<small>Required - this is the key used in form data (e.g., formData.email)</small>
<div class="pdfform-error" data-field="name"></div>
</div>
<div class="pdfform-form-group">
<label>Display Label:</label>
<input type="text" name="title" placeholder="e.g., Email Address, First Name" class="pdfform-input">
<small>User-friendly label shown in tooltips</small>
</div>
<div class="pdfform-form-group">
<label>Placeholder:</label>
<input type="text" name="placeholder" placeholder="e.g., Enter your email" class="pdfform-input">
</div>
<div class="pdfform-form-group">
<label class="pdfform-checkbox-label">
<input type="checkbox" name="required">
Required Field
</label>
</div>
<div class="pdfform-form-group">
<label>Validation Pattern (regex):</label>
<input type="text" name="pattern" placeholder="e.g., ^[^@]+@[^@]+\\.[^@]+$" class="pdfform-input">
</div>
<div class="pdfform-form-group">
<label>Error Message:</label>
<input type="text" name="errorMessage" placeholder="e.g., Please enter a valid email" class="pdfform-input">
</div>
<div class="pdfform-form-actions">
<button type="button" class="pdfform-btn pdfform-btn-danger pdfform-remove-btn" style="display: none;">
Remove Mapping
</button>
<button type="button" class="pdfform-btn pdfform-cancel-btn">Cancel</button>
<button type="submit" class="pdfform-btn pdfform-btn-primary">Save Field Definition</button>
</div>
</form>
</div>
`, document.body.appendChild(this.modal), this.form = this.modal.querySelector(".pdfform-modal-form"), this.setupEventListeners();
}
setupEventListeners() {
this.modal.querySelector(".pdfform-modal-close").addEventListener("click", () => this.hide()), this.modal.querySelector(".pdfform-cancel-btn").addEventListener("click", () => this.hide()), this.modal.querySelector(".pdfform-remove-btn").addEventListener("click", () => {
this.currentField && this.onRemove && (this.onRemove(this.currentField), this.hide());
}), this.form.addEventListener("submit", (r) => {
r.preventDefault(), this.handleSubmit();
}), this.modal.addEventListener("click", (r) => {
r.target === this.modal && this.hide();
});
const s = this.form.querySelector('input[name="name"]');
s.addEventListener("input", () => this.validateName(s.value));
}
validateName(e) {
const t = this.form.querySelector('[data-field="name"]');
return e.trim() ? (t.style.display = "none", !0) : (t.textContent = "Name is required", t.style.display = "block", !1);
}
handleSubmit() {
var i, s, r, n;
const e = new FormData(this.form), t = e.get("name");
if (this.validateName(t) && this.currentField && this.onSave) {
const a = {
key: this.currentField.key,
name: t.trim(),
title: ((i = e.get("title")) == null ? void 0 : i.trim()) || t.trim(),
placeholder: (s = e.get("placeholder")) == null ? void 0 : s.trim(),
required: e.get("required") === "on",
pattern: ((r = e.get("pattern")) == null ? void 0 : r.trim()) || void 0,
errorMessage: ((n = e.get("errorMessage")) == null ? void 0 : n.trim()) || void 0
};
this.onSave(this.currentField, a), this.hide();
}
}
show(e, t) {
this.currentField = e, this.form.reset();
const i = this.form.querySelector('input[name="key"]');
i.value = e.key;
const s = this.form.querySelector(".pdfform-remove-btn");
if (s.style.display = t ? "block" : "none", t) {
const n = {
name: t.name,
title: t.title || "",
placeholder: t.placeholder || "",
pattern: t.pattern || "",
errorMessage: t.errorMessage || ""
};
Object.entries(n).forEach(([o, c]) => {
const u = this.form.querySelector(`input[name="${o}"]`);
u && (u.value = c);
});
const a = this.form.querySelector('input[name="required"]');
a.checked = t.required || !1;
}
this.modal.style.display = "block", this.form.querySelector('input[name="name"]').focus();
}
hide() {
this.modal.style.display = "none", this.currentField = null;
}
onFieldSave(e) {
this.onSave = e;
}
onFieldRemove(e) {
this.onRemove = e;
}
destroy() {
this.modal && (this.modal.remove(), this.modal = null, this.form = null);
}
}
class G extends p {
constructor(e) {
var t;
super(), this.pdfDoc = null, this.currentPage = 1, this.pageWrapper = null, this.canvas = null, this.scale = 1, this.fields = [], this.renderTask = null, e.workerSrc && !((t = d.GlobalWorkerOptions) != null && t.workerSrc) && (d.GlobalWorkerOptions.workerSrc = e.workerSrc), this.container = e.container, this.pdfUrl = e.pdfUrl, this.discoveryService = new P(), this.mappingStateManager = new I(), this.modalManager = new Z(), e.fieldDefinitions && this.mappingStateManager.importDefinitions(e.fieldDefinitions), this.clickZoneRenderer = null, this.setupEventHandlers();
}
setupEventHandlers() {
this.mappingStateManager.on("mappingChanged", (e) => {
this.clickZoneRenderer && this.clickZoneRenderer.updateFieldStates(), this.emit("mappingChanged", e);
}), this.modalManager.onFieldSave((e, t) => {
this.mappingStateManager.setFieldDefinition(e.key, t);
}), this.modalManager.onFieldRemove((e) => {
this.mappingStateManager.removeFieldDefinition(e.key);
});
}
async render() {
try {
this.container.innerHTML = "", this.container.style.position = "relative";
const e = await this.loadPDF(), { fields: t, unparsedFields: i } = await this.discoveryService.discoverFields(e);
this.fields = t, this.mappingStateManager.setFields(t), this.pdfDoc = await d.getDocument({ data: e }).promise, await this.renderPage(), this.emit("ready", this.fields), this.emit("mappingChanged", this.mappingStateManager.getMappingStats()), i.length > 0 && console.warn("Unparsed fields:", i);
} catch (e) {
throw console.error("Failed to render PDF:", e), this.emit("error", e), e;
}
}
async loadPDF() {
const e = await fetch(this.pdfUrl);
if (!e.ok)
throw new Error(`Failed to load PDF: ${e.statusText}`);
return e.arrayBuffer();
}
async renderPage() {
if (!this.pdfDoc) return;
this.renderTask && (this.renderTask.cancel(), this.renderTask = null);
const e = await this.pdfDoc.getPage(this.currentPage), t = e.getViewport({ scale: 1 }), i = this.container.clientWidth - 40;
this.scale = i / t.width;
const s = e.getViewport({ scale: this.scale });
this.pageWrapper || (this.pageWrapper = document.createElement("div"), this.pageWrapper.style.position = "relative", this.pageWrapper.style.background = "white", this.pageWrapper.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.1)", this.pageWrapper.style.margin = "0 auto", this.container.appendChild(this.pageWrapper), this.clickZoneRenderer = new q(this.pageWrapper, this.mappingStateManager), this.clickZoneRenderer.on("fieldClicked", (a) => {
const o = this.mappingStateManager.getFieldDefinition(a.key);
this.modalManager.show(a, o);
})), this.pageWrapper.style.width = `${s.width}px`, this.pageWrapper.style.height = `${s.height}px`, this.canvas || (this.canvas = document.createElement("canvas"), this.canvas.style.display = "block", this.pageWrapper.appendChild(this.canvas)), this.canvas.width = s.width, this.canvas.height = s.height, this.canvas.style.width = `${s.width}px`, this.canvas.style.height = `${s.height}px`;
const r = this.canvas.getContext("2d");
if (!r) return;
const n = {
canvasContext: r,
viewport: s
};
this.renderTask = e.render(n), await this.renderTask.promise, this.clickZoneRenderer.clearZones(), this.clickZoneRenderer.renderZones(this.fields, this.currentPage, this.scale);
}
async setPage(e) {
if (!this.pdfDoc) return;
const t = this.pdfDoc.numPages;
e < 1 || e > t || (this.currentPage = e, await this.renderPage());
}
getPageCount() {
var e;
return ((e = this.pdfDoc) == null ? void 0 : e.numPages) || 0;
}
getCurrentPage() {
return this.currentPage;
}
setFieldDefinition(e, t) {
if (!t.name)
throw new Error("Field name is required");
const i = {
key: e,
name: t.name,
title: t.title,
placeholder: t.placeholder,
required: t.required,
pattern: t.pattern,
errorMessage: t.errorMessage
};
this.mappingStateManager.setFieldDefinition(e, i);
}
removeFieldDefinition(e) {
this.mappingStateManager.removeFieldDefinition(e);
}
getFieldDefinitions() {
return this.mappingStateManager.getAllDefinitions();
}
exportDefinitions() {
const e = this.getFieldDefinitions();
return JSON.stringify(e, null, 2);
}
importDefinitions(e) {
this.mappingStateManager.importDefinitions(e), this.clickZoneRenderer.updateFieldStates();
}
getUnmappedFields() {
return this.fields.filter((e) => !this.mappingStateManager.getFieldDefinition(e.key));
}
clearMappings() {
this.mappingStateManager.clearMappings(), this.clickZoneRenderer.updateFieldStates();
}
destroy() {
this.renderTask && this.renderTask.cancel(), this.clickZoneRenderer && this.clickZoneRenderer.destroy(), this.modalManager && this.modalManager.destroy(), this.container.innerHTML = "", this.pageWrapper = null, this.canvas = null;
}
}
class j {
constructor(e) {
var t;
this.options = e, this.pdfDoc = null, this.pages = [], this.renderedPages = /* @__PURE__ */ new Map(), this.container = e.container, this.pdfUrl = e.pdfUrl, this.scale = e.scale || 1, e.workerSrc && !((t = d.GlobalWorkerOptions) != null && t.workerSrc) && (d.GlobalWorkerOptions.workerSrc = e.workerSrc), this.container.innerHTML = "", this.loadPDF();
}
async loadPDF() {
try {
const e = d.getDocument(this.pdfUrl);
this.pdfDoc = await e.promise, this.pages = [];
for (let t = 1; t <= this.pdfDoc.numPages; t++) {
const s = (await this.pdfDoc.getPage(t)).getViewport({ scale: this.scale });
this.pages.push({
pageNumber: t,
width: s.width,
height: s.height,
scale: this.scale,
rotation: s.rotation,
viewBox: [0, 0, s.width, s.height]
});
}
this.options.onPagesLoaded && this.options.onPagesLoaded(this.pages);
} catch (e) {
throw console.error("Error loading PDF:", e), e;
}
}
async renderPage(e) {
if (!this.pdfDoc)
throw new Error("PDF document not loaded");
const t = this.renderedPages.get(e);
if (t)
return t;
const i = await this.pdfDoc.getPage(e), s = i.getViewport({ scale: this.scale }), r = document.createElement("canvas"), n = r.getContext("2d");
if (!n)
throw new Error("Failed to get canvas context");
r.width = s.width, r.height = s.height,