@signiphi/pdf-signer 0.2.0-beta.25 → 0.2.0-beta.26

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.
@@ -617,6 +617,118 @@ function isRequiredField(field, fieldName, fieldType) {
617
617
  return false;
618
618
  }
619
619
 
620
+ // src/utils/pdf-text-wrapping.ts
621
+ function wrapTextToLines(text, font, fontSize, maxWidth) {
622
+ if (!text) return [];
623
+ if (maxWidth <= 0) return [text];
624
+ const measure = (s) => {
625
+ try {
626
+ return font.widthOfTextAtSize(s, fontSize);
627
+ } catch {
628
+ return s.length * fontSize * 0.5;
629
+ }
630
+ };
631
+ const breakLongWord = (word) => {
632
+ const chunks = [];
633
+ let current = "";
634
+ for (const ch of word) {
635
+ const candidate = current + ch;
636
+ if (measure(candidate) > maxWidth && current.length > 0) {
637
+ chunks.push(current);
638
+ current = ch;
639
+ } else {
640
+ current = candidate;
641
+ }
642
+ }
643
+ if (current.length > 0) chunks.push(current);
644
+ return chunks;
645
+ };
646
+ const segments = text.split("\n");
647
+ const lines = [];
648
+ for (const segment of segments) {
649
+ if (segment.length === 0) {
650
+ lines.push("");
651
+ continue;
652
+ }
653
+ const words = segment.split(/(\s+)/).filter((token) => token.length > 0);
654
+ let current = "";
655
+ for (const token of words) {
656
+ const candidate = current + token;
657
+ if (measure(candidate) <= maxWidth) {
658
+ current = candidate;
659
+ continue;
660
+ }
661
+ if (current.trim().length > 0) {
662
+ lines.push(current.trimEnd());
663
+ current = "";
664
+ }
665
+ const trimmedToken = token.trimStart();
666
+ if (measure(trimmedToken) <= maxWidth) {
667
+ current = trimmedToken;
668
+ } else {
669
+ const broken = breakLongWord(trimmedToken);
670
+ for (let i = 0; i < broken.length - 1; i++) {
671
+ const piece = broken[i];
672
+ if (piece !== void 0) lines.push(piece);
673
+ }
674
+ current = broken[broken.length - 1] ?? "";
675
+ }
676
+ }
677
+ lines.push(current.trimEnd());
678
+ }
679
+ return lines;
680
+ }
681
+ var MIN_MULTILINE_FONT_SIZE = 8;
682
+ function findFittingFontSize(text, font, preferredSize, maxWidth, maxHeight) {
683
+ if (preferredSize <= MIN_MULTILINE_FONT_SIZE) return preferredSize;
684
+ for (let size = preferredSize; size >= MIN_MULTILINE_FONT_SIZE; size -= 1) {
685
+ const lines = wrapTextToLines(text, font, size, maxWidth);
686
+ if (lines.length * size * 1.2 <= maxHeight) return size;
687
+ }
688
+ return MIN_MULTILINE_FONT_SIZE;
689
+ }
690
+ function drawWrappedText(page, text, opts) {
691
+ if (!text) return;
692
+ const { x, y, width, height, fontSize, font, color, multiline } = opts;
693
+ const padding = opts.padding ?? 2;
694
+ const innerWidth = Math.max(0, width - padding * 2);
695
+ if (!multiline) {
696
+ let display = text.replace(/\n/g, " ");
697
+ if (font.widthOfTextAtSize(display, fontSize) > innerWidth) {
698
+ let truncated = "";
699
+ for (const ch of display) {
700
+ if (font.widthOfTextAtSize(truncated + ch, fontSize) > innerWidth) break;
701
+ truncated += ch;
702
+ }
703
+ display = truncated;
704
+ }
705
+ page.drawText(display, {
706
+ x: x + padding,
707
+ y: y + (height - fontSize) / 2,
708
+ size: fontSize,
709
+ font,
710
+ color
711
+ });
712
+ return;
713
+ }
714
+ const innerHeight = Math.max(0, height - padding * 2);
715
+ const fittedSize = findFittingFontSize(text, font, fontSize, innerWidth, innerHeight);
716
+ const lineHeight = fittedSize * 1.2;
717
+ const allLines = wrapTextToLines(text, font, fittedSize, innerWidth);
718
+ for (let i = 0; i < allLines.length; i++) {
719
+ const line = allLines[i];
720
+ if (!line) continue;
721
+ const lineY = y + height - padding - fittedSize - i * lineHeight;
722
+ page.drawText(line, {
723
+ x: x + padding,
724
+ y: lineY,
725
+ size: fittedSize,
726
+ font,
727
+ color
728
+ });
729
+ }
730
+ }
731
+
620
732
  // src/utils/pdf-manipulation.ts
