@signiphi/page-assembly 0.2.0-beta.17 → 0.2.0-beta.19
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.d.mts +43 -4
- package/dist/index.d.ts +43 -4
- package/dist/index.js +125 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +126 -70
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +0 -8
- package/package.json +94 -93
package/dist/index.d.mts
CHANGED
|
@@ -64,6 +64,13 @@ type TrackedFormField = {
|
|
|
64
64
|
label?: string;
|
|
65
65
|
required?: boolean;
|
|
66
66
|
options?: string[];
|
|
67
|
+
/** RADIO: each option's circle rect, top-origin, on `position.page`. */
|
|
68
|
+
optionPositions?: Array<{
|
|
69
|
+
x: number;
|
|
70
|
+
y: number;
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
}>;
|
|
67
74
|
placeholder?: string;
|
|
68
75
|
defaultValue?: string;
|
|
69
76
|
assignedSignerEmail?: string;
|
|
@@ -295,7 +302,7 @@ declare function normalizePdfEncryption(pdfBytes: Uint8Array): Promise<Uint8Arra
|
|
|
295
302
|
*/
|
|
296
303
|
interface ExtractedField {
|
|
297
304
|
name: string;
|
|
298
|
-
type: 'text' | 'signature' | 'initials' | 'date' | 'checkbox' | 'radio' | 'dropdown' | 'text_label';
|
|
305
|
+
type: 'text' | 'signature' | 'initials' | 'date' | 'date_signed' | 'checkbox' | 'radio' | 'dropdown' | 'text_label';
|
|
299
306
|
page: number;
|
|
300
307
|
x: number;
|
|
301
308
|
y: number;
|
|
@@ -307,12 +314,29 @@ interface ExtractedField {
|
|
|
307
314
|
options?: string[];
|
|
308
315
|
fieldId?: string;
|
|
309
316
|
signer?: string;
|
|
317
|
+
/**
|
|
318
|
+
* RADIO: each option's circle widget rect on this page, top-origin, in
|
|
319
|
+
* option order — exactly as read, so a re-add can put every circle back
|
|
320
|
+
* where it was. `x/y/width/height` above is only the first circle.
|
|
321
|
+
*/
|
|
322
|
+
optionPositions?: Array<{
|
|
323
|
+
x: number;
|
|
324
|
+
y: number;
|
|
325
|
+
width: number;
|
|
326
|
+
height: number;
|
|
327
|
+
}>;
|
|
310
328
|
}
|
|
311
329
|
/**
|
|
312
330
|
* One field's metadata entry, as stored in the block.
|
|
313
331
|
*/
|
|
314
332
|
interface SigniphiFieldMetadata {
|
|
315
333
|
fieldId?: string;
|
|
334
|
+
/**
|
|
335
|
+
* Persisted field type. Read for one value: `date_signed`, which the name
|
|
336
|
+
* guess can only see as a date. Every other key (`autofillTag`, `dateFormat`,
|
|
337
|
+
* …) is carried through the merge untouched without being declared here.
|
|
338
|
+
*/
|
|
339
|
+
type?: ExtractedField['type'];
|
|
316
340
|
label?: string;
|
|
317
341
|
signer?: string;
|
|
318
342
|
placeholder?: string;
|
|
@@ -398,7 +422,8 @@ interface SigniphiMetadata {
|
|
|
398
422
|
checkboxLabelsBaked?: boolean;
|
|
399
423
|
}
|
|
400
424
|
/**
|
|
401
|
-
* Extract SigniphiMetadata from a PDF document
|
|
425
|
+
* Extract SigniphiMetadata from a PDF document. Delegates to the contract
|
|
426
|
+
* package's reader; null when there is no block or it will not parse.
|
|
402
427
|
*/
|
|
403
428
|
declare function getSigniphiMetadata(pdfDoc: PDFDocument): SigniphiMetadata | null;
|
|
404
429
|
/**
|
|
@@ -408,7 +433,8 @@ declare function getSigniphiMetadata(pdfDoc: PDFDocument): SigniphiMetadata | nu
|
|
|
408
433
|
*/
|
|
409
434
|
declare function extractFieldsFromPdf(pdfBytes: Uint8Array): Promise<ExtractedField[]>;
|
|
410
435
|
/**
|
|
411
|
-
* Set SigniphiMetadata in a PDF document
|
|
436
|
+
* Set SigniphiMetadata in a PDF document. Delegates to the contract package's
|
|
437
|
+
* writer, which stores hex and throws on failure.
|
|
412
438
|
*/
|
|
413
439
|
declare function setSigniphiMetadata(pdfDoc: PDFDocument, metadata: SigniphiMetadata): void;
|
|
414
440
|
|
|
@@ -429,7 +455,9 @@ declare enum FormFieldType {
|
|
|
429
455
|
CHECKBOX = "checkbox",
|
|
430
456
|
RADIO = "radio",
|
|
431
457
|
DROPDOWN = "dropdown",
|
|
432
|
-
TEXT_LABEL = "text_label"
|
|
458
|
+
TEXT_LABEL = "text_label",
|
|
459
|
+
/** A DATE the signer never edits: stamped at signing. Re-added exactly like DATE. */
|
|
460
|
+
DATE_SIGNED = "date_signed"
|
|
433
461
|
}
|
|
434
462
|
/**
|
|
435
463
|
* Form field position
|
|
@@ -457,6 +485,17 @@ interface FormFieldForPdf {
|
|
|
457
485
|
multiline?: boolean;
|
|
458
486
|
maxLength?: number;
|
|
459
487
|
assignedSignerEmail?: string;
|
|
488
|
+
/**
|
|
489
|
+
* RADIO: each option's circle widget rect, top-origin, on `position.page`,
|
|
490
|
+
* as `extractFieldsFromPdf` read it. When it covers every option, the circles
|
|
491
|
+
* are re-added exactly there instead of being laid out afresh in `position`.
|
|
492
|
+
*/
|
|
493
|
+
optionPositions?: Array<{
|
|
494
|
+
x: number;
|
|
495
|
+
y: number;
|
|
496
|
+
width: number;
|
|
497
|
+
height: number;
|
|
498
|
+
}>;
|
|
460
499
|
}
|
|
461
500
|
/**
|
|
462
501
|
* Add form fields to a PDF document
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,13 @@ type TrackedFormField = {
|
|
|
64
64
|
label?: string;
|
|
65
65
|
required?: boolean;
|
|
66
66
|
options?: string[];
|
|
67
|
+
/** RADIO: each option's circle rect, top-origin, on `position.page`. */
|
|
68
|
+
optionPositions?: Array<{
|
|
69
|
+
x: number;
|
|
70
|
+
y: number;
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
}>;
|
|
67
74
|
placeholder?: string;
|
|
68
75
|
defaultValue?: string;
|
|
69
76
|
assignedSignerEmail?: string;
|
|
@@ -295,7 +302,7 @@ declare function normalizePdfEncryption(pdfBytes: Uint8Array): Promise<Uint8Arra
|
|
|
295
302
|
*/
|
|
296
303
|
interface ExtractedField {
|
|
297
304
|
name: string;
|
|
298
|
-
type: 'text' | 'signature' | 'initials' | 'date' | 'checkbox' | 'radio' | 'dropdown' | 'text_label';
|
|
305
|
+
type: 'text' | 'signature' | 'initials' | 'date' | 'date_signed' | 'checkbox' | 'radio' | 'dropdown' | 'text_label';
|
|
299
306
|
page: number;
|
|
300
307
|
x: number;
|
|
301
308
|
y: number;
|
|
@@ -307,12 +314,29 @@ interface ExtractedField {
|
|
|
307
314
|
options?: string[];
|
|
308
315
|
fieldId?: string;
|
|
309
316
|
signer?: string;
|
|
317
|
+
/**
|
|
318
|
+
* RADIO: each option's circle widget rect on this page, top-origin, in
|
|
319
|
+
* option order — exactly as read, so a re-add can put every circle back
|
|
320
|
+
* where it was. `x/y/width/height` above is only the first circle.
|
|
321
|
+
*/
|
|
322
|
+
optionPositions?: Array<{
|
|
323
|
+
x: number;
|
|
324
|
+
y: number;
|
|
325
|
+
width: number;
|
|
326
|
+
height: number;
|
|
327
|
+
}>;
|
|
310
328
|
}
|
|
311
329
|
/**
|
|
312
330
|
* One field's metadata entry, as stored in the block.
|
|
313
331
|
*/
|
|
314
332
|
interface SigniphiFieldMetadata {
|
|
315
333
|
fieldId?: string;
|
|
334
|
+
/**
|
|
335
|
+
* Persisted field type. Read for one value: `date_signed`, which the name
|
|
336
|
+
* guess can only see as a date. Every other key (`autofillTag`, `dateFormat`,
|
|
337
|
+
* …) is carried through the merge untouched without being declared here.
|
|
338
|
+
*/
|
|
339
|
+
type?: ExtractedField['type'];
|
|
316
340
|
label?: string;
|
|
317
341
|
signer?: string;
|
|
318
342
|
placeholder?: string;
|
|
@@ -398,7 +422,8 @@ interface SigniphiMetadata {
|
|
|
398
422
|
checkboxLabelsBaked?: boolean;
|
|
399
423
|
}
|
|
400
424
|
/**
|
|
401
|
-
* Extract SigniphiMetadata from a PDF document
|
|
425
|
+
* Extract SigniphiMetadata from a PDF document. Delegates to the contract
|
|
426
|
+
* package's reader; null when there is no block or it will not parse.
|
|
402
427
|
*/
|
|
403
428
|
declare function getSigniphiMetadata(pdfDoc: PDFDocument): SigniphiMetadata | null;
|
|
404
429
|
/**
|
|
@@ -408,7 +433,8 @@ declare function getSigniphiMetadata(pdfDoc: PDFDocument): SigniphiMetadata | nu
|
|
|
408
433
|
*/
|
|
409
434
|
declare function extractFieldsFromPdf(pdfBytes: Uint8Array): Promise<ExtractedField[]>;
|
|
410
435
|
/**
|
|
411
|
-
* Set SigniphiMetadata in a PDF document
|
|
436
|
+
* Set SigniphiMetadata in a PDF document. Delegates to the contract package's
|
|
437
|
+
* writer, which stores hex and throws on failure.
|
|
412
438
|
*/
|
|
413
439
|
declare function setSigniphiMetadata(pdfDoc: PDFDocument, metadata: SigniphiMetadata): void;
|
|
414
440
|
|
|
@@ -429,7 +455,9 @@ declare enum FormFieldType {
|
|
|
429
455
|
CHECKBOX = "checkbox",
|
|
430
456
|
RADIO = "radio",
|
|
431
457
|
DROPDOWN = "dropdown",
|
|
432
|
-
TEXT_LABEL = "text_label"
|
|
458
|
+
TEXT_LABEL = "text_label",
|
|
459
|
+
/** A DATE the signer never edits: stamped at signing. Re-added exactly like DATE. */
|
|
460
|
+
DATE_SIGNED = "date_signed"
|
|
433
461
|
}
|
|
434
462
|
/**
|
|
435
463
|
* Form field position
|
|
@@ -457,6 +485,17 @@ interface FormFieldForPdf {
|
|
|
457
485
|
multiline?: boolean;
|
|
458
486
|
maxLength?: number;
|
|
459
487
|
assignedSignerEmail?: string;
|
|
488
|
+
/**
|
|
489
|
+
* RADIO: each option's circle widget rect, top-origin, on `position.page`,
|
|
490
|
+
* as `extractFieldsFromPdf` read it. When it covers every option, the circles
|
|
491
|
+
* are re-added exactly there instead of being laid out afresh in `position`.
|
|
492
|
+
*/
|
|
493
|
+
optionPositions?: Array<{
|
|
494
|
+
x: number;
|
|
495
|
+
y: number;
|
|
496
|
+
width: number;
|
|
497
|
+
height: number;
|
|
498
|
+
}>;
|
|
460
499
|
}
|
|
461
500
|
/**
|
|
462
501
|
* Add form fields to a PDF document
|
package/dist/index.js
CHANGED
|
@@ -343,16 +343,23 @@ var AlertDialogOverlay = React7__namespace.forwardRef(({ className, ...props },
|
|
|
343
343
|
}
|
|
344
344
|
));
|
|
345
345
|
AlertDialogOverlay.displayName = AlertDialogPrimitive__namespace.Overlay.displayName;
|
|
346
|
-
var
|
|
346
|
+
var CENTERED = {
|
|
347
|
+
position: "fixed",
|
|
348
|
+
left: "50%",
|
|
349
|
+
top: "50%",
|
|
350
|
+
transform: "translate(-50%, -50%)"
|
|
351
|
+
};
|
|
352
|
+
var AlertDialogContent = React7__namespace.forwardRef(({ className, style, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogPortal, { container: document.querySelector(".signiphi-page-assembly"), children: [
|
|
347
353
|
/* @__PURE__ */ jsxRuntime.jsx(AlertDialogOverlay, {}),
|
|
348
354
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
349
355
|
AlertDialogPrimitive__namespace.Content,
|
|
350
356
|
{
|
|
351
357
|
ref,
|
|
352
358
|
className: cn(
|
|
353
|
-
"
|
|
359
|
+
"z-[9999] grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
|
|
354
360
|
className
|
|
355
361
|
),
|
|
362
|
+
style: { ...CENTERED, ...style },
|
|
356
363
|
...props
|
|
357
364
|
}
|
|
358
365
|
)
|
|
@@ -1401,6 +1408,46 @@ function downloadPdf(pdfBytes, filename) {
|
|
|
1401
1408
|
a.click();
|
|
1402
1409
|
URL.revokeObjectURL(url);
|
|
1403
1410
|
}
|
|
1411
|
+
var KEY = "SigniphiMetadata";
|
|
1412
|
+
function readMetadata(doc, pdfLib) {
|
|
1413
|
+
let blockPresent = false;
|
|
1414
|
+
try {
|
|
1415
|
+
const infoRef = doc.context.trailerInfo.Info;
|
|
1416
|
+
if (!infoRef) return { metadata: null, blockPresent };
|
|
1417
|
+
const info = doc.context.lookup(infoRef);
|
|
1418
|
+
if (!info || typeof info.get !== "function") return { metadata: null, blockPresent };
|
|
1419
|
+
const value = info.get(pdfLib.PDFName.of(KEY));
|
|
1420
|
+
if (!value) return { metadata: null, blockPresent };
|
|
1421
|
+
blockPresent = true;
|
|
1422
|
+
const json = typeof value.decodeText === "function" ? value.decodeText() : stripDelimiters(value.toString());
|
|
1423
|
+
return { metadata: JSON.parse(json), blockPresent };
|
|
1424
|
+
} catch {
|
|
1425
|
+
return { metadata: null, blockPresent };
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
function writeMetadata(doc, metadata, pdfLib) {
|
|
1429
|
+
const info = doc.context.lookup(doc.context.trailerInfo.Info);
|
|
1430
|
+
if (!info || typeof info.set !== "function") {
|
|
1431
|
+
throw new Error("Cannot write SigniphiMetadata: the PDF has no Info dictionary");
|
|
1432
|
+
}
|
|
1433
|
+
info.set(pdfLib.PDFName.of(KEY), pdfLib.PDFHexString.fromText(JSON.stringify(metadata)));
|
|
1434
|
+
}
|
|
1435
|
+
function stripDelimiters(raw) {
|
|
1436
|
+
const wrapped = raw.startsWith("(") && raw.endsWith(")") || raw.startsWith("<") && raw.endsWith(">");
|
|
1437
|
+
return wrapped ? raw.slice(1, -1) : raw;
|
|
1438
|
+
}
|
|
1439
|
+
function textLabelPlacements(source) {
|
|
1440
|
+
const fields = source.metadata?.fields;
|
|
1441
|
+
if (!fields || !source.pages?.length) return [];
|
|
1442
|
+
const out = [];
|
|
1443
|
+
for (const [name, entry] of Object.entries(fields)) {
|
|
1444
|
+
if (entry.type !== "text_label" || !entry.position) continue;
|
|
1445
|
+
for (const { source: page, combined } of source.pages) {
|
|
1446
|
+
if (page === entry.position.page) out.push({ name, page: combined });
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
return out;
|
|
1450
|
+
}
|
|
1404
1451
|
function toCombinedPage(entry, page) {
|
|
1405
1452
|
if (!entry.position && !entry.radioPositions && !entry.radioOptions) return entry;
|
|
1406
1453
|
const carried = { ...entry };
|
|
@@ -1411,78 +1458,55 @@ function toCombinedPage(entry, page) {
|
|
|
1411
1458
|
}
|
|
1412
1459
|
return carried;
|
|
1413
1460
|
}
|
|
1414
|
-
function
|
|
1461
|
+
function assembleMetadata(sources) {
|
|
1415
1462
|
const merged = { version: "1.0", fields: {}, fieldIdIndex: {} };
|
|
1416
1463
|
let radioLabelsBaked;
|
|
1417
1464
|
let textLabelsBaked;
|
|
1418
1465
|
let checkboxLabelsBaked;
|
|
1466
|
+
let legacyRadioStripped;
|
|
1419
1467
|
for (const source of sources) {
|
|
1420
1468
|
const meta = source.metadata;
|
|
1421
|
-
if (!meta || source.fields.length === 0) continue;
|
|
1469
|
+
if (!meta || source.fields.length === 0 && !source.pages?.length) continue;
|
|
1422
1470
|
if (meta.radioLabelsBaked === false) radioLabelsBaked = false;
|
|
1423
1471
|
else if (meta.radioLabelsBaked === true && radioLabelsBaked === void 0) radioLabelsBaked = true;
|
|
1424
1472
|
if (meta.textLabelsBaked === false) textLabelsBaked = false;
|
|
1425
1473
|
else if (meta.textLabelsBaked === true && textLabelsBaked === void 0) textLabelsBaked = true;
|
|
1426
1474
|
if (meta.checkboxLabelsBaked === false) checkboxLabelsBaked = false;
|
|
1427
1475
|
else if (meta.checkboxLabelsBaked === true && checkboxLabelsBaked === void 0) checkboxLabelsBaked = true;
|
|
1476
|
+
legacyRadioStripped = meta.legacyRadioStripped === true && legacyRadioStripped !== false;
|
|
1428
1477
|
}
|
|
1429
1478
|
for (const source of sources) {
|
|
1430
1479
|
const meta = source.metadata;
|
|
1431
1480
|
if (!meta) continue;
|
|
1432
1481
|
const sourceTextLabelsBaked = meta.textLabelsBaked !== false;
|
|
1433
|
-
for (const { name, page } of source.fields) {
|
|
1482
|
+
for (const { name, page } of [...source.fields, ...textLabelPlacements(source)]) {
|
|
1434
1483
|
const key = `${name}_p${page}`;
|
|
1435
1484
|
const entry = meta.fields?.[name];
|
|
1436
|
-
if (entry)
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
const fieldId = meta.fieldIdIndex?.[name];
|
|
1441
|
-
if (fieldId) merged.fieldIdIndex[key] = fieldId;
|
|
1485
|
+
if (!entry) continue;
|
|
1486
|
+
const carried = toCombinedPage(entry, page);
|
|
1487
|
+
merged.fields[key] = textLabelsBaked === false && sourceTextLabelsBaked ? { ...carried, textLabelBaked: true } : carried;
|
|
1488
|
+
if (entry.fieldId) merged.fieldIdIndex[entry.fieldId] = key;
|
|
1442
1489
|
}
|
|
1443
1490
|
}
|
|
1444
1491
|
if (radioLabelsBaked !== void 0) merged.radioLabelsBaked = radioLabelsBaked;
|
|
1445
1492
|
if (textLabelsBaked !== void 0) merged.textLabelsBaked = textLabelsBaked;
|
|
1446
1493
|
if (checkboxLabelsBaked !== void 0) merged.checkboxLabelsBaked = checkboxLabelsBaked;
|
|
1494
|
+
if (legacyRadioStripped) merged.legacyRadioStripped = true;
|
|
1447
1495
|
const hasEntries = Object.keys(merged.fields).length > 0;
|
|
1448
1496
|
const hasMarkers = radioLabelsBaked !== void 0 || textLabelsBaked !== void 0 || checkboxLabelsBaked !== void 0;
|
|
1449
1497
|
if (!hasEntries && !hasMarkers) return null;
|
|
1450
1498
|
if (Object.keys(merged.fieldIdIndex).length === 0) delete merged.fieldIdIndex;
|
|
1451
1499
|
return merged;
|
|
1452
1500
|
}
|
|
1501
|
+
function mergeSourceMetadata(sources) {
|
|
1502
|
+
return assembleMetadata(sources);
|
|
1503
|
+
}
|
|
1453
1504
|
function getSigniphiMetadata(pdfDoc) {
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
return null;
|
|
1458
|
-
}
|
|
1459
|
-
const infoObj = pdfDoc.context.lookup(infoRef);
|
|
1460
|
-
if (!infoObj || typeof infoObj.get !== "function") {
|
|
1461
|
-
return null;
|
|
1462
|
-
}
|
|
1463
|
-
const infoDict = infoObj;
|
|
1464
|
-
const metadataObj = infoDict.get(pdfLib.PDFName.of("SigniphiMetadata"));
|
|
1465
|
-
if (!metadataObj) {
|
|
1466
|
-
return null;
|
|
1467
|
-
}
|
|
1468
|
-
let jsonStr;
|
|
1469
|
-
if (typeof metadataObj.decodeText === "function") {
|
|
1470
|
-
jsonStr = metadataObj.decodeText();
|
|
1471
|
-
} else {
|
|
1472
|
-
const metadataStr = metadataObj.toString();
|
|
1473
|
-
jsonStr = metadataStr;
|
|
1474
|
-
if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
|
|
1475
|
-
jsonStr = metadataStr.slice(1, -1);
|
|
1476
|
-
} else if (metadataStr.startsWith("<") && metadataStr.endsWith(">")) {
|
|
1477
|
-
jsonStr = metadataStr.slice(1, -1);
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
const metadata = JSON.parse(jsonStr);
|
|
1481
|
-
return metadata;
|
|
1482
|
-
} catch (error) {
|
|
1483
|
-
console.error("Failed to load Signiphi metadata:", error);
|
|
1484
|
-
return null;
|
|
1505
|
+
const { metadata, blockPresent } = readMetadata(pdfDoc, { PDFName: pdfLib.PDFName });
|
|
1506
|
+
if (blockPresent && !metadata) {
|
|
1507
|
+
console.error("Failed to load Signiphi metadata: the block is present but will not parse");
|
|
1485
1508
|
}
|
|
1509
|
+
return metadata;
|
|
1486
1510
|
}
|
|
1487
1511
|
function getBaseFieldName(encodedName) {
|
|
1488
1512
|
let baseName = encodedName;
|
|
@@ -1541,6 +1565,10 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1541
1565
|
const metadata = getSigniphiMetadata(pdfDoc);
|
|
1542
1566
|
const form = pdfDoc.getForm();
|
|
1543
1567
|
const pdfFields = form.getFields();
|
|
1568
|
+
const pages = pdfDoc.getPages();
|
|
1569
|
+
const pageHeights = pages.map((p) => p.getHeight());
|
|
1570
|
+
const pageIndexByRef = new Map(pages.map((p, i) => [p.ref, i + 1]));
|
|
1571
|
+
const pageNumberOf = (ref) => ref && pageIndexByRef.get(ref) || 1;
|
|
1544
1572
|
for (const field of pdfFields) {
|
|
1545
1573
|
const fieldName = field.getName();
|
|
1546
1574
|
const widgets = field.acroField.getWidgets();
|
|
@@ -1569,19 +1597,20 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1569
1597
|
} else {
|
|
1570
1598
|
console.log(`[field-extraction] Unknown field type "${fieldName}" - defaulting required=false`);
|
|
1571
1599
|
}
|
|
1600
|
+
const optionPositionsByPage = /* @__PURE__ */ new Map();
|
|
1601
|
+
if (widgetType === "radio") {
|
|
1602
|
+
for (const w of widgets) {
|
|
1603
|
+
const pageNumber = pageNumberOf(w.P());
|
|
1604
|
+
const r = w.getRectangle();
|
|
1605
|
+
const top = pageHeights[pageNumber - 1] - r.y - r.height;
|
|
1606
|
+
const bucket = optionPositionsByPage.get(pageNumber) ?? [];
|
|
1607
|
+
bucket.push({ x: r.x, y: top, width: r.width, height: r.height });
|
|
1608
|
+
optionPositionsByPage.set(pageNumber, bucket);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1572
1611
|
for (const widget of widgets) {
|
|
1573
1612
|
const rect = widget.getRectangle();
|
|
1574
|
-
const
|
|
1575
|
-
let pageNumber = 1;
|
|
1576
|
-
if (pageRef) {
|
|
1577
|
-
const pages = pdfDoc.getPages();
|
|
1578
|
-
for (let i = 0; i < pages.length; i++) {
|
|
1579
|
-
if (pages[i].ref === pageRef) {
|
|
1580
|
-
pageNumber = i + 1;
|
|
1581
|
-
break;
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1613
|
+
const pageNumber = pageNumberOf(widget.P());
|
|
1585
1614
|
const pageQualifiedKey = `${baseName}_p${pageNumber}`;
|
|
1586
1615
|
let fieldMetadata = metadata?.fields?.[pageQualifiedKey];
|
|
1587
1616
|
if (!fieldMetadata) {
|
|
@@ -1598,13 +1627,13 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1598
1627
|
if (fieldMetadata?.options?.length) {
|
|
1599
1628
|
fieldOptions = fieldMetadata.options;
|
|
1600
1629
|
}
|
|
1601
|
-
const
|
|
1602
|
-
const pageHeight = page.getHeight();
|
|
1630
|
+
const pageHeight = pageHeights[pageNumber - 1];
|
|
1603
1631
|
const extractedField = {
|
|
1604
1632
|
name: baseName,
|
|
1605
1633
|
// Use clean base name without __SIGNER__ encoding (signer stored in signer property)
|
|
1606
|
-
|
|
1607
|
-
//
|
|
1634
|
+
// Uses base name internally. A stored date_signed is a declaration the
|
|
1635
|
+
// name can't express, so it wins.
|
|
1636
|
+
type: fieldMetadata?.type === "date_signed" ? "date_signed" : determineFieldType(fieldName, widgetType),
|
|
1608
1637
|
page: pageNumber,
|
|
1609
1638
|
x: rect.x,
|
|
1610
1639
|
y: pageHeight - rect.y - rect.height,
|
|
@@ -1616,6 +1645,9 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1616
1645
|
signer: signerFromName
|
|
1617
1646
|
// Extract signer from encoded name
|
|
1618
1647
|
};
|
|
1648
|
+
if (widgetType === "radio") {
|
|
1649
|
+
extractedField.optionPositions = optionPositionsByPage.get(pageNumber) ?? [];
|
|
1650
|
+
}
|
|
1619
1651
|
if (fieldMetadata) {
|
|
1620
1652
|
if (fieldMetadata.fieldId) {
|
|
1621
1653
|
extractedField.fieldId = fieldMetadata.fieldId;
|
|
@@ -1661,20 +1693,14 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1661
1693
|
}
|
|
1662
1694
|
function setSigniphiMetadata(pdfDoc, metadata) {
|
|
1663
1695
|
try {
|
|
1664
|
-
|
|
1665
|
-
const infoObj = pdfDoc.context.lookup(infoRef);
|
|
1666
|
-
if (!infoObj || typeof infoObj.set !== "function") {
|
|
1667
|
-
throw new Error("Info object is not a PDFDict or does not have set method");
|
|
1668
|
-
}
|
|
1669
|
-
const infoDict = infoObj;
|
|
1670
|
-
const metadataString = JSON.stringify(metadata);
|
|
1671
|
-
infoDict.set(pdfLib.PDFName.of("SigniphiMetadata"), pdfLib.PDFHexString.fromText(metadataString));
|
|
1696
|
+
writeMetadata(pdfDoc, metadata, { PDFName: pdfLib.PDFName, PDFHexString: pdfLib.PDFHexString });
|
|
1672
1697
|
} catch (error) {
|
|
1673
1698
|
console.error("Failed to set Signiphi metadata:", error);
|
|
1674
1699
|
throw error;
|
|
1675
1700
|
}
|
|
1676
1701
|
}
|
|
1677
1702
|
var SIGNIPHI_CHECKBOX_LABEL_MARKER = "SiPL_Check";
|
|
1703
|
+
var RADIO_BORDER_WIDTH = 1;
|
|
1678
1704
|
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
1679
1705
|
FormFieldType2["TEXT"] = "text";
|
|
1680
1706
|
FormFieldType2["SIGNATURE"] = "signature";
|
|
@@ -1684,6 +1710,7 @@ var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
|
1684
1710
|
FormFieldType2["RADIO"] = "radio";
|
|
1685
1711
|
FormFieldType2["DROPDOWN"] = "dropdown";
|
|
1686
1712
|
FormFieldType2["TEXT_LABEL"] = "text_label";
|
|
1713
|
+
FormFieldType2["DATE_SIGNED"] = "date_signed";
|
|
1687
1714
|
return FormFieldType2;
|
|
1688
1715
|
})(FormFieldType || {});
|
|
1689
1716
|
function parseEncodedFieldName(encodedName) {
|
|
@@ -1914,7 +1941,10 @@ async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
|
|
|
1914
1941
|
}
|
|
1915
1942
|
break;
|
|
1916
1943
|
}
|
|
1917
|
-
|
|
1944
|
+
// DATE_SIGNED is the same widget; its metadata `type` (carried by the
|
|
1945
|
+
// merge, not by this pass) is what tells pdf-signer to stamp it.
|
|
1946
|
+
case "date" /* DATE */:
|
|
1947
|
+
case "date_signed" /* DATE_SIGNED */: {
|
|
1918
1948
|
const baseWithSuffix = field.name.endsWith("_date") ? field.name : `${field.name}_date`;
|
|
1919
1949
|
const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
|
|
1920
1950
|
const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
|
|
@@ -1971,7 +2001,24 @@ async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
|
|
|
1971
2001
|
case "radio" /* RADIO */: {
|
|
1972
2002
|
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1973
2003
|
const radioGroup = form.createRadioGroup(encodedName);
|
|
1974
|
-
if (field.options && field.options.length > 0) {
|
|
2004
|
+
if (field.options && field.options.length > 0 && field.optionPositions?.length === field.options.length) {
|
|
2005
|
+
const optionPositions = field.optionPositions;
|
|
2006
|
+
field.options.forEach((option, index) => {
|
|
2007
|
+
const at = optionPositions[index];
|
|
2008
|
+
radioGroup.addOptionToPage(option, page, {
|
|
2009
|
+
x: at.x + RADIO_BORDER_WIDTH / 2,
|
|
2010
|
+
y: pageHeight - at.y - at.height + RADIO_BORDER_WIDTH / 2,
|
|
2011
|
+
width: at.width - RADIO_BORDER_WIDTH,
|
|
2012
|
+
height: at.height - RADIO_BORDER_WIDTH,
|
|
2013
|
+
borderWidth: RADIO_BORDER_WIDTH,
|
|
2014
|
+
borderColor: pdfLib.rgb(0.5, 0.5, 0.5),
|
|
2015
|
+
backgroundColor: pdfLib.rgb(1, 1, 1)
|
|
2016
|
+
});
|
|
2017
|
+
});
|
|
2018
|
+
if (field.defaultValue && field.options.includes(field.defaultValue)) {
|
|
2019
|
+
radioGroup.select(field.defaultValue);
|
|
2020
|
+
}
|
|
2021
|
+
} else if (field.options && field.options.length > 0) {
|
|
1975
2022
|
const optionHeight = Math.min(20, field.position.height / field.options.length);
|
|
1976
2023
|
const spacing = field.position.height / field.options.length;
|
|
1977
2024
|
field.options.forEach((option, index) => {
|
|
@@ -3116,12 +3163,19 @@ var PageAssembler = React7.forwardRef(
|
|
|
3116
3163
|
if (sourcePdf && !sourceMetadata) {
|
|
3117
3164
|
console.warn(`[PageAssembler] No readable SigniphiMetadata in "${fileName}"`);
|
|
3118
3165
|
}
|
|
3166
|
+
const pageUsageForFile = filePageUsage.get(fileId) || /* @__PURE__ */ new Map();
|
|
3119
3167
|
const placements = [];
|
|
3120
|
-
|
|
3168
|
+
const pageLandings = [];
|
|
3169
|
+
for (const [srcIndex, pageInstances] of pageUsageForFile) {
|
|
3170
|
+
for (const pageInstance of pageInstances) {
|
|
3171
|
+
const combined = pageIdToNewPageNumber.get(pageInstance.id);
|
|
3172
|
+
if (combined !== void 0) pageLandings.push({ source: srcIndex + 1, combined });
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
metadataSources.push({ metadata: sourceMetadata, fields: placements, pages: pageLandings });
|
|
3121
3176
|
console.log(`[PageAssembler] Extracting fields from "${fileName}"`);
|
|
3122
3177
|
const extractedFields = await extractFieldsFromPdf(pdfBytes2);
|
|
3123
3178
|
console.log(`[PageAssembler] Found ${extractedFields.length} fields in "${fileName}"`);
|
|
3124
|
-
const pageUsageForFile = filePageUsage.get(fileId) || /* @__PURE__ */ new Map();
|
|
3125
3179
|
for (const field of extractedFields) {
|
|
3126
3180
|
const originalPage = field.page;
|
|
3127
3181
|
const srcIndex = originalPage - 1;
|
|
@@ -3145,6 +3199,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
3145
3199
|
label: field.label,
|
|
3146
3200
|
required: field.required,
|
|
3147
3201
|
options: field.options,
|
|
3202
|
+
optionPositions: field.optionPositions,
|
|
3148
3203
|
placeholder: field.placeholder,
|
|
3149
3204
|
fieldId: field.fieldId,
|
|
3150
3205
|
assignedSignerEmail: field.signer
|
|
@@ -3249,6 +3304,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
3249
3304
|
label: field.label,
|
|
3250
3305
|
required: field.required,
|
|
3251
3306
|
options: field.options,
|
|
3307
|
+
optionPositions: field.optionPositions,
|
|
3252
3308
|
placeholder: field.placeholder,
|
|
3253
3309
|
assignedSignerEmail: field.assignedSignerEmail
|
|
3254
3310
|
}));
|