@unlev/exeq 0.3.1 → 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/dist/index.mjs CHANGED
@@ -614,6 +614,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillCon
614
614
  /* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.maxLength || 0, onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) }) })
615
615
  ] })
616
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
+ ] }),
617
624
  (field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
618
625
  /* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
619
626
  /* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
@@ -1982,34 +1989,137 @@ function DesignerView({
1982
1989
  import { useState as useState7, useCallback as useCallback5, useEffect as useEffect3, useRef as useRef5 } from "react";
1983
1990
 
1984
1991
  // src/utils/pdfFiller.ts
1985
- import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
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
1986
2088
  var FONT_MAP = {
1987
- "Helvetica": StandardFonts.Helvetica,
1988
- "Courier": StandardFonts.Courier,
1989
- "TimesRoman": StandardFonts.TimesRoman
2089
+ Helvetica: StandardFonts.Helvetica,
2090
+ Courier: StandardFonts.Courier,
2091
+ TimesRoman: StandardFonts.TimesRoman
1990
2092
  };
1991
- async function generateFilledPdf(pdfSource, fields) {
1992
- let pdfBytes;
1993
- if (typeof pdfSource === "string") {
1994
- const res = await fetch(pdfSource);
1995
- pdfBytes = await res.arrayBuffer();
1996
- } else {
1997
- pdfBytes = pdfSource;
1998
- }
1999
- const pdfDoc = await PDFDocument.load(pdfBytes);
2000
- const fontCache = /* @__PURE__ */ new Map();
2001
- const usedFontKeys = new Set(fields.map((f) => f.fontFamily || "Helvetica"));
2002
- for (const key of usedFontKeys) {
2003
- const stdFont = FONT_MAP[key] || StandardFonts.Helvetica;
2004
- fontCache.set(key, await pdfDoc.embedFont(stdFont));
2005
- }
2006
- const pages = pdfDoc.getPages();
2007
- function hexToRgb(hex) {
2008
- const r = parseInt(hex.slice(1, 3), 16) / 255;
2009
- const g = parseInt(hex.slice(3, 5), 16) / 255;
2010
- const b = parseInt(hex.slice(5, 7), 16) / 255;
2011
- 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;
2012
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) {
2013
2123
  for (const field of fields) {
2014
2124
  const page = pages[field.page];
2015
2125
  if (!page) continue;
@@ -2050,37 +2160,32 @@ async function generateFilledPdf(pdfSource, fields) {
2050
2160
  }
2051
2161
  } else if (field.type === "signature" || field.type === "initials") {
2052
2162
  if (field.value.startsWith("data:image/png")) {
2053
- const pngBytes = Uint8Array.from(
2054
- atob(field.value.split(",")[1]),
2055
- (c) => c.charCodeAt(0)
2056
- );
2057
- const pngImage = await pdfDoc.embedPng(pngBytes);
2058
- page.drawImage(pngImage, {
2059
- x,
2060
- y,
2061
- width: w,
2062
- height: h
2063
- });
2163
+ const img = await getSignature(field.value);
2164
+ page.drawImage(img, { x, y, width: w, height: h });
2064
2165
  }
2065
2166
  } else {
2066
- const font = fontCache.get(field.fontFamily || "Helvetica");
2167
+ const font = await getFont(field.fontFamily || "Helvetica");
2067
2168
  const spacing = field.letterSpacing || 0;
2068
- const textWidthAtSize = (text, size) => {
2069
- const baseWidth = font.widthOfTextAtSize(text, size);
2070
- return baseWidth + spacing * (text.length - 1);
2071
- };
2072
- const maxFontSize = Math.min(field.fontSize, h * 0.7);
2073
- let fontSize = maxFontSize;
2074
- const padding = 4;
2075
- while (fontSize > 4) {
2076
- if (textWidthAtSize(field.value, fontSize) <= w - padding) break;
2077
- 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
+ }
2078
2177
  }
2079
2178
  if (spacing > 0) {
2080
2179
  let cx = x + 2;
2081
2180
  const cy = y + h * 0.3;
2082
2181
  for (const char of field.value) {
2083
- page.drawText(char, { x: cx, y: cy, size: fontSize, font, color: inkColor });
2182
+ page.drawText(char, {
2183
+ x: cx,
2184
+ y: cy,
2185
+ size: fontSize,
2186
+ font,
2187
+ color: inkColor
2188
+ });
2084
2189
  cx += font.widthOfTextAtSize(char, fontSize) + spacing;
2085
2190
  }
2086
2191
  } else {
@@ -2094,7 +2199,98 @@ async function generateFilledPdf(pdfSource, fields) {
2094
2199
  }
2095
2200
  }
2096
2201
  }
2097
- return pdfDoc.save();
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
+ };
2098
2294
  }
2099
2295
  function downloadPdf(bytes, filename) {
2100
2296
  const blob = new Blob([bytes.slice().buffer], { type: "application/pdf" });
@@ -2206,95 +2402,6 @@ function FieldNavigator({
2206
2402
  ] });
2207
2403
  }
2208
2404
 
