@signiphi/page-assembly 0.2.0-beta.13 → 0.2.0-beta.15

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
@@ -14,7 +14,7 @@ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
14
14
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
15
15
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
16
16
  import * as pdfjsLib from 'pdfjs-dist';
17
- import { PDFDocument, PDFTextField, PDFCheckBox, PDFDropdown, PDFRadioGroup, StandardFonts, rgb, PDFName, PDFString, PDFDict, PDFArray, PDFHexString, PDFInvalidObject, PDFObjectParser, PDFRawStream, PDFObjectStreamParser, PDFRef, PDFBool, PDFNumber } from 'pdf-lib';
17
+ import { PDFDocument, PDFName, PDFTextField, PDFCheckBox, PDFDropdown, PDFRadioGroup, PDFHexString, StandardFonts, rgb, PDFString, PDFOperator, PDFOperatorNames, PDFDict, PDFArray, PDFInvalidObject, PDFObjectParser, PDFRawStream, PDFObjectStreamParser, PDFRef, PDFBool, PDFNumber } from 'pdf-lib';
18
18
 
19
19
  // src/components/PageAssembler.tsx
20
20
  function cn(...inputs) {
@@ -1372,6 +1372,55 @@ function downloadPdf(pdfBytes, filename) {
1372
1372
  a.click();
1373
1373
  URL.revokeObjectURL(url);
1374
1374
  }
1375
+ function toCombinedPage(entry, page) {
1376
+ if (!entry.position && !entry.radioPositions && !entry.radioOptions) return entry;
1377
+ const carried = { ...entry };
1378
+ if (entry.position) carried.position = { ...entry.position, page };
1379
+ if (entry.radioPositions) carried.radioPositions = entry.radioPositions.map((p) => ({ ...p, page }));
1380
+ if (entry.radioOptions) {
1381
+ carried.radioOptions = entry.radioOptions.map((o) => ({ ...o, position: { ...o.position, page } }));
1382
+ }
1383
+ return carried;
1384
+ }
1385
+ function mergeSourceMetadata(sources) {
1386
+ const merged = { version: "1.0", fields: {}, fieldIdIndex: {} };
1387
+ let radioLabelsBaked;
1388
+ let textLabelsBaked;
1389
+ let checkboxLabelsBaked;
1390
+ for (const source of sources) {
1391
+ const meta = source.metadata;
1392
+ if (!meta || source.fields.length === 0) continue;
1393
+ if (meta.radioLabelsBaked === false) radioLabelsBaked = false;
1394
+ else if (meta.radioLabelsBaked === true && radioLabelsBaked === void 0) radioLabelsBaked = true;
1395
+ if (meta.textLabelsBaked === false) textLabelsBaked = false;
1396
+ else if (meta.textLabelsBaked === true && textLabelsBaked === void 0) textLabelsBaked = true;
1397
+ if (meta.checkboxLabelsBaked === false) checkboxLabelsBaked = false;
1398
+ else if (meta.checkboxLabelsBaked === true && checkboxLabelsBaked === void 0) checkboxLabelsBaked = true;
1399
+ }
1400
+ for (const source of sources) {
1401
+ const meta = source.metadata;
1402
+ if (!meta) continue;
1403
+ const sourceTextLabelsBaked = meta.textLabelsBaked !== false;
1404
+ for (const { name, page } of source.fields) {
1405
+ const key = `${name}_p${page}`;
1406
+ const entry = meta.fields?.[name];
1407
+ if (entry) {
1408
+ const carried = toCombinedPage(entry, page);
1409
+ merged.fields[key] = textLabelsBaked === false && sourceTextLabelsBaked ? { ...carried, textLabelBaked: true } : carried;
1410
+ }
1411
+ const fieldId = meta.fieldIdIndex?.[name];
1412
+ if (fieldId) merged.fieldIdIndex[key] = fieldId;
1413
+ }
1414
+ }
1415
+ if (radioLabelsBaked !== void 0) merged.radioLabelsBaked = radioLabelsBaked;
1416
+ if (textLabelsBaked !== void 0) merged.textLabelsBaked = textLabelsBaked;
1417
+ if (checkboxLabelsBaked !== void 0) merged.checkboxLabelsBaked = checkboxLabelsBaked;
1418
+ const hasEntries = Object.keys(merged.fields).length > 0;
1419
+ const hasMarkers = radioLabelsBaked !== void 0 || textLabelsBaked !== void 0 || checkboxLabelsBaked !== void 0;
1420
+ if (!hasEntries && !hasMarkers) return null;
1421
+ if (Object.keys(merged.fieldIdIndex).length === 0) delete merged.fieldIdIndex;
1422
+ return merged;
1423
+ }
1375
1424
  function getSigniphiMetadata(pdfDoc) {
1376
1425
  try {
1377
1426
  const infoRef = pdfDoc.context.trailerInfo.Info;
@@ -1387,12 +1436,17 @@ function getSigniphiMetadata(pdfDoc) {
1387
1436
  if (!metadataObj) {
1388
1437
  return null;
1389
1438
  }
1390
- const metadataStr = metadataObj.toString();
1391
- let jsonStr = metadataStr;
1392
- if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
1393
- jsonStr = metadataStr.slice(1, -1);
1394
- } else if (metadataStr.startsWith("<") && metadataStr.endsWith(">")) {
1395
- jsonStr = metadataStr.slice(1, -1);
1439
+ let jsonStr;
1440
+ if (typeof metadataObj.decodeText === "function") {
1441
+ jsonStr = metadataObj.decodeText();
1442
+ } else {
1443
+ const metadataStr = metadataObj.toString();
1444
+ jsonStr = metadataStr;
1445
+ if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
1446
+ jsonStr = metadataStr.slice(1, -1);
1447
+ } else if (metadataStr.startsWith("<") && metadataStr.endsWith(">")) {
1448
+ jsonStr = metadataStr.slice(1, -1);
1449
+ }
1396
1450
  }
1397
1451
  const metadata = JSON.parse(jsonStr);
1398
1452
  return metadata;
@@ -1585,12 +1639,13 @@ function setSigniphiMetadata(pdfDoc, metadata) {
1585
1639
  }
1586
1640
  const infoDict = infoObj;
1587
1641
  const metadataString = JSON.stringify(metadata);
1588
- infoDict.set(PDFName.of("SigniphiMetadata"), PDFString.of(metadataString));
1642
+ infoDict.set(PDFName.of("SigniphiMetadata"), PDFHexString.fromText(metadataString));
1589
1643
  } catch (error) {
1590
1644
  console.error("Failed to set Signiphi metadata:", error);
1591
1645
  throw error;
1592
1646
  }
1593
1647
  }
1648
+ var SIGNIPHI_CHECKBOX_LABEL_MARKER = "SiPL_Check";
1594
1649
  var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
1595
1650
  FormFieldType2["TEXT"] = "text";
1596
1651
  FormFieldType2["SIGNATURE"] = "signature";
@@ -1676,6 +1731,7 @@ async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
1676
1731
  }
