@unlev/exeq 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -2
- package/dist/index.d.mts +120 -4
- package/dist/index.d.ts +120 -4
- package/dist/index.js +294 -156
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +293 -157
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/lib/index.ts
|
|
31
31
|
var lib_exports = {};
|
|
32
32
|
__export(lib_exports, {
|
|
33
|
+
A4: () => A4,
|
|
33
34
|
BUILTIN_TRANSFORMS: () => BUILTIN_TRANSFORMS,
|
|
35
|
+
DEFAULT_CALIBRATION: () => DEFAULT_CALIBRATION,
|
|
34
36
|
DEFAULT_SIGNER_ROLES: () => DEFAULT_SIGNER_ROLES,
|
|
35
37
|
DesignerView: () => DesignerView,
|
|
36
38
|
FIELD_DEFAULTS: () => FIELD_DEFAULTS,
|
|
@@ -42,7 +44,11 @@ __export(lib_exports, {
|
|
|
42
44
|
SignatureCanvas: () => SignatureCanvas,
|
|
43
45
|
SignerRoleSelector: () => SignerRoleSelector,
|
|
44
46
|
SignerView: () => SignerView,
|
|
47
|
+
US_LEGAL: () => US_LEGAL,
|
|
48
|
+
US_LETTER: () => US_LETTER,
|
|
49
|
+
applyCalibration: () => applyCalibration,
|
|
45
50
|
createField: () => createField,
|
|
51
|
+
createPdfBuilder: () => createPdfBuilder,
|
|
46
52
|
downloadPdf: () => downloadPdf,
|
|
47
53
|
generateFilledPdf: () => generateFilledPdf,
|
|
48
54
|
generateId: () => generateId,
|
|
@@ -222,6 +228,7 @@ function PdfViewer({
|
|
|
222
228
|
onPageClick,
|
|
223
229
|
onDropField,
|
|
224
230
|
onGroupMove,
|
|
231
|
+
onMoveStart,
|
|
225
232
|
onMoveEnd,
|
|
226
233
|
mode,
|
|
227
234
|
currentSigner,
|
|
@@ -331,6 +338,7 @@ function PdfViewer({
|
|
|
331
338
|
onMove: onFieldMove,
|
|
332
339
|
onResize: onFieldResize,
|
|
333
340
|
onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
|
|
341
|
+
onMoveStart,
|
|
334
342
|
onMoveEnd,
|
|
335
343
|
otherFields: mode === "designer" ? pageFields.filter((f) => f.id !== field.id && !selectedFieldIds.has(f.id)) : void 0,
|
|
336
344
|
setGuides: mode === "designer" ? setGuides : void 0,
|
|
@@ -420,6 +428,7 @@ function FieldOverlayItem({
|
|
|
420
428
|
onMove,
|
|
421
429
|
onResize,
|
|
422
430
|
onGroupMove,
|
|
431
|
+
onMoveStart,
|
|
423
432
|
onMoveEnd,
|
|
424
433
|
otherFields,
|
|
425
434
|
setGuides,
|
|
@@ -449,6 +458,7 @@ function FieldOverlayItem({
|
|
|
449
458
|
onSelect(e);
|
|
450
459
|
}
|
|
451
460
|
if (field.locked) return;
|
|
461
|
+
onMoveStart?.();
|
|
452
462
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
453
463
|
if (!pageEl) return;
|
|
454
464
|
didDragRef.current = false;
|
|
@@ -497,11 +507,12 @@ function FieldOverlayItem({
|
|
|
497
507
|
};
|
|
498
508
|
window.addEventListener("mousemove", handleMouseMove);
|
|
499
509
|
window.addEventListener("mouseup", handleMouseUp);
|
|
500
|
-
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
|
|
510
|
+
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveStart, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
|
|
501
511
|
const handleResizeMouseDown = (0, import_react.useCallback)((e) => {
|
|
502
512
|
if (mode !== "designer" || !onResize || field.locked) return;
|
|
503
513
|
e.preventDefault();
|
|
504
514
|
e.stopPropagation();
|
|
515
|
+
onMoveStart?.();
|
|
505
516
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
506
517
|
if (!pageEl) return;
|
|
507
518
|
resizeStartRef.current = {
|
|
@@ -527,7 +538,7 @@ function FieldOverlayItem({
|
|
|
527
538
|
};
|
|
528
539
|
window.addEventListener("mousemove", handleMouseMove);
|
|
529
540
|
window.addEventListener("mouseup", handleMouseUp);
|
|
530
|
-
}, [field, mode, onResize, onMoveEnd]);
|
|
541
|
+
}, [field, mode, onResize, onMoveStart, onMoveEnd]);
|
|
531
542
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
532
543
|
"div",
|
|
533
544
|
{
|
|
@@ -666,6 +677,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillCon
|
|
|
666
677
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "number", min: "0", max: "9999", value: field.maxLength || 0, onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) }) })
|
|
667
678
|
] })
|
|
668
679
|
] }),
|
|
680
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
681
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
682
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: field.autoShrink || false, onChange: (e) => onUpdate(field.id, { autoShrink: e.target.checked }) }),
|
|
683
|
+
"Auto-shrink to fit"
|
|
684
|
+
] }),
|
|
685
|
+
field.autoShrink && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "Font size reduces to fit text within the field width" })
|
|
686
|
+
] }),
|
|
669
687
|
(field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
670
688
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
|
|
671
689
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-options-list", children: [
|
|
@@ -1088,13 +1106,13 @@ function useHistory(initialState, maxHistory = 50) {
|
|
|
1088
1106
|
present: initialState,
|
|
1089
1107
|
future: []
|
|
1090
1108
|
});
|
|
1091
|
-
const
|
|
1109
|
+
const batchRef = (0, import_react4.useRef)(false);
|
|
1110
|
+
const batchStartRef = (0, import_react4.useRef)(null);
|
|
1092
1111
|
const set = (0, import_react4.useCallback)((updater) => {
|
|
1093
1112
|
setState((prev) => {
|
|
1094
1113
|
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1095
1114
|
if (newPresent === prev.present) return prev;
|
|
1096
|
-
if (
|
|
1097
|
-
skipRef.current = false;
|
|
1115
|
+
if (batchRef.current) {
|
|
1098
1116
|
return { ...prev, present: newPresent };
|
|
1099
1117
|
}
|
|
1100
1118
|
return {
|
|
@@ -1105,15 +1123,31 @@ function useHistory(initialState, maxHistory = 50) {
|
|
|
1105
1123
|
});
|
|
1106
1124
|
}, [maxHistory]);
|
|
1107
1125
|
const setWithoutHistory = (0, import_react4.useCallback)((updater) => {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1126
|
+
setState((prev) => {
|
|
1127
|
+
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1128
|
+
if (newPresent === prev.present) return prev;
|
|
1129
|
+
return { ...prev, present: newPresent };
|
|
1130
|
+
});
|
|
1131
|
+
}, []);
|
|
1132
|
+
const beginBatch = (0, import_react4.useCallback)(() => {
|
|
1133
|
+
batchRef.current = true;
|
|
1134
|
+
setState((prev) => {
|
|
1135
|
+
batchStartRef.current = prev.present;
|
|
1136
|
+
return prev;
|
|
1137
|
+
});
|
|
1138
|
+
}, []);
|
|
1139
|
+
const commitBatch = (0, import_react4.useCallback)(() => {
|
|
1140
|
+
batchRef.current = false;
|
|
1141
|
+
setState((prev) => {
|
|
1142
|
+
const startState = batchStartRef.current;
|
|
1143
|
+
batchStartRef.current = null;
|
|
1144
|
+
if (!startState || startState === prev.present) return prev;
|
|
1145
|
+
return {
|
|
1146
|
+
past: [...prev.past.slice(-maxHistory), startState],
|
|
1147
|
+
present: prev.present,
|
|
1148
|
+
future: []
|
|
1149
|
+
};
|
|
1150
|
+
});
|
|
1117
1151
|
}, [maxHistory]);
|
|
1118
1152
|
const undo = (0, import_react4.useCallback)(() => {
|
|
1119
1153
|
setState((prev) => {
|
|
@@ -1141,7 +1175,8 @@ function useHistory(initialState, maxHistory = 50) {
|
|
|
1141
1175
|
state: state.present,
|
|
1142
1176
|
set,
|
|
1143
1177
|
setWithoutHistory,
|
|
1144
|
-
|
|
1178
|
+
beginBatch,
|
|
1179
|
+
commitBatch,
|
|
1145
1180
|
undo,
|
|
1146
1181
|
redo,
|
|
1147
1182
|
canUndo: state.past.length > 0,
|
|
@@ -1180,7 +1215,7 @@ function DesignerView({
|
|
|
1180
1215
|
] }) });
|
|
1181
1216
|
}
|
|
1182
1217
|
const [pages, setPages] = (0, import_react5.useState)([]);
|
|
1183
|
-
const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent,
|
|
1218
|
+
const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent, beginBatch, commitBatch, undo: undoFields, redo: redoFields, canUndo, canRedo } = useHistory(initialTemplate?.fields ?? []);
|
|
1184
1219
|
const [selectedFieldIds, setSelectedFieldIds] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
|
|
1185
1220
|
const [signerRoles, setSignerRoles] = (0, import_react5.useState)(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
1186
1221
|
const [activeRole, setActiveRole] = (0, import_react5.useState)("Sender");
|
|
@@ -1765,7 +1800,8 @@ function DesignerView({
|
|
|
1765
1800
|
onFieldMove: handleFieldMove,
|
|
1766
1801
|
onFieldResize: handleFieldResize,
|
|
1767
1802
|
onGroupMove: handleGroupMove,
|
|
1768
|
-
|
|
1803
|
+
onMoveStart: beginBatch,
|
|
1804
|
+
onMoveEnd: commitBatch,
|
|
1769
1805
|
onPageClick: handlePageClick,
|
|
1770
1806
|
onDropField: handleDropOnPage,
|
|
1771
1807
|
onMarqueeSelect: (ids) => setSelectedFieldIds(new Set(ids)),
|
|
@@ -2017,33 +2053,132 @@ var import_react7 = require("react");
|
|
|
2017
2053
|
|
|
2018
2054
|
// src/utils/pdfFiller.ts
|
|
2019
2055
|
var import_pdf_lib = require("pdf-lib");
|
|
2056
|
+
|
|
2057
|
+
// src/utils/formulaResolver.ts
|
|
2058
|
+
var BUILTIN_TRANSFORMS = {
|
|
2059
|
+
// Date transforms (expects a parseable date string)
|
|
2060
|
+
month: (v) => {
|
|
2061
|
+
const d = new Date(v);
|
|
2062
|
+
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
|
|
2063
|
+
},
|
|
2064
|
+
month2: (v) => {
|
|
2065
|
+
const d = new Date(v);
|
|
2066
|
+
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
|
|
2067
|
+
},
|
|
2068
|
+
monthname: (v) => {
|
|
2069
|
+
const d = new Date(v);
|
|
2070
|
+
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
|
|
2071
|
+
},
|
|
2072
|
+
monthshort: (v) => {
|
|
2073
|
+
const d = new Date(v);
|
|
2074
|
+
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
|
|
2075
|
+
},
|
|
2076
|
+
day: (v) => {
|
|
2077
|
+
const d = new Date(v);
|
|
2078
|
+
return isNaN(d.getTime()) ? "" : String(d.getDate());
|
|
2079
|
+
},
|
|
2080
|
+
day2: (v) => {
|
|
2081
|
+
const d = new Date(v);
|
|
2082
|
+
return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
|
|
2083
|
+
},
|
|
2084
|
+
year: (v) => {
|
|
2085
|
+
const d = new Date(v);
|
|
2086
|
+
return isNaN(d.getTime()) ? "" : String(d.getFullYear());
|
|
2087
|
+
},
|
|
2088
|
+
year2: (v) => {
|
|
2089
|
+
const d = new Date(v);
|
|
2090
|
+
return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
|
|
2091
|
+
},
|
|
2092
|
+
// String transforms
|
|
2093
|
+
upper: (v) => v.toUpperCase(),
|
|
2094
|
+
lower: (v) => v.toLowerCase(),
|
|
2095
|
+
trim: (v) => v.trim(),
|
|
2096
|
+
first: (v) => v.split(/\s+/)[0] || "",
|
|
2097
|
+
last: (v) => {
|
|
2098
|
+
const parts = v.split(/\s+/);
|
|
2099
|
+
return parts[parts.length - 1] || "";
|
|
2100
|
+
},
|
|
2101
|
+
initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
|
|
2102
|
+
// Numeric / substring
|
|
2103
|
+
last4: (v) => v.slice(-4),
|
|
2104
|
+
last2: (v) => v.slice(-2),
|
|
2105
|
+
first4: (v) => v.slice(0, 4),
|
|
2106
|
+
first2: (v) => v.slice(0, 2),
|
|
2107
|
+
digits: (v) => v.replace(/\D/g, ""),
|
|
2108
|
+
number: (v) => {
|
|
2109
|
+
const n = parseFloat(v);
|
|
2110
|
+
return isNaN(n) ? "" : String(n);
|
|
2111
|
+
},
|
|
2112
|
+
currency: (v) => {
|
|
2113
|
+
const n = parseFloat(v.replace(/[^0-9.-]/g, ""));
|
|
2114
|
+
return isNaN(n) ? "" : `$${n.toFixed(2)}`;
|
|
2115
|
+
}
|
|
2116
|
+
};
|
|
2117
|
+
var FORMULA_RE = /\{\{(.+?)\}\}/g;
|
|
2118
|
+
var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
|
|
2119
|
+
function resolveFormula(formula, fields, customTransforms) {
|
|
2120
|
+
const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
|
|
2121
|
+
return formula.replace(FORMULA_RE, (_match, expr) => {
|
|
2122
|
+
const pipeMatch = expr.match(PIPE_RE);
|
|
2123
|
+
const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
|
|
2124
|
+
const transformName = pipeMatch ? pipeMatch[2].trim() : null;
|
|
2125
|
+
const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
|
|
2126
|
+
if (!sourceField) return "";
|
|
2127
|
+
const rawValue = sourceField.value || "";
|
|
2128
|
+
if (!transformName) return rawValue;
|
|
2129
|
+
const fn = transforms[transformName];
|
|
2130
|
+
if (!fn) return rawValue;
|
|
2131
|
+
try {
|
|
2132
|
+
return fn(rawValue);
|
|
2133
|
+
} catch {
|
|
2134
|
+
return rawValue;
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
function resolveAllFormulas(fields, customTransforms) {
|
|
2139
|
+
return fields.map((f) => {
|
|
2140
|
+
if (!f.formula) return f;
|
|
2141
|
+
const computed = resolveFormula(f.formula, fields, customTransforms);
|
|
2142
|
+
return computed !== f.value ? { ...f, value: computed } : f;
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
// src/utils/pdfFiller.ts
|
|
2020
2147
|
var FONT_MAP = {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2148
|
+
Helvetica: import_pdf_lib.StandardFonts.Helvetica,
|
|
2149
|
+
Courier: import_pdf_lib.StandardFonts.Courier,
|
|
2150
|
+
TimesRoman: import_pdf_lib.StandardFonts.TimesRoman
|
|
2024
2151
|
};
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
const stdFont = FONT_MAP[key] || import_pdf_lib.StandardFonts.Helvetica;
|
|
2038
|
-
fontCache.set(key, await pdfDoc.embedFont(stdFont));
|
|
2039
|
-
}
|
|
2040
|
-
const pages = pdfDoc.getPages();
|
|
2041
|
-
function hexToRgb(hex) {
|
|
2042
|
-
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
2043
|
-
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
2044
|
-
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
2045
|
-
return (0, import_pdf_lib.rgb)(r, g, b);
|
|
2152
|
+
var US_LETTER = [612, 792];
|
|
2153
|
+
var US_LEGAL = [612, 1008];
|
|
2154
|
+
var A4 = [595.28, 841.89];
|
|
2155
|
+
var DEFAULT_CALIBRATION = {
|
|
2156
|
+
xOffset: 0,
|
|
2157
|
+
yOffset: 0,
|
|
2158
|
+
xScale: 1,
|
|
2159
|
+
yScale: 1
|
|
2160
|
+
};
|
|
2161
|
+
function applyCalibration(fields, calibration, pageSize = US_LETTER) {
|
|
2162
|
+
if (calibration.xOffset === 0 && calibration.yOffset === 0 && calibration.xScale === 1 && calibration.yScale === 1) {
|
|
2163
|
+
return fields;
|
|
2046
2164
|
}
|
|
2165
|
+
const xPctOff = calibration.xOffset / pageSize[0] * 100;
|
|
2166
|
+
const yPctOff = calibration.yOffset / pageSize[1] * 100;
|
|
2167
|
+
return fields.map((f) => ({
|
|
2168
|
+
...f,
|
|
2169
|
+
x: f.x * calibration.xScale + xPctOff,
|
|
2170
|
+
y: f.y * calibration.yScale + yPctOff,
|
|
2171
|
+
width: f.width * calibration.xScale,
|
|
2172
|
+
height: f.height * calibration.yScale
|
|
2173
|
+
}));
|
|
2174
|
+
}
|
|
2175
|
+
function hexToRgb(hex) {
|
|
2176
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
2177
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
2178
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
2179
|
+
return (0, import_pdf_lib.rgb)(r, g, b);
|
|
2180
|
+
}
|
|
2181
|
+
async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
|
|
2047
2182
|
for (const field of fields) {
|
|
2048
2183
|
const page = pages[field.page];
|
|
2049
2184
|
if (!page) continue;
|
|
@@ -2084,37 +2219,32 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
2084
2219
|
}
|
|
2085
2220
|
} else if (field.type === "signature" || field.type === "initials") {
|
|
2086
2221
|
if (field.value.startsWith("data:image/png")) {
|
|
2087
|
-
const
|
|
2088
|
-
|
|
2089
|
-
(c) => c.charCodeAt(0)
|
|
2090
|
-
);
|
|
2091
|
-
const pngImage = await pdfDoc.embedPng(pngBytes);
|
|
2092
|
-
page.drawImage(pngImage, {
|
|
2093
|
-
x,
|
|
2094
|
-
y,
|
|
2095
|
-
width: w,
|
|
2096
|
-
height: h
|
|
2097
|
-
});
|
|
2222
|
+
const img = await getSignature(field.value);
|
|
2223
|
+
page.drawImage(img, { x, y, width: w, height: h });
|
|
2098
2224
|
}
|
|
2099
2225
|
} else {
|
|
2100
|
-
const font =
|
|
2226
|
+
const font = await getFont(field.fontFamily || "Helvetica");
|
|
2101
2227
|
const spacing = field.letterSpacing || 0;
|
|
2102
|
-
const textWidthAtSize = (text, size) =>
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
if (textWidthAtSize(field.value, fontSize) <= w - padding) break;
|
|
2111
|
-
fontSize -= 0.5;
|
|
2228
|
+
const textWidthAtSize = (text, size) => font.widthOfTextAtSize(text, size) + spacing * (text.length - 1);
|
|
2229
|
+
let fontSize = Math.min(field.fontSize, h * 0.7);
|
|
2230
|
+
if (field.autoShrink) {
|
|
2231
|
+
const padding = 4;
|
|
2232
|
+
while (fontSize > 4) {
|
|
2233
|
+
if (textWidthAtSize(field.value, fontSize) <= w - padding) break;
|
|
2234
|
+
fontSize -= 0.5;
|
|
2235
|
+
}
|
|
2112
2236
|
}
|
|
2113
2237
|
if (spacing > 0) {
|
|
2114
2238
|
let cx = x + 2;
|
|
2115
2239
|
const cy = y + h * 0.3;
|
|
2116
2240
|
for (const char of field.value) {
|
|
2117
|
-
page.drawText(char, {
|
|
2241
|
+
page.drawText(char, {
|
|
2242
|
+
x: cx,
|
|
2243
|
+
y: cy,
|
|
2244
|
+
size: fontSize,
|
|
2245
|
+
font,
|
|
2246
|
+
color: inkColor
|
|
2247
|
+
});
|
|
2118
2248
|
cx += font.widthOfTextAtSize(char, fontSize) + spacing;
|
|
2119
2249
|
}
|
|
2120
2250
|
} else {
|
|
@@ -2128,7 +2258,98 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
2128
2258
|
}
|
|
2129
2259
|
}
|
|
2130
2260
|
}
|
|
2131
|
-
|
|
2261
|
+
}
|
|
2262
|
+
async function generateFilledPdf(opts) {
|
|
2263
|
+
const builder = await createPdfBuilder({ pageSize: opts.pageSize });
|
|
2264
|
+
await builder.addRecord(opts);
|
|
2265
|
+
return builder.save();
|
|
2266
|
+
}
|
|
2267
|
+
async function createPdfBuilder(defaults = {}) {
|
|
2268
|
+
const doc = await import_pdf_lib.PDFDocument.create();
|
|
2269
|
+
const fontCache = /* @__PURE__ */ new Map();
|
|
2270
|
+
const embeddedPagesByUrl = /* @__PURE__ */ new Map();
|
|
2271
|
+
const signatureCache = /* @__PURE__ */ new Map();
|
|
2272
|
+
async function getFont(family) {
|
|
2273
|
+
let f = fontCache.get(family);
|
|
2274
|
+
if (!f) {
|
|
2275
|
+
const stdName = FONT_MAP[family] || import_pdf_lib.StandardFonts.Helvetica;
|
|
2276
|
+
f = await doc.embedFont(stdName);
|
|
2277
|
+
fontCache.set(family, f);
|
|
2278
|
+
}
|
|
2279
|
+
return f;
|
|
2280
|
+
}
|
|
2281
|
+
async function ensureFonts(fields) {
|
|
2282
|
+
const keys = new Set(fields.map((f) => f.fontFamily || "Helvetica"));
|
|
2283
|
+
for (const k of keys) await getFont(k);
|
|
2284
|
+
}
|
|
2285
|
+
async function embedSource(source) {
|
|
2286
|
+
if (typeof source === "string") {
|
|
2287
|
+
let pages = embeddedPagesByUrl.get(source);
|
|
2288
|
+
if (!pages) {
|
|
2289
|
+
const res = await fetch(source);
|
|
2290
|
+
const bytes = await res.arrayBuffer();
|
|
2291
|
+
const srcDoc2 = await import_pdf_lib.PDFDocument.load(bytes);
|
|
2292
|
+
pages = await Promise.all(
|
|
2293
|
+
srcDoc2.getPages().map((p) => doc.embedPage(p))
|
|
2294
|
+
);
|
|
2295
|
+
embeddedPagesByUrl.set(source, pages);
|
|
2296
|
+
}
|
|
2297
|
+
return pages;
|
|
2298
|
+
}
|
|
2299
|
+
const srcDoc = await import_pdf_lib.PDFDocument.load(source);
|
|
2300
|
+
return Promise.all(srcDoc.getPages().map((p) => doc.embedPage(p)));
|
|
2301
|
+
}
|
|
2302
|
+
async function getSignature(dataUrl) {
|
|
2303
|
+
let img = signatureCache.get(dataUrl);
|
|
2304
|
+
if (!img) {
|
|
2305
|
+
const b64 = dataUrl.split(",")[1] ?? "";
|
|
2306
|
+
const pngBytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
2307
|
+
img = await doc.embedPng(pngBytes);
|
|
2308
|
+
signatureCache.set(dataUrl, img);
|
|
2309
|
+
}
|
|
2310
|
+
return img;
|
|
2311
|
+
}
|
|
2312
|
+
return {
|
|
2313
|
+
doc,
|
|
2314
|
+
async addRecord(opts) {
|
|
2315
|
+
const pageSize = opts.pageSize ?? defaults.pageSize;
|
|
2316
|
+
let fields = opts.resolveFormulas ? resolveAllFormulas(opts.fields, opts.customTransforms) : opts.fields;
|
|
2317
|
+
if (opts.calibration) {
|
|
2318
|
+
fields = applyCalibration(
|
|
2319
|
+
fields,
|
|
2320
|
+
opts.calibration,
|
|
2321
|
+
pageSize ?? US_LETTER
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
await ensureFonts(fields);
|
|
2325
|
+
let sourcePages = [];
|
|
2326
|
+
let pageCount;
|
|
2327
|
+
if (opts.pdfSource !== null) {
|
|
2328
|
+
sourcePages = await embedSource(opts.pdfSource);
|
|
2329
|
+
pageCount = sourcePages.length;
|
|
2330
|
+
} else {
|
|
2331
|
+
pageCount = opts.pageCount ?? 1;
|
|
2332
|
+
}
|
|
2333
|
+
const newPages = [];
|
|
2334
|
+
for (let i = 0; i < pageCount; i++) {
|
|
2335
|
+
const size = pageSize ? pageSize : sourcePages[i] ? [sourcePages[i].width, sourcePages[i].height] : US_LETTER;
|
|
2336
|
+
const page = doc.addPage(size);
|
|
2337
|
+
newPages.push(page);
|
|
2338
|
+
if (sourcePages[i]) {
|
|
2339
|
+
page.drawPage(sourcePages[i], {
|
|
2340
|
+
x: 0,
|
|
2341
|
+
y: 0,
|
|
2342
|
+
width: size[0],
|
|
2343
|
+
height: size[1]
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
await renderFieldsOnPages(newPages, fields, getFont, getSignature);
|
|
2348
|
+
},
|
|
2349
|
+
async save() {
|
|
2350
|
+
return doc.save();
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2132
2353
|
}
|
|
2133
2354
|
function downloadPdf(bytes, filename) {
|
|
2134
2355
|
const blob = new Blob([bytes.slice().buffer], { type: "application/pdf" });
|
|
@@ -2240,95 +2461,6 @@ function FieldNavigator({
|
|
|
2240
2461
|
] });
|
|
2241
2462
|
}
|
|
2242
2463
|
|
|
2243
|
-
// src/utils/formulaResolver.ts
|
|
2244
|
-
var BUILTIN_TRANSFORMS = {
|
|
2245
|
-
// Date transforms (expects a parseable date string)
|
|
2246
|
-
month: (v) => {
|
|
2247
|
-
const d = new Date(v);
|
|
2248
|
-
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
|
|
2249
|
-
},
|
|
2250
|
-
month2: (v) => {
|
|
2251
|
-
const d = new Date(v);
|
|
2252
|
-
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
|
|
2253
|
-
},
|
|
2254
|
-
monthname: (v) => {
|
|
2255
|
-
const d = new Date(v);
|
|
2256
|
-
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
|
|
2257
|
-
},
|
|
2258
|
-
monthshort: (v) => {
|
|
2259
|
-
const d = new Date(v);
|
|
2260
|
-
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
|
|
2261
|
-
},
|
|
2262
|
-
day: (v) => {
|
|
2263
|
-
const d = new Date(v);
|
|
2264
|
-
return isNaN(d.getTime()) ? "" : String(d.getDate());
|
|
2265
|
-
},
|
|
2266
|
-
day2: (v) => {
|
|
2267
|
-
const d = new Date(v);
|
|
2268
|
-
return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
|
|
2269
|
-
},
|
|
2270
|
-
year: (v) => {
|
|
2271
|
-
const d = new Date(v);
|
|
2272
|
-
return isNaN(d.getTime()) ? "" : String(d.getFullYear());
|
|
2273
|
-
},
|
|
2274
|
-
year2: (v) => {
|
|
2275
|
-
const d = new Date(v);
|
|
2276
|
-
return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
|
|
2277
|
-
},
|
|
2278
|
-
// String transforms
|
|
2279
|
-
upper: (v) => v.toUpperCase(),
|
|
2280
|
-
lower: (v) => v.toLowerCase(),
|
|
2281
|
-
trim: (v) => v.trim(),
|
|
2282
|
-
first: (v) => v.split(/\s+/)[0] || "",
|
|
2283
|
-
last: (v) => {
|
|
2284
|
-
const parts = v.split(/\s+/);
|
|
2285
|
-
return parts[parts.length - 1] || "";
|
|
2286
|
-
},
|
|
2287
|
-
initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
|
|
2288
|
-
// Numeric / substring
|
|
2289
|
-
last4: (v) => v.slice(-4),
|
|
2290
|
-
last2: (v) => v.slice(-2),
|
|
2291
|
-
first4: (v) => v.slice(0, 4),
|
|
2292
|
-
first2: (v) => v.slice(0, 2),
|
|
2293
|
-
digits: (v) => v.replace(/\D/g, ""),
|
|
2294
|
-
number: (v) => {
|
|
2295
|
-
const n = parseFloat(v);
|
|
2296
|
-
return isNaN(n) ? "" : String(n);
|
|
2297
|
-
},
|
|
2298
|
-
currency: (v) => {
|
|
2299
|
-
const n = parseFloat(v.replace(/[^0-9.-]/g, ""));
|
|
2300
|
-
return isNaN(n) ? "" : `$${n.toFixed(2)}`;
|
|
2301
|
-
}
|
|
2302
|
-
};
|
|
2303
|
-
var FORMULA_RE = /\{\{(.+?)\}\}/g;
|
|
2304
|
-
var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
|
|
2305
|
-
function resolveFormula(formula, fields, customTransforms) {
|
|
2306
|
-
const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
|
|
2307
|
-
return formula.replace(FORMULA_RE, (_match, expr) => {
|
|
2308
|
-
const pipeMatch = expr.match(PIPE_RE);
|
|
2309
|
-
const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
|
|
2310
|
-
const transformName = pipeMatch ? pipeMatch[2].trim() : null;
|
|
2311
|
-
const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
|
|
2312
|
-
if (!sourceField) return "";
|
|
2313
|
-
const rawValue = sourceField.value || "";
|
|
2314
|
-
if (!transformName) return rawValue;
|
|
2315
|
-
const fn = transforms[transformName];
|
|
2316
|
-
if (!fn) return rawValue;
|
|
2317
|
-
try {
|
|
2318
|
-
return fn(rawValue);
|
|
2319
|
-
} catch {
|
|
2320
|
-
return rawValue;
|
|
2321
|
-
}
|
|
2322
|
-
});
|
|
2323
|
-
}
|
|
2324
|
-
function resolveAllFormulas(fields, customTransforms) {
|
|
2325
|
-
return fields.map((f) => {
|
|
2326
|
-
if (!f.formula) return f;
|
|
2327
|
-
const computed = resolveFormula(f.formula, fields, customTransforms);
|
|
2328
|
-
return computed !== f.value ? { ...f, value: computed } : f;
|
|
2329
|
-
});
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
2464
|
// src/components/pdf-builder/SignerView.tsx
|
|
2333
2465
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2334
2466
|
function SignerView({
|
|
@@ -2546,7 +2678,7 @@ function SignerView({
|
|
|
2546
2678
|
let pdfBytes;
|
|
2547
2679
|
if (includeAuditTrail) {
|
|
2548
2680
|
const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
|
|
2549
|
-
const basePdf = await generateFilledPdf(pdfSource, finalFields);
|
|
2681
|
+
const basePdf = await generateFilledPdf({ pdfSource, fields: finalFields });
|
|
2550
2682
|
const pdfDoc = await PDFDocument2.load(basePdf);
|
|
2551
2683
|
const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
|
|
2552
2684
|
const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
|
|
@@ -2563,7 +2695,7 @@ function SignerView({
|
|
|
2563
2695
|
}
|
|
2564
2696
|
pdfBytes = await pdfDoc.save();
|
|
2565
2697
|
} else {
|
|
2566
|
-
pdfBytes = await generateFilledPdf(pdfSource, finalFields);
|
|
2698
|
+
pdfBytes = await generateFilledPdf({ pdfSource, fields: finalFields });
|
|
2567
2699
|
}
|
|
2568
2700
|
const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
|
|
2569
2701
|
if (onExport && exportFormat) {
|
|
@@ -2886,7 +3018,9 @@ function SignerRoleSelector({
|
|
|
2886
3018
|
}
|
|
2887
3019
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2888
3020
|
0 && (module.exports = {
|
|
3021
|
+
A4,
|
|
2889
3022
|
BUILTIN_TRANSFORMS,
|
|
3023
|
+
DEFAULT_CALIBRATION,
|
|
2890
3024
|
DEFAULT_SIGNER_ROLES,
|
|
2891
3025
|
DesignerView,
|
|
2892
3026
|
FIELD_DEFAULTS,
|
|
@@ -2898,7 +3032,11 @@ function SignerRoleSelector({
|
|
|
2898
3032
|
SignatureCanvas,
|
|
2899
3033
|
SignerRoleSelector,
|
|
2900
3034
|
SignerView,
|
|
3035
|
+
US_LEGAL,
|
|
3036
|
+
US_LETTER,
|
|
3037
|
+
applyCalibration,
|
|
2901
3038
|
createField,
|
|
3039
|
+
createPdfBuilder,
|
|
2902
3040
|
downloadPdf,
|
|
2903
3041
|
generateFilledPdf,
|
|
2904
3042
|
generateId,
|