@unlev/exeq 0.4.2 → 0.5.1

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
@@ -56,6 +56,17 @@ var CSS_FONT_MAP = {
56
56
  function getCssFontFamily(fontFamily) {
57
57
  return fontFamily ? CSS_FONT_MAP[fontFamily] : void 0;
58
58
  }
59
+ function getCssTextStyle(field) {
60
+ const decorations = [];
61
+ if (field.underline) decorations.push("underline");
62
+ if (field.strikethrough) decorations.push("line-through");
63
+ return {
64
+ fontFamily: getCssFontFamily(field.fontFamily),
65
+ fontWeight: field.bold ? "bold" : void 0,
66
+ fontStyle: field.italic ? "italic" : void 0,
67
+ textDecorationLine: decorations.length ? decorations.join(" ") : void 0
68
+ };
69
+ }
59
70
  function sortFieldsByPosition(fields) {
60
71
  return [...fields].sort((a, b) => {
61
72
  if (a.page !== b.page) return a.page - b.page;
@@ -711,6 +722,15 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillCon
711
722
  /* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
712
723
  /* @__PURE__ */ jsx2("input", { type: "number", min: "6", max: "72", value: field.fontSize, onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) }) })
713
724
  ] }),
725
+ isText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
726
+ /* @__PURE__ */ jsx2("label", { children: "Formatting" }),
727
+ /* @__PURE__ */ jsxs2("div", { className: "text-format-buttons", children: [
728
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${field.bold ? "active" : ""}`, style: { fontWeight: "bold" }, title: "Bold", onClick: () => onUpdate(field.id, { bold: !field.bold }), children: "B" }),
729
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${field.italic ? "active" : ""}`, style: { fontStyle: "italic" }, title: "Italic", onClick: () => onUpdate(field.id, { italic: !field.italic }), children: "I" }),
730
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${field.underline ? "active" : ""}`, style: { textDecorationLine: "underline" }, title: "Underline", onClick: () => onUpdate(field.id, { underline: !field.underline }), children: "U" }),
731
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${field.strikethrough ? "active" : ""}`, style: { textDecorationLine: "line-through" }, title: "Strikethrough", onClick: () => onUpdate(field.id, { strikethrough: !field.strikethrough }), children: "S" })
732
+ ] })
733
+ ] }),
714
734
  isText && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
715
735
  /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
716
736
  /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
@@ -783,6 +803,10 @@ function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete })
783
803
  const sharedInkColor = sharedValue(fields.map((f) => f.inkColor || "#000000"));
784
804
  const sharedLetterSpacing = sharedValue(fields.map((f) => f.letterSpacing || 0));
785
805
  const sharedLineHeight = sharedValue(fields.map((f) => f.lineHeight || 1.2));
806
+ const allBold = fields.every((f) => f.bold);
807
+ const allItalic = fields.every((f) => f.italic);
808
+ const allUnderline = fields.every((f) => f.underline);
809
+ const allStrike = fields.every((f) => f.strikethrough);
786
810
  return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
787
811
  /* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
788
812
  /* @__PURE__ */ jsxs2("h3", { children: [
@@ -897,6 +921,15 @@ function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete })
897
921
  }
898
922
  )
899
923
  ] }),
924
+ hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
925
+ /* @__PURE__ */ jsx2("label", { children: "Formatting" }),
926
+ /* @__PURE__ */ jsxs2("div", { className: "text-format-buttons", children: [
927
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${allBold ? "active" : ""}`, style: { fontWeight: "bold" }, title: "Bold", onClick: () => onUpdate({ bold: !allBold }), children: "B" }),
928
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${allItalic ? "active" : ""}`, style: { fontStyle: "italic" }, title: "Italic", onClick: () => onUpdate({ italic: !allItalic }), children: "I" }),
929
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${allUnderline ? "active" : ""}`, style: { textDecorationLine: "underline" }, title: "Underline", onClick: () => onUpdate({ underline: !allUnderline }), children: "U" }),
930
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${allStrike ? "active" : ""}`, style: { textDecorationLine: "line-through" }, title: "Strikethrough", onClick: () => onUpdate({ strikethrough: !allStrike }), children: "S" })
931
+ ] })
932
+ ] }),
900
933
  hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
901
934
  /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
902
935
  /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
@@ -1679,7 +1712,6 @@ function DesignerView({
1679
1712
  }
1680
1713
  }
1681
1714
  const inkColor = field.inkColor || "#000000";
