@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.mjs
CHANGED
|
@@ -165,6 +165,7 @@ function PdfViewer({
|
|
|
165
165
|
onPageClick,
|
|
166
166
|
onDropField,
|
|
167
167
|
onGroupMove,
|
|
168
|
+
onMoveStart,
|
|
168
169
|
onMoveEnd,
|
|
169
170
|
mode,
|
|
170
171
|
currentSigner,
|
|
@@ -274,6 +275,7 @@ function PdfViewer({
|
|
|
274
275
|
onMove: onFieldMove,
|
|
275
276
|
onResize: onFieldResize,
|
|
276
277
|
onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
|
|
278
|
+
onMoveStart,
|
|
277
279
|
onMoveEnd,
|
|
278
280
|
otherFields: mode === "designer" ? pageFields.filter((f) => f.id !== field.id && !selectedFieldIds.has(f.id)) : void 0,
|
|
279
281
|
setGuides: mode === "designer" ? setGuides : void 0,
|
|
@@ -363,6 +365,7 @@ function FieldOverlayItem({
|
|
|
363
365
|
onMove,
|
|
364
366
|
onResize,
|
|
365
367
|
onGroupMove,
|
|
368
|
+
onMoveStart,
|
|
366
369
|
onMoveEnd,
|
|
367
370
|
otherFields,
|
|
368
371
|
setGuides,
|
|
@@ -392,6 +395,7 @@ function FieldOverlayItem({
|
|
|
392
395
|
onSelect(e);
|
|
393
396
|
}
|
|
394
397
|
if (field.locked) return;
|
|
398
|
+
onMoveStart?.();
|
|
395
399
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
396
400
|
if (!pageEl) return;
|
|
397
401
|
didDragRef.current = false;
|
|
@@ -440,11 +444,12 @@ function FieldOverlayItem({
|
|
|
440
444
|
};
|
|
441
445
|
window.addEventListener("mousemove", handleMouseMove);
|
|
442
446
|
window.addEventListener("mouseup", handleMouseUp);
|
|
443
|
-
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
|
|
447
|
+
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveStart, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
|
|
444
448
|
const handleResizeMouseDown = useCallback((e) => {
|
|
445
449
|
if (mode !== "designer" || !onResize || field.locked) return;
|
|
446
450
|
e.preventDefault();
|
|
447
451
|
e.stopPropagation();
|
|
452
|
+
onMoveStart?.();
|
|
448
453
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
449
454
|
if (!pageEl) return;
|
|
450
455
|
resizeStartRef.current = {
|
|
@@ -470,7 +475,7 @@ function FieldOverlayItem({
|
|
|
470
475
|
};
|
|
471
476
|
window.addEventListener("mousemove", handleMouseMove);
|
|
472
477
|
window.addEventListener("mouseup", handleMouseUp);
|
|
473
|
-
}, [field, mode, onResize, onMoveEnd]);
|
|
478
|
+
}, [field, mode, onResize, onMoveStart, onMoveEnd]);
|
|
474
479
|
return /* @__PURE__ */ jsxs(
|
|
475
480
|
"div",
|
|
476
481
|
{
|
|
@@ -609,6 +614,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillCon
|
|
|
609
614
|
/* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.maxLength || 0, onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) }) })
|
|
610
615
|
] })
|
|
611
616
|
] }),
|
|
617
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
618
|
+
/* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
|
|
619
|
+
/* @__PURE__ */ jsx2("input", { type: "checkbox", checked: field.autoShrink || false, onChange: (e) => onUpdate(field.id, { autoShrink: e.target.checked }) }),
|
|
620
|
+
"Auto-shrink to fit"
|
|
621
|
+
] }),
|
|
622
|
+
field.autoShrink && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Font size reduces to fit text within the field width" })
|
|
623
|
+
] }),
|
|
612
624
|
(field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
613
625
|
/* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
|
|
614
626
|
/* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
|
|
@@ -1031,13 +1043,13 @@ function useHistory(initialState, maxHistory = 50) {
|
|
|
1031
1043
|
present: initialState,
|
|
1032
1044
|
future: []
|
|
1033
1045
|
});
|
|
1034
|
-
const
|
|
1046
|
+
const batchRef = useRef3(false);
|
|
1047
|
+
const batchStartRef = useRef3(null);
|
|
1035
1048
|
const set = useCallback3((updater) => {
|
|
1036
1049
|
setState((prev) => {
|
|
1037
1050
|
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1038
1051
|
if (newPresent === prev.present) return prev;
|
|
1039
|
-
if (
|
|
1040
|
-
skipRef.current = false;
|
|
1052
|
+
if (batchRef.current) {
|
|
1041
1053
|
return { ...prev, present: newPresent };
|
|
1042
1054
|
}
|
|
1043
1055
|
return {
|
|
@@ -1048,15 +1060,31 @@ function useHistory(initialState, maxHistory = 50) {
|
|
|
1048
1060
|
});
|
|
1049
1061
|
}, [maxHistory]);
|
|
1050
1062
|
const setWithoutHistory = useCallback3((updater) => {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1063
|
+
setState((prev) => {
|
|
1064
|
+
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1065
|
+
if (newPresent === prev.present) return prev;
|
|
1066
|
+
return { ...prev, present: newPresent };
|
|
1067
|
+
});
|
|
1068
|
+
}, []);
|
|
1069
|
+
const beginBatch = useCallback3(() => {
|
|
1070
|
+
batchRef.current = true;
|
|
1071
|
+
setState((prev) => {
|
|
1072
|
+
batchStartRef.current = prev.present;
|
|
1073
|
+
return prev;
|
|
1074
|
+
});
|
|
1075
|
+
}, []);
|
|
1076
|
+
const commitBatch = useCallback3(() => {
|
|
1077
|
+
batchRef.current = false;
|
|
1078
|
+
setState((prev) => {
|
|
1079
|
+
const startState = batchStartRef.current;
|
|
1080
|
+
batchStartRef.current = null;
|
|
1081
|
+
if (!startState || startState === prev.present) return prev;
|
|
1082
|
+
return {
|
|
1083
|
+
past: [...prev.past.slice(-maxHistory), startState],
|
|
1084
|
+
present: prev.present,
|
|
1085
|
+
future: []
|
|
1086
|
+
};
|
|
1087
|
+
});
|
|
1060
1088
|
}, [maxHistory]);
|
|
1061
1089
|
const undo = useCallback3(() => {
|
|
1062
1090
|
setState((prev) => {
|
|
@@ -1084,7 +1112,8 @@ function useHistory(initialState, maxHistory = 50) {
|
|
|
1084
1112
|
state: state.present,
|
|
1085
1113
|
set,
|
|
1086
1114
|
setWithoutHistory,
|
|
1087
|
-
|
|
1115
|
+
beginBatch,
|
|
1116
|
+
commitBatch,
|
|
1088
1117
|
undo,
|
|
1089
1118
|
redo,
|
|
1090
1119
|
canUndo: state.past.length > 0,
|
|
@@ -1123,7 +1152,7 @@ function DesignerView({
|
|
|
1123
1152
|
] }) });
|
|
1124
1153
|
}
|
|
1125
1154
|
const [pages, setPages] = useState5([]);
|
|
1126
|
-
const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent,
|
|
1155
|
+
const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent, beginBatch, commitBatch, undo: undoFields, redo: redoFields, canUndo, canRedo } = useHistory(initialTemplate?.fields ?? []);
|
|
1127
1156
|
const [selectedFieldIds, setSelectedFieldIds] = useState5(/* @__PURE__ */ new Set());
|
|
1128
1157
|
const [signerRoles, setSignerRoles] = useState5(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
1129
1158
|
const [activeRole, setActiveRole] = useState5("Sender");
|
|
@@ -1708,7 +1737,8 @@ function DesignerView({
|
|
|
1708
1737
|
onFieldMove: handleFieldMove,
|
|
1709
1738
|
onFieldResize: handleFieldResize,
|
|
1710
1739
|
onGroupMove: handleGroupMove,
|
|
1711
|
-
|
|
1740
|
+
onMoveStart: beginBatch,
|
|
1741
|
+
onMoveEnd: commitBatch,
|
|
1712
1742
|
onPageClick: handlePageClick,
|
|
1713
1743
|
onDropField: handleDropOnPage,
|
|
1714
1744
|
onMarqueeSelect: (ids) => setSelectedFieldIds(new Set(ids)),
|
|
@@ -1959,34 +1989,137 @@ function DesignerView({
|
|
|
1959
1989
|
import { useState as useState7, useCallback as useCallback5, useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
1960
1990
|
|
|
1961
1991
|
// src/utils/pdfFiller.ts
|
|
1962
|
-
import {
|
|
1992
|
+
import {
|
|
1993
|
+
PDFDocument,
|
|
1994
|
+
rgb,
|
|
1995
|
+
StandardFonts
|
|
1996
|
+
} from "pdf-lib";
|
|
1997
|
+
|
|
1998
|
+
// src/utils/formulaResolver.ts
|
|
1999
|
+
var BUILTIN_TRANSFORMS = {
|
|
2000
|
+
// Date transforms (expects a parseable date string)
|
|
2001
|
+
month: (v) => {
|
|
2002
|
+
const d = new Date(v);
|
|
2003
|
+
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
|
|
2004
|
+
},
|
|
2005
|
+
month2: (v) => {
|
|
2006
|
+
const d = new Date(v);
|
|
2007
|
+
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
|
|
2008
|
+
},
|
|
2009
|
+
monthname: (v) => {
|
|
2010
|
+
const d = new Date(v);
|
|
2011
|
+
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
|
|
2012
|
+
},
|
|
2013
|
+
monthshort: (v) => {
|
|
2014
|
+
const d = new Date(v);
|
|
2015
|
+
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
|
|
2016
|
+
},
|
|
2017
|
+
day: (v) => {
|
|
2018
|
+
const d = new Date(v);
|
|
2019
|
+
return isNaN(d.getTime()) ? "" : String(d.getDate());
|
|
2020
|
+
},
|
|
2021
|
+
day2: (v) => {
|
|
2022
|
+
const d = new Date(v);
|
|
2023
|
+
return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
|
|
2024
|
+
},
|
|
2025
|
+
year: (v) => {
|
|
2026
|
+
const d = new Date(v);
|
|
2027
|
+
return isNaN(d.getTime()) ? "" : String(d.getFullYear());
|
|
2028
|
+
},
|
|
2029
|
+
year2: (v) => {
|
|
2030
|
+
const d = new Date(v);
|
|
2031
|
+
return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
|
|
2032
|
+
},
|
|
2033
|
+
// String transforms
|
|
2034
|
+
upper: (v) => v.toUpperCase(),
|
|
2035
|
+
lower: (v) => v.toLowerCase(),
|
|
2036
|
+
trim: (v) => v.trim(),
|
|
2037
|
+
first: (v) => v.split(/\s+/)[0] || "",
|
|
2038
|
+
last: (v) => {
|
|
2039
|
+
const parts = v.split(/\s+/);
|
|
2040
|
+
return parts[parts.length - 1] || "";
|
|
2041
|
+
},
|
|
2042
|
+
initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
|
|
2043
|
+
// Numeric / substring
|
|
2044
|
+
last4: (v) => v.slice(-4),
|
|
2045
|
+
last2: (v) => v.slice(-2),
|
|
2046
|
+
first4: (v) => v.slice(0, 4),
|
|
2047
|
+
first2: (v) => v.slice(0, 2),
|
|
2048
|
+
digits: (v) => v.replace(/\D/g, ""),
|
|
2049
|
+
number: (v) => {
|
|
2050
|
+
const n = parseFloat(v);
|
|
2051
|
+
return isNaN(n) ? "" : String(n);
|
|
2052
|
+
},
|
|
2053
|
+
currency: (v) => {
|
|
2054
|
+
const n = parseFloat(v.replace(/[^0-9.-]/g, ""));
|
|
2055
|
+
return isNaN(n) ? "" : `$${n.toFixed(2)}`;
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
var FORMULA_RE = /\{\{(.+?)\}\}/g;
|
|
2059
|
+
var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
|
|
2060
|
+
function resolveFormula(formula, fields, customTransforms) {
|
|
2061
|
+
const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
|
|
2062
|
+
return formula.replace(FORMULA_RE, (_match, expr) => {
|
|
2063
|
+
const pipeMatch = expr.match(PIPE_RE);
|
|
2064
|
+
const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
|
|
2065
|
+
const transformName = pipeMatch ? pipeMatch[2].trim() : null;
|
|
2066
|
+
const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
|
|
2067
|
+
if (!sourceField) return "";
|
|
2068
|
+
const rawValue = sourceField.value || "";
|
|
2069
|
+
if (!transformName) return rawValue;
|
|
2070
|
+
const fn = transforms[transformName];
|
|
2071
|
+
if (!fn) return rawValue;
|
|
2072
|
+
try {
|
|
2073
|
+
return fn(rawValue);
|
|
2074
|
+
} catch {
|
|
2075
|
+
return rawValue;
|
|
2076
|
+
}
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
function resolveAllFormulas(fields, customTransforms) {
|
|
2080
|
+
return fields.map((f) => {
|
|
2081
|
+
if (!f.formula) return f;
|
|
2082
|
+
const computed = resolveFormula(f.formula, fields, customTransforms);
|
|
2083
|
+
return computed !== f.value ? { ...f, value: computed } : f;
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// src/utils/pdfFiller.ts
|
|
1963
2088
|
var FONT_MAP = {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2089
|
+
Helvetica: StandardFonts.Helvetica,
|
|
2090
|
+
Courier: StandardFonts.Courier,
|
|
2091
|
+
TimesRoman: StandardFonts.TimesRoman
|
|
1967
2092
|
};
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
const stdFont = FONT_MAP[key] || StandardFonts.Helvetica;
|
|
1981
|
-
fontCache.set(key, await pdfDoc.embedFont(stdFont));
|
|
1982
|
-
}
|
|
1983
|
-
const pages = pdfDoc.getPages();
|
|
1984
|
-
function hexToRgb(hex) {
|
|
1985
|
-
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
1986
|
-
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
1987
|
-
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
1988
|
-
return rgb(r, g, b);
|
|
2093
|
+
var US_LETTER = [612, 792];
|
|
2094
|
+
var US_LEGAL = [612, 1008];
|
|
2095
|
+
var A4 = [595.28, 841.89];
|
|
2096
|
+
var DEFAULT_CALIBRATION = {
|
|
2097
|
+
xOffset: 0,
|
|
2098
|
+
yOffset: 0,
|
|
2099
|
+
xScale: 1,
|
|
2100
|
+
yScale: 1
|
|
2101
|
+
};
|
|
2102
|
+
function applyCalibration(fields, calibration, pageSize = US_LETTER) {
|
|
2103
|
+
if (calibration.xOffset === 0 && calibration.yOffset === 0 && calibration.xScale === 1 && calibration.yScale === 1) {
|
|
2104
|
+
return fields;
|
|
1989
2105
|
}
|
|
2106
|
+
const xPctOff = calibration.xOffset / pageSize[0] * 100;
|
|
2107
|
+
const yPctOff = calibration.yOffset / pageSize[1] * 100;
|
|
2108
|
+
return fields.map((f) => ({
|
|
2109
|
+
...f,
|
|
2110
|
+
x: f.x * calibration.xScale + xPctOff,
|
|
2111
|
+
y: f.y * calibration.yScale + yPctOff,
|
|
2112
|
+
width: f.width * calibration.xScale,
|
|
2113
|
+
height: f.height * calibration.yScale
|
|
2114
|
+
}));
|
|
2115
|
+
}
|
|
2116
|
+
function hexToRgb(hex) {
|
|
2117
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
2118
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
2119
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
2120
|
+
return rgb(r, g, b);
|
|
2121
|
+
}
|
|
2122
|
+
async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
|
|
1990
2123
|
for (const field of fields) {
|
|
1991
2124
|
const page = pages[field.page];
|
|
1992
2125
|
if (!page) continue;
|
|
@@ -2027,37 +2160,32 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
2027
2160
|
}
|
|
2028
2161
|
} else if (field.type === "signature" || field.type === "initials") {
|
|
2029
2162
|
if (field.value.startsWith("data:image/png")) {
|
|
2030
|
-
const
|
|
2031
|
-
|
|
2032
|
-
(c) => c.charCodeAt(0)
|
|
2033
|
-
);
|
|
2034
|
-
const pngImage = await pdfDoc.embedPng(pngBytes);
|
|
2035
|
-
page.drawImage(pngImage, {
|
|
2036
|
-
x,
|
|
2037
|
-
y,
|
|
2038
|
-
width: w,
|
|
2039
|
-
height: h
|
|
2040
|
-
});
|
|
2163
|
+
const img = await getSignature(field.value);
|
|
2164
|
+
page.drawImage(img, { x, y, width: w, height: h });
|
|
2041
2165
|
}
|
|
2042
2166
|
} else {
|
|
2043
|
-
const font =
|
|
2167
|
+
const font = await getFont(field.fontFamily || "Helvetica");
|
|
2044
2168
|
const spacing = field.letterSpacing || 0;
|
|
2045
|
-
const textWidthAtSize = (text, size) =>
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
if (textWidthAtSize(field.value, fontSize) <= w - padding) break;
|
|
2054
|
-
fontSize -= 0.5;
|
|
2169
|
+
const textWidthAtSize = (text, size) => font.widthOfTextAtSize(text, size) + spacing * (text.length - 1);
|
|
2170
|
+
let fontSize = Math.min(field.fontSize, h * 0.7);
|
|
2171
|
+
if (field.autoShrink) {
|
|
2172
|
+
const padding = 4;
|
|
2173
|
+
while (fontSize > 4) {
|
|
2174
|
+
if (textWidthAtSize(field.value, fontSize) <= w - padding) break;
|
|
2175
|
+
fontSize -= 0.5;
|
|
2176
|
+
}
|
|
2055
2177
|
}
|
|
2056
2178
|
if (spacing > 0) {
|
|
2057
2179
|
let cx = x + 2;
|
|
2058
2180
|
const cy = y + h * 0.3;
|
|
2059
2181
|
for (const char of field.value) {
|
|
2060
|
-
page.drawText(char, {
|
|
2182
|
+
page.drawText(char, {
|
|
2183
|
+
x: cx,
|
|
2184
|
+
y: cy,
|
|
2185
|
+
size: fontSize,
|
|
2186
|
+
font,
|
|
2187
|
+
color: inkColor
|
|
2188
|
+
});
|
|
2061
2189
|
cx += font.widthOfTextAtSize(char, fontSize) + spacing;
|
|
2062
2190
|
}
|
|
2063
2191
|
} else {
|
|
@@ -2071,7 +2199,98 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
2071
2199
|
}
|
|
2072
2200
|
}
|
|
2073
2201
|
}
|
|
2074
|
-
|
|
2202
|
+
}
|
|
2203
|
+
async function generateFilledPdf(opts) {
|
|
2204
|
+
const builder = await createPdfBuilder({ pageSize: opts.pageSize });
|
|
2205
|
+
await builder.addRecord(opts);
|
|
2206
|
+
return builder.save();
|
|
2207
|
+
}
|
|
2208
|
+
async function createPdfBuilder(defaults = {}) {
|
|
2209
|
+
const doc = await PDFDocument.create();
|
|
2210
|
+
const fontCache = /* @__PURE__ */ new Map();
|
|
2211
|
+
const embeddedPagesByUrl = /* @__PURE__ */ new Map();
|
|
2212
|
+
const signatureCache = /* @__PURE__ */ new Map();
|
|
2213
|
+
async function getFont(family) {
|
|
2214
|
+
let f = fontCache.get(family);
|
|
2215
|
+
if (!f) {
|
|
2216
|
+
const stdName = FONT_MAP[family] || StandardFonts.Helvetica;
|
|
2217
|
+
f = await doc.embedFont(stdName);
|
|
2218
|
+
fontCache.set(family, f);
|
|
2219
|
+
}
|
|
2220
|
+
return f;
|
|
2221
|
+
}
|
|
2222
|
+
async function ensureFonts(fields) {
|
|
2223
|
+
const keys = new Set(fields.map((f) => f.fontFamily || "Helvetica"));
|
|
2224
|
+
for (const k of keys) await getFont(k);
|
|
2225
|
+
}
|
|
2226
|
+
async function embedSource(source) {
|
|
2227
|
+
if (typeof source === "string") {
|
|
2228
|
+
let pages = embeddedPagesByUrl.get(source);
|
|
2229
|
+
if (!pages) {
|
|
2230
|
+
const res = await fetch(source);
|
|
2231
|
+
const bytes = await res.arrayBuffer();
|
|
2232
|
+
const srcDoc2 = await PDFDocument.load(bytes);
|
|
2233
|
+
pages = await Promise.all(
|
|
2234
|
+
srcDoc2.getPages().map((p) => doc.embedPage(p))
|
|
2235
|
+
);
|
|
2236
|
+
embeddedPagesByUrl.set(source, pages);
|
|
2237
|
+
}
|
|
2238
|
+
return pages;
|
|
2239
|
+
}
|
|
2240
|
+
const srcDoc = await PDFDocument.load(source);
|
|
2241
|
+
return Promise.all(srcDoc.getPages().map((p) => doc.embedPage(p)));
|
|
2242
|
+
}
|
|
2243
|
+
async function getSignature(dataUrl) {
|
|
2244
|
+
let img = signatureCache.get(dataUrl);
|
|
2245
|
+
if (!img) {
|
|
2246
|
+
const b64 = dataUrl.split(",")[1] ?? "";
|
|
2247
|
+
const pngBytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
2248
|
+
img = await doc.embedPng(pngBytes);
|
|
2249
|
+
signatureCache.set(dataUrl, img);
|
|
2250
|
+
}
|
|
2251
|
+
return img;
|
|
2252
|
+
}
|
|
2253
|
+
return {
|
|
2254
|
+
doc,
|
|
2255
|
+
async addRecord(opts) {
|
|
2256
|
+
const pageSize = opts.pageSize ?? defaults.pageSize;
|
|
2257
|
+
let fields = opts.resolveFormulas ? resolveAllFormulas(opts.fields, opts.customTransforms) : opts.fields;
|
|
2258
|
+
if (opts.calibration) {
|
|
2259
|
+
fields = applyCalibration(
|
|
2260
|
+
fields,
|
|
2261
|
+
opts.calibration,
|
|
2262
|
+
pageSize ?? US_LETTER
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
await ensureFonts(fields);
|
|
2266
|
+
let sourcePages = [];
|
|
2267
|
+
let pageCount;
|
|
2268
|
+
if (opts.pdfSource !== null) {
|
|
2269
|
+
sourcePages = await embedSource(opts.pdfSource);
|
|
2270
|
+
pageCount = sourcePages.length;
|
|
2271
|
+
} else {
|
|
2272
|
+
pageCount = opts.pageCount ?? 1;
|
|
2273
|
+
}
|
|
2274
|
+
const newPages = [];
|
|
2275
|
+
for (let i = 0; i < pageCount; i++) {
|
|
2276
|
+
const size = pageSize ? pageSize : sourcePages[i] ? [sourcePages[i].width, sourcePages[i].height] : US_LETTER;
|
|
2277
|
+
const page = doc.addPage(size);
|
|
2278
|
+
newPages.push(page);
|
|
2279
|
+
if (sourcePages[i]) {
|
|
2280
|
+
page.drawPage(sourcePages[i], {
|
|
2281
|
+
x: 0,
|
|
2282
|
+
y: 0,
|
|
2283
|
+
width: size[0],
|
|
2284
|
+
height: size[1]
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
await renderFieldsOnPages(newPages, fields, getFont, getSignature);
|
|
2289
|
+
},
|
|
2290
|
+
async save() {
|
|
2291
|
+
return doc.save();
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2075
2294
|
}
|
|
2076
2295
|
function downloadPdf(bytes, filename) {
|
|
2077
2296
|
const blob = new Blob([bytes.slice().buffer], { type: "application/pdf" });
|
|
@@ -2183,95 +2402,6 @@ function FieldNavigator({
|
|
|
2183
2402
|
] });
|
|
2184
2403
|
}
|
|
2185
2404
|
|
|
2186
|
-
// src/utils/formulaResolver.ts
|
|
2187
|
-
var BUILTIN_TRANSFORMS = {
|
|
2188
|
-
// Date transforms (expects a parseable date string)
|
|
2189
|
-
month: (v) => {
|
|
2190
|
-
const d = new Date(v);
|
|
2191
|
-
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
|
|
2192
|
-
},
|
|
2193
|
-
month2: (v) => {
|
|
2194
|
-
const d = new Date(v);
|
|
2195
|
-
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
|
|
2196
|
-
},
|
|
2197
|
-
monthname: (v) => {
|
|
2198
|
-
const d = new Date(v);
|
|
2199
|
-
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
|
|
2200
|
-
},
|
|
2201
|
-
monthshort: (v) => {
|
|
2202
|
-
const d = new Date(v);
|
|
2203
|
-
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
|
|
2204
|
-
},
|
|
2205
|
-
day: (v) => {
|
|
2206
|
-
const d = new Date(v);
|
|
2207
|
-
return isNaN(d.getTime()) ? "" : String(d.getDate());
|
|
2208
|
-
},
|
|
2209
|
-
day2: (v) => {
|
|
2210
|
-
const d = new Date(v);
|
|
2211
|
-
return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
|
|
2212
|
-
},
|
|
2213
|
-
year: (v) => {
|
|
2214
|
-
const d = new Date(v);
|
|
2215
|
-
return isNaN(d.getTime()) ? "" : String(d.getFullYear());
|
|
2216
|
-
},
|
|
2217
|
-
year2: (v) => {
|
|
2218
|
-
const d = new Date(v);
|
|
2219
|
-
return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
|
|
2220
|
-
},
|
|
2221
|
-
// String transforms
|
|
2222
|
-
upper: (v) => v.toUpperCase(),
|
|
2223
|
-
lower: (v) => v.toLowerCase(),
|
|
2224
|
-
trim: (v) => v.trim(),
|
|
2225
|
-
first: (v) => v.split(/\s+/)[0] || "",
|
|
2226
|
-
last: (v) => {
|
|
2227
|
-
const parts = v.split(/\s+/);
|
|
2228
|
-
return parts[parts.length - 1] || "";
|
|
2229
|
-
},
|
|
2230
|
-
initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
|
|
2231
|
-
// Numeric / substring
|
|
2232
|
-
last4: (v) => v.slice(-4),
|
|
2233
|
-
last2: (v) => v.slice(-2),
|
|
2234
|
-
first4: (v) => v.slice(0, 4),
|
|
2235
|
-
first2: (v) => v.slice(0, 2),
|
|
2236
|
-
digits: (v) => v.replace(/\D/g, ""),
|
|
2237
|
-
number: (v) => {
|
|
2238
|
-
const n = parseFloat(v);
|
|
2239
|
-
return isNaN(n) ? "" : String(n);
|
|
2240
|
-
},
|
|
2241
|
-
currency: (v) => {
|
|
2242
|
-
const n = parseFloat(v.replace(/[^0-9.-]/g, ""));
|
|
2243
|
-
return isNaN(n) ? "" : `$${n.toFixed(2)}`;
|
|
2244
|
-
}
|
|
2245
|
-
};
|
|
2246
|
-
var FORMULA_RE = /\{\{(.+?)\}\}/g;
|
|
2247
|
-
var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
|
|
2248
|
-
function resolveFormula(formula, fields, customTransforms) {
|
|
2249
|
-
const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
|
|
2250
|
-
return formula.replace(FORMULA_RE, (_match, expr) => {
|
|
2251
|
-
const pipeMatch = expr.match(PIPE_RE);
|
|
2252
|
-
const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
|
|
2253
|
-
const transformName = pipeMatch ? pipeMatch[2].trim() : null;
|
|
2254
|
-
const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
|
|
2255
|
-
if (!sourceField) return "";
|
|
2256
|
-
const rawValue = sourceField.value || "";
|
|
2257
|
-
if (!transformName) return rawValue;
|
|
2258
|
-
const fn = transforms[transformName];
|
|
2259
|
-
if (!fn) return rawValue;
|
|
2260
|
-
try {
|
|
2261
|
-
return fn(rawValue);
|
|
2262
|
-
} catch {
|
|
2263
|
-
return rawValue;
|
|
2264
|
-
}
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2267
|
-
function resolveAllFormulas(fields, customTransforms) {
|
|
2268
|
-
return fields.map((f) => {
|
|
2269
|
-
if (!f.formula) return f;
|
|
2270
|
-
const computed = resolveFormula(f.formula, fields, customTransforms);
|
|
2271
|
-
return computed !== f.value ? { ...f, value: computed } : f;
|
|
2272
|
-
});
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
2405
|
// src/components/pdf-builder/SignerView.tsx
|
|
2276
2406
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2277
2407
|
function SignerView({
|
|
@@ -2489,7 +2619,7 @@ function SignerView({
|
|
|
2489
2619
|
let pdfBytes;
|
|
2490
2620
|
if (includeAuditTrail) {
|
|
2491
2621
|
const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
|
|
2492
|
-
const basePdf = await generateFilledPdf(pdfSource, finalFields);
|
|
2622
|
+
const basePdf = await generateFilledPdf({ pdfSource, fields: finalFields });
|
|
2493
2623
|
const pdfDoc = await PDFDocument2.load(basePdf);
|
|
2494
2624
|
const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
|
|
2495
2625
|
const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
|
|
@@ -2506,7 +2636,7 @@ function SignerView({
|
|
|
2506
2636
|
}
|
|
2507
2637
|
pdfBytes = await pdfDoc.save();
|
|
2508
2638
|
} else {
|
|
2509
|
-
pdfBytes = await generateFilledPdf(pdfSource, finalFields);
|
|
2639
|
+
pdfBytes = await generateFilledPdf({ pdfSource, fields: finalFields });
|
|
2510
2640
|
}
|
|
2511
2641
|
const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
|
|
2512
2642
|
if (onExport && exportFormat) {
|
|
@@ -2828,7 +2958,9 @@ function SignerRoleSelector({
|
|
|
2828
2958
|
] });
|
|
2829
2959
|
}
|
|
2830
2960
|
export {
|
|
2961
|
+
A4,
|
|
2831
2962
|
BUILTIN_TRANSFORMS,
|
|
2963
|
+
DEFAULT_CALIBRATION,
|
|
2832
2964
|
DEFAULT_SIGNER_ROLES,
|
|
2833
2965
|
DesignerView,
|
|
2834
2966
|
FIELD_DEFAULTS,
|
|
@@ -2840,7 +2972,11 @@ export {
|
|
|
2840
2972
|
SignatureCanvas,
|
|
2841
2973
|
SignerRoleSelector,
|
|
2842
2974
|
SignerView,
|
|
2975
|
+
US_LEGAL,
|
|
2976
|
+
US_LETTER,
|
|
2977
|
+
applyCalibration,
|
|
2843
2978
|
createField,
|
|
2979
|
+
createPdfBuilder,
|
|
2844
2980
|
downloadPdf,
|
|
2845
2981
|
generateFilledPdf,
|
|
2846
2982
|
generateId,
|