2209
- // src/utils/formulaResolver.ts
2210
- var BUILTIN_TRANSFORMS = {
2211
- // Date transforms (expects a parseable date string)
2212
- month: (v) => {
2213
- const d = new Date(v);
2214
- return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
2215
- },
2216
- month2: (v) => {
2217
- const d = new Date(v);
2218
- return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
2219
- },
2220
- monthname: (v) => {
2221
- const d = new Date(v);
2222
- return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
2223
- },
2224
- monthshort: (v) => {
2225
- const d = new Date(v);
2226
- return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
2227
- },
2228
- day: (v) => {
2229
- const d = new Date(v);
2230
- return isNaN(d.getTime()) ? "" : String(d.getDate());
2231
- },
2232
- day2: (v) => {
2233
- const d = new Date(v);
2234
- return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
2235
- },
2236
- year: (v) => {
2237
- const d = new Date(v);
2238
- return isNaN(d.getTime()) ? "" : String(d.getFullYear());
2239
- },
2240
- year2: (v) => {
2241
- const d = new Date(v);
2242
- return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
2243
- },
2244
- // String transforms
2245
- upper: (v) => v.toUpperCase(),
2246
- lower: (v) => v.toLowerCase(),
2247
- trim: (v) => v.trim(),
2248
- first: (v) => v.split(/\s+/)[0] || "",
2249
- last: (v) => {
2250
- const parts = v.split(/\s+/);
2251
- return parts[parts.length - 1] || "";
2252
- },
2253
- initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
2254
- // Numeric / substring
2255
- last4: (v) => v.slice(-4),
2256
- last2: (v) => v.slice(-2),
2257
- first4: (v) => v.slice(0, 4),
2258
- first2: (v) => v.slice(0, 2),
2259
- digits: (v) => v.replace(/\D/g, ""),
2260
- number: (v) => {
2261
- const n = parseFloat(v);
2262
- return isNaN(n) ? "" : String(n);
2263
- },
2264
- currency: (v) => {
2265
- const n = parseFloat(v.replace(/[^0-9.-]/g, ""));
2266
- return isNaN(n) ? "" : `$${n.toFixed(2)}`;
2267
- }
2268
- };
2269
- var FORMULA_RE = /\{\{(.+?)\}\}/g;
2270
- var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
2271
- function resolveFormula(formula, fields, customTransforms) {
2272
- const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
2273
- return formula.replace(FORMULA_RE, (_match, expr) => {
2274
- const pipeMatch = expr.match(PIPE_RE);
2275
- const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
2276
- const transformName = pipeMatch ? pipeMatch[2].trim() : null;
2277
- const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
2278
- if (!sourceField) return "";
2279
- const rawValue = sourceField.value || "";
2280
- if (!transformName) return rawValue;
2281
- const fn = transforms[transformName];
2282
- if (!fn) return rawValue;
2283
- try {
2284
- return fn(rawValue);
2285
- } catch {
2286
- return rawValue;
2287
- }
2288
- });
2289
- }
2290
- function resolveAllFormulas(fields, customTransforms) {
2291
- return fields.map((f) => {
2292
- if (!f.formula) return f;
2293
- const computed = resolveFormula(f.formula, fields, customTransforms);
2294
- return computed !== f.value ? { ...f, value: computed } : f;
2295
- });
2296
- }
2297
-
2298
2405
  // src/components/pdf-builder/SignerView.tsx
2299
2406
  import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2300
2407
  function SignerView({
@@ -2512,7 +2619,7 @@ function SignerView({
2512
2619
  let pdfBytes;
2513
2620
  if (includeAuditTrail) {
2514
2621
  const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
2515
- const basePdf = await generateFilledPdf(pdfSource, finalFields);
2622
+ const basePdf = await generateFilledPdf({ pdfSource, fields: finalFields });
2516
2623
  const pdfDoc = await PDFDocument2.load(basePdf);
2517
2624
  const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
2518
2625
  const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
@@ -2529,7 +2636,7 @@ function SignerView({
2529
2636
  }
2530
2637
  pdfBytes = await pdfDoc.save();
2531
2638
  } else {
2532
- pdfBytes = await generateFilledPdf(pdfSource, finalFields);
2639
+ pdfBytes = await generateFilledPdf({ pdfSource, fields: finalFields });
2533
2640
  }
2534
2641
  const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
2535
2642
  if (onExport && exportFormat) {
@@ -2851,7 +2958,9 @@ function SignerRoleSelector({
2851
2958
  ] });
2852
2959
  }
2853
2960
  export {
2961
+ A4,
2854
2962
  BUILTIN_TRANSFORMS,
2963
+ DEFAULT_CALIBRATION,
2855
2964
  DEFAULT_SIGNER_ROLES,
2856
2965
  DesignerView,
2857
2966
  FIELD_DEFAULTS,
@@ -2863,7 +2972,11 @@ export {
2863
2972
  SignatureCanvas,
2864
2973
  SignerRoleSelector,
2865
2974
  SignerView,
2975
+ US_LEGAL,
2976
+ US_LETTER,
2977
+ applyCalibration,
2866
2978
  createField,
2979
+ createPdfBuilder,
2867
2980
  downloadPdf,
2868
2981
  generateFilledPdf,
2869
2982
  generateId,