1677
1732
  }
1678
1733
  const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
1734
+ const bakedCheckboxCaptions = /* @__PURE__ */ new Map();
1679
1735
  const fieldNamePages = /* @__PURE__ */ new Map();
1680
1736
  for (const field of formFields) {
1681
1737
  const baseName = field.name;
@@ -1772,10 +1828,15 @@ async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
1772
1828
  } catch {
1773
1829
  }
1774
1830
  if (drawLabels) {
1775
- const fontSize = Math.min(12, field.position.height * 0.6);
1831
+ const fontSize = Math.max(1, Math.min(10, field.position.height * 0.4));
1776
1832
  const labelX = pdfX + field.position.width + 5;
1777
1833
  const labelY = pdfY + (field.position.height - fontSize) / 2;
1834
+ page.pushOperators(
1835
+ PDFOperator.of(PDFOperatorNames.BeginMarkedContent, [PDFName.of(SIGNIPHI_CHECKBOX_LABEL_MARKER)])
1836
+ );
1778
1837
  page.drawText(field.label, { x: labelX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
1838
+ page.pushOperators(PDFOperator.of(PDFOperatorNames.EndMarkedContent));
1839
+ bakedCheckboxCaptions.set(encodedName, field);
1779
1840
  }
1780
1841
  }
1781
1842
  break;
@@ -1927,8 +1988,28 @@ async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
1927
1988
  console.error(`Error adding field ${field.name}:`, error);
1928
1989
  }
1929
1990
  }
1991
+ recordBakedCheckboxCaptions(pdfDoc, bakedCheckboxCaptions);
1930
1992
  return await pdfDoc.save();
1931
1993
  }