621
733
  async function readPdfFormFields(pdfBytes) {
622
734
  try {
@@ -1261,6 +1373,21 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
1261
1373
  const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
1262
1374
  const isDateField = fieldName.toLowerCase().includes("date") || fieldName.toLowerCase().includes("_date");
1263
1375
  const fontSize = isDateField ? 14 : extractFieldFontSize(fieldName, field, extractedFormFields);
1376
+ let isMultiline = false;
1377
+ if (!isDateField) {
1378
+ try {
1379
+ const textFieldField = field;
1380
+ if (typeof textFieldField.isMultiline === "function") {
1381
+ isMultiline = textFieldField.isMultiline() === true;
1382
+ }
1383
+ } catch {
1384
+ isMultiline = false;
1385
+ }
1386
+ if (!isMultiline) {
1387
+ isMultiline = fieldInfo?.multiline === true;
1388
+ }
1389
+ }
1390
+ const valueFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
1264
1391
  for (const widget of widgets) {
1265
1392
  const result = getWidgetRectangleAndPage(widget, pages);
1266
1393
  if (!result) continue;
@@ -1278,12 +1405,15 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
1278
1405
  });
1279
1406
  }
1280
1407
  if (fieldValue && fieldValue.trim()) {
1281
- page.drawText(fieldValue, {
1282
- x: rect.x + 2,
1283
- y: rect.y + 2,
1284
- size: fontSize,
1285
- font: await pdfDoc.embedFont(StandardFonts.Helvetica),
1286
- color: rgb(0, 0, 0)
1408
+ drawWrappedText(page, fieldValue, {
1409
+ x: rect.x,
1410
+ y: rect.y,
1411
+ width: rect.width,
1412
+ height: rect.height,
1413
+ fontSize,
1414
+ font: valueFont,
1415
+ color: rgb(0, 0, 0),
1416
+ multiline: isMultiline
1287
1417
  });
1288
1418
  }
1289
1419
  }
@@ -2043,14 +2173,17 @@ function parseAndValidateDate(value) {
2043
2173
  } catch {
2044
2174
  }
2045
2175
  }
2046
- const nativeParsed = new Date(sanitizedValue);
2047
- if (dateFns.isValid(nativeParsed) && !isNaN(nativeParsed.getTime())) {
2048
- return {
2049
- isValid: true,
2050
- date: nativeParsed,
2051
- isoString: toIsoDateString(nativeParsed),
2052
- originalValue
2053
- };
2176
+ const looksLikeNumericDate = /^\d{1,4}[\/.\-]\d{1,2}[\/.\-]\d{1,4}$/.test(sanitizedValue);
2177
+ if (!looksLikeNumericDate) {
2178
+ const nativeParsed = new Date(sanitizedValue);
2179
+ if (dateFns.isValid(nativeParsed) && !isNaN(nativeParsed.getTime())) {
2180
+ return {
2181
+ isValid: true,
2182
+ date: nativeParsed,
2183
+ isoString: toIsoDateString(nativeParsed),
2184
+ originalValue
2185
+ };
2186
+ }
2054
2187
  }
2055
2188
  return {
2056
2189
  isValid: false,