1682
- const cssFontFamily = getCssFontFamily(field.fontFamily);
1683
1715
  return /* @__PURE__ */ jsx4(
1684
1716
  "div",
1685
1717
  {
@@ -1687,9 +1719,9 @@ function DesignerView({
1687
1719
  style: {
1688
1720
  color: field.value ? inkColor : void 0,
1689
1721
  fontSize: `${field.fontSize}pt`,
1690
- fontFamily: cssFontFamily,
1691
1722
  letterSpacing: field.letterSpacing ? `${field.letterSpacing}pt` : void 0,
1692
- lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0
1723
+ lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
1724
+ ...getCssTextStyle(field)
1693
1725
  },
1694
1726
  children: field.value || field.placeholder
1695
1727
  }
@@ -2061,39 +2093,56 @@ import {
2061
2093
  } from "pdf-lib";
2062
2094
 
2063
2095
  // src/utils/formulaResolver.ts
2096
+ function parseDate(value) {
2097
+ const s = value.trim();
2098
+ if (!s) return null;
2099
+ if (/^\d+(\.\d+)?$/.test(s)) {
2100
+ const serial = parseFloat(s);
2101
+ if (serial > 59) {
2102
+ const utc = new Date(Math.round((serial - 25569) * 864e5));
2103
+ if (!isNaN(utc.getTime())) {
2104
+ return new Date(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate());
2105
+ }
2106
+ }
2107
+ }
2108
+ const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
2109
+ if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3]);
2110
+ const d = new Date(s);
2111
+ return isNaN(d.getTime()) ? null : d;
2112
+ }
2064
2113
  var BUILTIN_TRANSFORMS = {
2065
2114
  // Date transforms (expects a parseable date string)
2066
2115
  month: (v) => {
2067
- const d = new Date(v);
2068
- return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
2116
+ const d = parseDate(v);
2117
+ return d ? String(d.getMonth() + 1) : "";
2069
2118
  },
2070
2119
  month2: (v) => {
2071
- const d = new Date(v);
2072
- return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
2120
+ const d = parseDate(v);
2121
+ return d ? String(d.getMonth() + 1).padStart(2, "0") : "";
2073
2122
  },
2074
2123
  monthname: (v) => {
2075
- const d = new Date(v);
2076
- return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
2124
+ const d = parseDate(v);
2125
+ return d ? d.toLocaleString("en", { month: "long" }) : "";
2077
2126
  },
2078
2127
  monthshort: (v) => {
2079
- const d = new Date(v);
2080
- return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
2128
+ const d = parseDate(v);
2129
+ return d ? d.toLocaleString("en", { month: "short" }) : "";
2081
2130
  },
2082
2131
  day: (v) => {
2083
- const d = new Date(v);
2084
- return isNaN(d.getTime()) ? "" : String(d.getDate());
2132
+ const d = parseDate(v);
2133
+ return d ? String(d.getDate()) : "";
2085
2134
  },
2086
2135
  day2: (v) => {
2087
- const d = new Date(v);
2088
- return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
2136
+ const d = parseDate(v);
2137
+ return d ? String(d.getDate()).padStart(2, "0") : "";
2089
2138
  },
2090
2139
  year: (v) => {
2091
- const d = new Date(v);
2092
- return isNaN(d.getTime()) ? "" : String(d.getFullYear());
2140
+ const d = parseDate(v);
2141
+ return d ? String(d.getFullYear()) : "";
2093
2142
  },
2094
2143
  year2: (v) => {
2095
- const d = new Date(v);
2096
- return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
2144
+ const d = parseDate(v);
2145
+ return d ? String(d.getFullYear()).slice(-2) : "";
2097
2146
  },
2098
2147
  // String transforms
2099
2148
  upper: (v) => v.toUpperCase(),
@@ -2122,15 +2171,27 @@ var BUILTIN_TRANSFORMS = {
2122
2171
  };
2123
2172
  var FORMULA_RE = /\{\{(.+?)\}\}/g;
2124
2173
  var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
2125
- function resolveFormula(formula, fields, customTransforms) {
2174
+ function todayIso() {
2175
+ const d = /* @__PURE__ */ new Date();
2176
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2177
+ }
2178
+ function resolveFormula(formula, fields, customTransforms, values) {
2126
2179
  const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
2127
2180
  return formula.replace(FORMULA_RE, (_match, expr) => {
2128
2181
  const pipeMatch = expr.match(PIPE_RE);
2129
2182
  const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
2130
2183
  const transformName = pipeMatch ? pipeMatch[2].trim() : null;
2131
- const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
2132
- if (!sourceField) return "";
2133
- const rawValue = sourceField.value || "";
2184
+ const lowerLabel = sourceLabel.toLowerCase();
2185
+ const sourceField = fields.find((f) => f.label.toLowerCase() === lowerLabel) || fields.find((f) => f.id === sourceLabel);
2186
+ let rawValue = sourceField ? sourceField.value || "" : void 0;
2187
+ if (rawValue === void 0 && values) {
2188
+ const key = Object.keys(values).find((k) => k.toLowerCase() === lowerLabel);
2189
+ if (key !== void 0) rawValue = values[key] || "";
2190
+ }
2191
+ if (rawValue === void 0 && (lowerLabel === "today" || lowerLabel === "now")) {
2192
+ rawValue = todayIso();
2193
+ }
2194
+ if (rawValue === void 0) return "";
2134
2195
  if (!transformName) return rawValue;
2135
2196
  const fn = transforms[transformName];
2136
2197
  if (!fn) return rawValue;
@@ -2141,20 +2202,28 @@ function resolveFormula(formula, fields, customTransforms) {
2141
2202
  }
2142
2203
  });
2143
2204
  }
2144
- function resolveAllFormulas(fields, customTransforms) {
2205
+ function resolveAllFormulas(fields, customTransforms, values) {
2145
2206
  return fields.map((f) => {
2146
2207
  if (!f.formula) return f;
2147
- const computed = resolveFormula(f.formula, fields, customTransforms);
2208
+ const computed = resolveFormula(f.formula, fields, customTransforms, values);
2148
2209
  return computed !== f.value ? { ...f, value: computed } : f;
2149
2210
  });
2150
2211
  }
2151
2212
 
2152
2213
  // src/utils/pdfFiller.ts
2153
- var FONT_MAP = {
2154
- Helvetica: StandardFonts.Helvetica,
2155
- Courier: StandardFonts.Courier,
2156
- TimesRoman: StandardFonts.TimesRoman
2214
+ var FONT_VARIANTS = {
2215
+ Helvetica: [StandardFonts.Helvetica, StandardFonts.HelveticaBold, StandardFonts.HelveticaOblique, StandardFonts.HelveticaBoldOblique],
2216
+ Courier: [StandardFonts.Courier, StandardFonts.CourierBold, StandardFonts.CourierOblique, StandardFonts.CourierBoldOblique],
2217
+ TimesRoman: [StandardFonts.TimesRoman, StandardFonts.TimesRomanBold, StandardFonts.TimesRomanItalic, StandardFonts.TimesRomanBoldItalic]
2157
2218
  };
2219
+ function resolveStandardFont(family, bold, italic) {
2220
+ const variants = FONT_VARIANTS[family] || FONT_VARIANTS.Helvetica;
2221
+ const idx = (bold ? 1 : 0) + (italic ? 2 : 0);
2222
+ return variants[idx];
2223
+ }
2224
+ function fontKey(family, bold, italic) {
2225
+ return `${family}|${bold ? "b" : ""}|${italic ? "i" : ""}`;
2226
+ }
2158
2227
  var US_LETTER = [612, 792];
2159
2228
  var US_LEGAL = [612, 1008];
2160
2229
  var A4 = [595.28, 841.89];
@@ -2229,7 +2298,7 @@ async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
2229
2298
  page.drawImage(img, { x, y, width: w, height: h });
2230
2299
  }
2231
2300
  } else {
2232
- const font = await getFont(field.fontFamily || "Helvetica");
2301
+ const font = await getFont(field.fontFamily || "Helvetica", field.bold, field.italic);
2233
2302
  const spacing = field.letterSpacing || 0;
2234
2303
  const textWidthAtSize = (text, size) => font.widthOfTextAtSize(text, size) + spacing * (text.length - 1);
2235
2304
  let fontSize = field.autoShrink ? Math.min(field.fontSize, h * 0.7) : field.fontSize;
@@ -2240,13 +2309,14 @@ async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
2240
2309
  fontSize -= 0.5;
2241
2310
  }
2242
2311
  }
2312
+ const textX = x + 2;
2313
+ const baselineY = y + h * 0.3;
2243
2314
  if (spacing > 0) {
2244
- let cx = x + 2;
2245
- const cy = y + h * 0.3;
2315
+ let cx = textX;
2246
2316
  for (const char of field.value) {
2247
2317
  page.drawText(char, {
2248
2318
  x: cx,
2249
- y: cy,
2319
+ y: baselineY,
2250
2320
  size: fontSize,
2251
2321
  font,
2252
2322
  color: inkColor
@@ -2255,13 +2325,35 @@ async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
2255
2325
  }
2256
2326
  } else {
2257
2327
  page.drawText(field.value, {
2258
- x: x + 2,
2259
- y: y + h * 0.3,
2328
+ x: textX,
2329
+ y: baselineY,
2260
2330
  size: fontSize,
2261
2331
  font,
2262
2332
  color: inkColor
2263
2333
  });
2264
2334
  }
2335
+ if (field.underline || field.strikethrough) {
2336
+ const textWidth = textWidthAtSize(field.value, fontSize);
2337
+ const lineThickness = Math.max(0.5, fontSize * 0.06);
2338
+ if (field.underline) {
2339
+ const uy = baselineY - fontSize * 0.12;
2340
+ page.drawLine({
2341
+ start: { x: textX, y: uy },
2342
+ end: { x: textX + textWidth, y: uy },
2343
+ thickness: lineThickness,
2344
+ color: inkColor
2345
+ });
2346
+ }
2347
+ if (field.strikethrough) {
2348
+ const sy = baselineY + fontSize * 0.3;
2349
+ page.drawLine({
2350
+ start: { x: textX, y: sy },
2351
+ end: { x: textX + textWidth, y: sy },
2352
+ thickness: lineThickness,
2353
+ color: inkColor
2354
+ });
2355
+ }
2356
+ }
2265
2357
  }
2266
2358
  }
2267
2359
  }
@@ -2275,18 +2367,19 @@ async function createPdfBuilder(defaults = {}) {
2275
2367
  const fontCache = /* @__PURE__ */ new Map();
2276
2368
  const embeddedPagesByUrl = /* @__PURE__ */ new Map();
2277
2369
  const signatureCache = /* @__PURE__ */ new Map();
2278
- async function getFont(family) {
2279
- let f = fontCache.get(family);
2370
+ async function getFont(family, bold, italic) {
2371
+ const key = fontKey(family, bold, italic);
2372
+ let f = fontCache.get(key);
2280
2373
  if (!f) {
2281
- const stdName = FONT_MAP[family] || StandardFonts.Helvetica;
2282
- f = await doc.embedFont(stdName);
2283
- fontCache.set(family, f);
2374
+ f = await doc.embedFont(resolveStandardFont(family, bold, italic));
2375
+ fontCache.set(key, f);
2284
2376
  }
2285
2377
  return f;
2286
2378
  }
2287
2379
  async function ensureFonts(fields) {
2288
- const keys = new Set(fields.map((f) => f.fontFamily || "Helvetica"));
2289
- for (const k of keys) await getFont(k);
2380
+ const keys = /* @__PURE__ */ new Map();
2381
+ for (const f of fields) keys.set(fontKey(f.fontFamily || "Helvetica", f.bold, f.italic), f);
2382
+ for (const f of keys.values()) await getFont(f.fontFamily || "Helvetica", f.bold, f.italic);
2290
2383
  }
2291
2384
  async function embedSource(source) {
2292
2385
  if (typeof source === "string") {
@@ -2319,7 +2412,7 @@ async function createPdfBuilder(defaults = {}) {
2319
2412
  doc,
2320
2413
  async addRecord(opts) {
2321
2414
  const pageSize = opts.pageSize ?? defaults.pageSize;
2322
- let fields = opts.resolveFormulas ? resolveAllFormulas(opts.fields, opts.customTransforms) : opts.fields;
2415
+ let fields = opts.resolveFormulas ? resolveAllFormulas(opts.fields, opts.customTransforms, opts.formulaValues) : opts.fields;
2323
2416
  if (opts.calibration) {
2324
2417
  fields = applyCalibration(
2325
2418
  fields,
@@ -2763,7 +2856,7 @@ ${row.join(",")}`, "csv");
2763
2856
  fontSize: `${field.fontSize}pt`,
2764
2857
  letterSpacing: field.letterSpacing ? `${field.letterSpacing}pt` : void 0,
2765
2858
  lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
2766
- fontFamily: getCssFontFamily(field.fontFamily)
2859
+ ...getCssTextStyle(field)
2767
2860
  };
2768
2861
  if (field.type === "dropdown" || field.options && field.options.length > 0) {
2769
2862
  return /* @__PURE__ */ jsxs6(
@@ -3041,6 +3134,7 @@ export {
3041
3134
  isRedactField,
3042
3135
  isSignatureField,
3043
3136
  isTextLikeField,
3137
+ parseDate,
3044
3138
  postPdfToCallback,
3045
3139
  preserveFieldValues,
3046
3140
  renderPdfPages,