1994
+ function recordBakedCheckboxCaptions(pdfDoc, captions) {
1995
+ if (captions.size === 0) return;
1996
+ try {
1997
+ const metadata = getSigniphiMetadata(pdfDoc);
1998
+ if (!metadata) return;
1999
+ metadata.fields ?? (metadata.fields = {});
2000
+ for (const [name, field] of captions) {
2001
+ metadata.fields[name] = {
2002
+ signer: field.assignedSignerEmail,
2003
+ label: field.label,
2004
+ ...metadata.fields[name],
2005
+ checkboxCaptionBaked: true
2006
+ };
2007
+ }
2008
+ setSigniphiMetadata(pdfDoc, metadata);
2009
+ } catch (error) {
2010
+ console.warn("Failed to record baked checkbox captions:", error);
2011
+ }
2012
+ }
1932
2013
  function mapFieldPositionsAfterAssembly(fields, pageMapping) {
1933
2014
  return fields.map((field) => {
1934
2015
  const mappingKey = String(field.position.page - 1);
@@ -2898,7 +2979,7 @@ var PageAssembler = forwardRef(
2898
2979
  setIsLoading(true);
2899
2980
  setProcessingStatus("Assembling PDF...");
2900
2981
  try {
2901
- const { PDFDocument: PDFDocument5, PDFName: PDFName4, PDFString: PDFString4 } = await import('pdf-lib');
2982
+ const { PDFDocument: PDFDocument5, PDFName: PDFName4 } = await import('pdf-lib');
2902
2983
  const loadedPdfs = /* @__PURE__ */ new Map();
2903
2984
  const fileMap = /* @__PURE__ */ new Map();
2904
2985
  const pdfFiles = state.files.filter((f) => f.type === "pdf" && f.originalBytes?.length > 0);
@@ -2977,11 +3058,7 @@ var PageAssembler = forwardRef(
2977
3058
  }
2978
3059
  console.log("[PageAssembler] Starting multi-PDF assembly with field preservation");
2979
3060
  const allExtractedFields = [];
2980
- const mergedMetadata = {
2981
- version: "1.0",
2982
- fields: {},
2983
- fieldIdIndex: {}
2984
- };
3061
+ const metadataSources = [];
2985
3062
  const filePageUsage = /* @__PURE__ */ new Map();
2986
3063
  for (const file of state.files) {
2987
3064
  for (const page of file.pages) {
@@ -3006,28 +3083,12 @@ var PageAssembler = forwardRef(
3006
3083
  }
3007
3084
  console.log("[PageAssembler] Page mapping:", Object.fromEntries(pageIdToNewPageNumber));
3008
3085
  const extractAndMapFields = async (fileId, fileName, pdfBytes2, sourcePdf) => {
3009
- let sourceMetadata = null;
3010
- if (sourcePdf) {
3011
- try {
3012
- const sourceInfoRef = sourcePdf.context.trailerInfo.Info;
3013
- if (sourceInfoRef) {
3014
- const sourceInfoDict = sourcePdf.context.lookup(sourceInfoRef);
3015
- if (sourceInfoDict && typeof sourceInfoDict.get === "function") {
3016
- const metadataObj = sourceInfoDict.get(PDFName4.of("SigniphiMetadata"));
3017
- if (metadataObj) {
3018
- const metadataStr = metadataObj.toString();
3019
- let jsonStr = metadataStr;
3020
- if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
3021
- jsonStr = metadataStr.slice(1, -1);
3022
- }
3023
- sourceMetadata = JSON.parse(jsonStr);
3024
- }
3025
- }
3026
- }
3027
- } catch (metadataError) {
3028
- console.warn(`Could not read metadata from PDF ${fileName}:`, metadataError);
3029
- }
3086
+ const sourceMetadata = sourcePdf ? getSigniphiMetadata(sourcePdf) : null;
3087
+ if (sourcePdf && !sourceMetadata) {
3088
+ console.warn(`[PageAssembler] No readable SigniphiMetadata in "${fileName}"`);
3030
3089
  }
3090
+ const placements = [];
3091
+ metadataSources.push({ metadata: sourceMetadata, fields: placements });
3031
3092
  console.log(`[PageAssembler] Extracting fields from "${fileName}"`);
3032
3093
  const extractedFields = await extractFieldsFromPdf(pdfBytes2);
3033
3094
  console.log(`[PageAssembler] Found ${extractedFields.length} fields in "${fileName}"`);
@@ -3061,13 +3122,7 @@ var PageAssembler = forwardRef(
3061
3122
  };
3062
3123
  allExtractedFields.push(trackedField);
3063
3124
  console.log(`[PageAssembler] Mapped field "${field.name}" from page ${originalPage} to page ${newPageNumber}`);
3064
- const metadataKey = `${field.name}_p${newPageNumber}`;
3065
- if (sourceMetadata?.fields?.[field.name]) {
3066
- mergedMetadata.fields[metadataKey] = sourceMetadata.fields[field.name];
3067
- }
3068
- if (sourceMetadata?.fieldIdIndex?.[field.name]) {
3069
- mergedMetadata.fieldIdIndex[metadataKey] = sourceMetadata.fieldIdIndex[field.name];
3070
- }
3125
+ placements.push({ name: field.name, page: newPageNumber });
3071
3126
  }
3072
3127
  }
3073
3128
  }
@@ -3145,21 +3200,15 @@ var PageAssembler = forwardRef(
3145
3200
  }
3146
3201
  }
3147
3202
  }
3148
- if (Object.keys(mergedMetadata.fields).length > 0) {
3203
+ const mergedMetadata = mergeSourceMetadata(metadataSources);
3204
+ if (mergedMetadata) {
3149
3205
  try {
3150
- const newInfoRef = combinedDoc.context.trailerInfo.Info;
3151
- if (newInfoRef) {
3152
- const newInfoDict = combinedDoc.context.lookup(newInfoRef);
3153
- if (newInfoDict && typeof newInfoDict.set === "function") {
3154
- newInfoDict.set(
3155
- PDFName4.of("SigniphiMetadata"),
3156
- PDFString4.of(JSON.stringify(mergedMetadata))
3157
- );
3158
- }
3159
- }
3206
+ setSigniphiMetadata(combinedDoc, mergedMetadata);
3160
3207
  } catch (metadataError) {
3161
3208
  console.warn("Could not write merged metadata:", metadataError);
3162
3209
  }
3210
+ } else {
3211
+ console.warn("[PageAssembler] No source metadata survived the merge; the assembled document will read as unprepared downstream");
3163
3212
  }
3164
3213
  let pdfBytes = await combinedDoc.save();
3165
3214
  if (allExtractedFields.length > 0) {