@signiphi/page-assembly 0.2.0-beta.17 → 0.2.0-beta.18
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 +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +117 -66
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +118 -67
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +0 -8
- package/package.json +94 -93
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,
|
|
17
|
+
import { PDFDocument, PDFTextField, PDFCheckBox, PDFDropdown, PDFRadioGroup, PDFHexString, PDFName, 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) {
|
|
@@ -316,16 +316,23 @@ var AlertDialogOverlay = React7.forwardRef(({ className, ...props }, ref) => /*
|
|
|
316
316
|
}
|
|
317
317
|
));
|
|
318
318
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
319
|
-
var
|
|
319
|
+
var CENTERED = {
|
|
320
|
+
position: "fixed",
|
|
321
|
+
left: "50%",
|
|
322
|
+
top: "50%",
|
|
323
|
+
transform: "translate(-50%, -50%)"
|
|
324
|
+
};
|
|
325
|
+
var AlertDialogContent = React7.forwardRef(({ className, style, ...props }, ref) => /* @__PURE__ */ jsxs(AlertDialogPortal, { container: document.querySelector(".signiphi-page-assembly"), children: [
|
|
320
326
|
/* @__PURE__ */ jsx(AlertDialogOverlay, {}),
|
|
321
327
|
/* @__PURE__ */ jsx(
|
|
322
328
|
AlertDialogPrimitive.Content,
|
|
323
329
|
{
|
|
324
330
|
ref,
|
|
325
331
|
className: cn(
|
|
326
|
-
"
|
|
332
|
+
"z-[9999] grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
|
|
327
333
|
className
|
|
328
334
|
),
|
|
335
|
+
style: { ...CENTERED, ...style },
|
|
329
336
|
...props
|
|
330
337
|
}
|
|
331
338
|
)
|
|
@@ -1374,6 +1381,46 @@ function downloadPdf(pdfBytes, filename) {
|
|
|
1374
1381
|
a.click();
|
|
1375
1382
|
URL.revokeObjectURL(url);
|
|
1376
1383
|
}
|
|
1384
|
+
var KEY = "SigniphiMetadata";
|
|
1385
|
+
function readMetadata(doc, pdfLib) {
|
|
1386
|
+
let blockPresent = false;
|
|
1387
|
+
try {
|
|
1388
|
+
const infoRef = doc.context.trailerInfo.Info;
|
|
1389
|
+
if (!infoRef) return { metadata: null, blockPresent };
|
|
1390
|
+
const info = doc.context.lookup(infoRef);
|
|
1391
|
+
if (!info || typeof info.get !== "function") return { metadata: null, blockPresent };
|
|
1392
|
+
const value = info.get(pdfLib.PDFName.of(KEY));
|
|
1393
|
+
if (!value) return { metadata: null, blockPresent };
|
|
1394
|
+
blockPresent = true;
|
|
1395
|
+
const json = typeof value.decodeText === "function" ? value.decodeText() : stripDelimiters(value.toString());
|
|
1396
|
+
return { metadata: JSON.parse(json), blockPresent };
|
|
1397
|
+
} catch {
|
|
1398
|
+
return { metadata: null, blockPresent };
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function writeMetadata(doc, metadata, pdfLib) {
|
|
1402
|
+
const info = doc.context.lookup(doc.context.trailerInfo.Info);
|
|
1403
|
+
if (!info || typeof info.set !== "function") {
|
|
1404
|
+
throw new Error("Cannot write SigniphiMetadata: the PDF has no Info dictionary");
|
|
1405
|
+
}
|
|
1406
|
+
info.set(pdfLib.PDFName.of(KEY), pdfLib.PDFHexString.fromText(JSON.stringify(metadata)));
|
|
1407
|
+
}
|
|
1408
|
+
function stripDelimiters(raw) {
|
|
1409
|
+
const wrapped = raw.startsWith("(") && raw.endsWith(")") || raw.startsWith("<") && raw.endsWith(">");
|
|
1410
|
+
return wrapped ? raw.slice(1, -1) : raw;
|
|
1411
|
+
}
|
|
1412
|
+
function textLabelPlacements(source) {
|
|
1413
|
+
const fields = source.metadata?.fields;
|
|
1414
|
+
if (!fields || !source.pages?.length) return [];
|
|
1415
|
+
const out = [];
|
|
1416
|
+
for (const [name, entry] of Object.entries(fields)) {
|
|
1417
|
+
if (entry.type !== "text_label" || !entry.position) continue;
|
|
1418
|
+
for (const { source: page, combined } of source.pages) {
|
|
1419
|
+
if (page === entry.position.page) out.push({ name, page: combined });
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return out;
|
|
1423
|
+
}
|
|
1377
1424
|
function toCombinedPage(entry, page) {
|
|
1378
1425
|
if (!entry.position && !entry.radioPositions && !entry.radioOptions) return entry;
|
|
1379
1426
|
const carried = { ...entry };
|
|
@@ -1384,78 +1431,55 @@ function toCombinedPage(entry, page) {
|
|
|
1384
1431
|
}
|
|
1385
1432
|
return carried;
|
|
1386
1433
|
}
|
|
1387
|
-
function
|
|
1434
|
+
function assembleMetadata(sources) {
|
|
1388
1435
|
const merged = { version: "1.0", fields: {}, fieldIdIndex: {} };
|
|
1389
1436
|
let radioLabelsBaked;
|
|
1390
1437
|
let textLabelsBaked;
|
|
1391
1438
|
let checkboxLabelsBaked;
|
|
1439
|
+
let legacyRadioStripped;
|
|
1392
1440
|
for (const source of sources) {
|
|
1393
1441
|
const meta = source.metadata;
|
|
1394
|
-
if (!meta || source.fields.length === 0) continue;
|
|
1442
|
+
if (!meta || source.fields.length === 0 && !source.pages?.length) continue;
|
|
1395
1443
|
if (meta.radioLabelsBaked === false) radioLabelsBaked = false;
|
|
1396
1444
|
else if (meta.radioLabelsBaked === true && radioLabelsBaked === void 0) radioLabelsBaked = true;
|
|
1397
1445
|
if (meta.textLabelsBaked === false) textLabelsBaked = false;
|
|
1398
1446
|
else if (meta.textLabelsBaked === true && textLabelsBaked === void 0) textLabelsBaked = true;
|
|
1399
1447
|
if (meta.checkboxLabelsBaked === false) checkboxLabelsBaked = false;
|
|
1400
1448
|
else if (meta.checkboxLabelsBaked === true && checkboxLabelsBaked === void 0) checkboxLabelsBaked = true;
|
|
1449
|
+
legacyRadioStripped = meta.legacyRadioStripped === true && legacyRadioStripped !== false;
|
|
1401
1450
|
}
|
|
1402
1451
|
for (const source of sources) {
|
|
1403
1452
|
const meta = source.metadata;
|
|
1404
1453
|
if (!meta) continue;
|
|
1405
1454
|
const sourceTextLabelsBaked = meta.textLabelsBaked !== false;
|
|
1406
|
-
for (const { name, page } of source.fields) {
|
|
1455
|
+
for (const { name, page } of [...source.fields, ...textLabelPlacements(source)]) {
|
|
1407
1456
|
const key = `${name}_p${page}`;
|
|
1408
1457
|
const entry = meta.fields?.[name];
|
|
1409
|
-
if (entry)
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
const fieldId = meta.fieldIdIndex?.[name];
|
|
1414
|
-
if (fieldId) merged.fieldIdIndex[key] = fieldId;
|
|
1458
|
+
if (!entry) continue;
|
|
1459
|
+
const carried = toCombinedPage(entry, page);
|
|
1460
|
+
merged.fields[key] = textLabelsBaked === false && sourceTextLabelsBaked ? { ...carried, textLabelBaked: true } : carried;
|
|
1461
|
+
if (entry.fieldId) merged.fieldIdIndex[entry.fieldId] = key;
|
|
1415
1462
|
}
|
|
1416
1463
|
}
|
|
1417
1464
|
if (radioLabelsBaked !== void 0) merged.radioLabelsBaked = radioLabelsBaked;
|
|
1418
1465
|
if (textLabelsBaked !== void 0) merged.textLabelsBaked = textLabelsBaked;
|
|
1419
1466
|
if (checkboxLabelsBaked !== void 0) merged.checkboxLabelsBaked = checkboxLabelsBaked;
|
|
1467
|
+
if (legacyRadioStripped) merged.legacyRadioStripped = true;
|
|
1420
1468
|
const hasEntries = Object.keys(merged.fields).length > 0;
|
|
1421
1469
|
const hasMarkers = radioLabelsBaked !== void 0 || textLabelsBaked !== void 0 || checkboxLabelsBaked !== void 0;
|
|
1422
1470
|
if (!hasEntries && !hasMarkers) return null;
|
|
1423
1471
|
if (Object.keys(merged.fieldIdIndex).length === 0) delete merged.fieldIdIndex;
|
|
1424
1472
|
return merged;
|
|
1425
1473
|
}
|
|
1474
|
+
function mergeSourceMetadata(sources) {
|
|
1475
|
+
return assembleMetadata(sources);
|
|
1476
|
+
}
|
|
1426
1477
|
function getSigniphiMetadata(pdfDoc) {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
return null;
|
|
1431
|
-
}
|
|
1432
|
-
const infoObj = pdfDoc.context.lookup(infoRef);
|
|
1433
|
-
if (!infoObj || typeof infoObj.get !== "function") {
|
|
1434
|
-
return null;
|
|
1435
|
-
}
|
|
1436
|
-
const infoDict = infoObj;
|
|
1437
|
-
const metadataObj = infoDict.get(PDFName.of("SigniphiMetadata"));
|
|
1438
|
-
if (!metadataObj) {
|
|
1439
|
-
return null;
|
|
1440
|
-
}
|
|
1441
|
-
let jsonStr;
|
|
1442
|
-
if (typeof metadataObj.decodeText === "function") {
|
|
1443
|
-
jsonStr = metadataObj.decodeText();
|
|
1444
|
-
} else {
|
|
1445
|
-
const metadataStr = metadataObj.toString();
|
|
1446
|
-
jsonStr = metadataStr;
|
|
1447
|
-
if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
|
|
1448
|
-
jsonStr = metadataStr.slice(1, -1);
|
|
1449
|
-
} else if (metadataStr.startsWith("<") && metadataStr.endsWith(">")) {
|
|
1450
|
-
jsonStr = metadataStr.slice(1, -1);
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
const metadata = JSON.parse(jsonStr);
|
|
1454
|
-
return metadata;
|
|
1455
|
-
} catch (error) {
|
|
1456
|
-
console.error("Failed to load Signiphi metadata:", error);
|
|
1457
|
-
return null;
|
|
1478
|
+
const { metadata, blockPresent } = readMetadata(pdfDoc, { PDFName: PDFName });
|
|
1479
|
+
if (blockPresent && !metadata) {
|
|
1480
|
+
console.error("Failed to load Signiphi metadata: the block is present but will not parse");
|
|
1458
1481
|
}
|
|
1482
|
+
return metadata;
|
|
1459
1483
|
}
|
|
1460
1484
|
function getBaseFieldName(encodedName) {
|
|
1461
1485
|
let baseName = encodedName;
|
|
@@ -1514,6 +1538,10 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1514
1538
|
const metadata = getSigniphiMetadata(pdfDoc);
|
|
1515
1539
|
const form = pdfDoc.getForm();
|
|
1516
1540
|
const pdfFields = form.getFields();
|
|
1541
|
+
const pages = pdfDoc.getPages();
|
|
1542
|
+
const pageHeights = pages.map((p) => p.getHeight());
|
|
1543
|
+
const pageIndexByRef = new Map(pages.map((p, i) => [p.ref, i + 1]));
|
|
1544
|
+
const pageNumberOf = (ref) => ref && pageIndexByRef.get(ref) || 1;
|
|
1517
1545
|
for (const field of pdfFields) {
|
|
1518
1546
|
const fieldName = field.getName();
|
|
1519
1547
|
const widgets = field.acroField.getWidgets();
|
|
@@ -1542,19 +1570,20 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1542
1570
|
} else {
|
|
1543
1571
|
console.log(`[field-extraction] Unknown field type "${fieldName}" - defaulting required=false`);
|
|
1544
1572
|
}
|
|
1573
|
+
const optionPositionsByPage = /* @__PURE__ */ new Map();
|
|
1574
|
+
if (widgetType === "radio") {
|
|
1575
|
+
for (const w of widgets) {
|
|
1576
|
+
const pageNumber = pageNumberOf(w.P());
|
|
1577
|
+
const r = w.getRectangle();
|
|
1578
|
+
const top = pageHeights[pageNumber - 1] - r.y - r.height;
|
|
1579
|
+
const bucket = optionPositionsByPage.get(pageNumber) ?? [];
|
|
1580
|
+
bucket.push({ x: r.x, y: top, width: r.width, height: r.height });
|
|
1581
|
+
optionPositionsByPage.set(pageNumber, bucket);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1545
1584
|
for (const widget of widgets) {
|
|
1546
1585
|
const rect = widget.getRectangle();
|
|
1547
|
-
const
|
|
1548
|
-
let pageNumber = 1;
|
|
1549
|
-
if (pageRef) {
|
|
1550
|
-
const pages = pdfDoc.getPages();
|
|
1551
|
-
for (let i = 0; i < pages.length; i++) {
|
|
1552
|
-
if (pages[i].ref === pageRef) {
|
|
1553
|
-
pageNumber = i + 1;
|
|
1554
|
-
break;
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1586
|
+
const pageNumber = pageNumberOf(widget.P());
|
|
1558
1587
|
const pageQualifiedKey = `${baseName}_p${pageNumber}`;
|
|
1559
1588
|
let fieldMetadata = metadata?.fields?.[pageQualifiedKey];
|
|
1560
1589
|
if (!fieldMetadata) {
|
|
@@ -1571,8 +1600,7 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1571
1600
|
if (fieldMetadata?.options?.length) {
|
|
1572
1601
|
fieldOptions = fieldMetadata.options;
|
|
1573
1602
|
}
|
|
1574
|
-
const
|
|
1575
|
-
const pageHeight = page.getHeight();
|
|
1603
|
+
const pageHeight = pageHeights[pageNumber - 1];
|
|
1576
1604
|
const extractedField = {
|
|
1577
1605
|
name: baseName,
|
|
1578
1606
|
// Use clean base name without __SIGNER__ encoding (signer stored in signer property)
|
|
@@ -1589,6 +1617,9 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1589
1617
|
signer: signerFromName
|
|
1590
1618
|
// Extract signer from encoded name
|
|
1591
1619
|
};
|
|
1620
|
+
if (widgetType === "radio") {
|
|
1621
|
+
extractedField.optionPositions = optionPositionsByPage.get(pageNumber) ?? [];
|
|
1622
|
+
}
|
|
1592
1623
|
if (fieldMetadata) {
|
|
1593
1624
|
if (fieldMetadata.fieldId) {
|
|
1594
1625
|
extractedField.fieldId = fieldMetadata.fieldId;
|
|
@@ -1634,20 +1665,14 @@ async function extractFieldsFromPdf(pdfBytes) {
|
|
|
1634
1665
|
}
|
|
1635
1666
|
function setSigniphiMetadata(pdfDoc, metadata) {
|
|
1636
1667
|
try {
|
|
1637
|
-
|
|
1638
|
-
const infoObj = pdfDoc.context.lookup(infoRef);
|
|
1639
|
-
if (!infoObj || typeof infoObj.set !== "function") {
|
|
1640
|
-
throw new Error("Info object is not a PDFDict or does not have set method");
|
|
1641
|
-
}
|
|
1642
|
-
const infoDict = infoObj;
|
|
1643
|
-
const metadataString = JSON.stringify(metadata);
|
|
1644
|
-
infoDict.set(PDFName.of("SigniphiMetadata"), PDFHexString.fromText(metadataString));
|
|
1668
|
+
writeMetadata(pdfDoc, metadata, { PDFName: PDFName, PDFHexString: PDFHexString });
|
|
1645
1669
|
} catch (error) {
|
|
1646
1670
|
console.error("Failed to set Signiphi metadata:", error);
|
|
1647
1671
|
throw error;
|
|
1648
1672
|
}
|
|
1649
1673
|
}
|
|
1650
1674
|
var SIGNIPHI_CHECKBOX_LABEL_MARKER = "SiPL_Check";
|
|
1675
|
+
var RADIO_BORDER_WIDTH = 1;
|
|
1651
1676
|
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
1652
1677
|
FormFieldType2["TEXT"] = "text";
|
|
1653
1678
|
FormFieldType2["SIGNATURE"] = "signature";
|
|
@@ -1944,7 +1969,24 @@ async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
|
|
|
1944
1969
|
case "radio" /* RADIO */: {
|
|
1945
1970
|
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1946
1971
|
const radioGroup = form.createRadioGroup(encodedName);
|
|
1947
|
-
if (field.options && field.options.length > 0) {
|
|
1972
|
+
if (field.options && field.options.length > 0 && field.optionPositions?.length === field.options.length) {
|
|
1973
|
+
const optionPositions = field.optionPositions;
|
|
1974
|
+
field.options.forEach((option, index) => {
|
|
1975
|
+
const at = optionPositions[index];
|
|
1976
|
+
radioGroup.addOptionToPage(option, page, {
|
|
1977
|
+
x: at.x + RADIO_BORDER_WIDTH / 2,
|
|
1978
|
+
y: pageHeight - at.y - at.height + RADIO_BORDER_WIDTH / 2,
|
|
1979
|
+
width: at.width - RADIO_BORDER_WIDTH,
|
|
1980
|
+
height: at.height - RADIO_BORDER_WIDTH,
|
|
1981
|
+
borderWidth: RADIO_BORDER_WIDTH,
|
|
1982
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1983
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1984
|
+
});
|
|
1985
|
+
});
|
|
1986
|
+
if (field.defaultValue && field.options.includes(field.defaultValue)) {
|
|
1987
|
+
radioGroup.select(field.defaultValue);
|
|
1988
|
+
}
|
|
1989
|
+
} else if (field.options && field.options.length > 0) {
|
|
1948
1990
|
const optionHeight = Math.min(20, field.position.height / field.options.length);
|
|
1949
1991
|
const spacing = field.position.height / field.options.length;
|
|
1950
1992
|
field.options.forEach((option, index) => {
|
|
@@ -3089,12 +3131,19 @@ var PageAssembler = forwardRef(
|
|
|
3089
3131
|
if (sourcePdf && !sourceMetadata) {
|
|
3090
3132
|
console.warn(`[PageAssembler] No readable SigniphiMetadata in "${fileName}"`);
|
|
3091
3133
|
}
|
|
3134
|
+
const pageUsageForFile = filePageUsage.get(fileId) || /* @__PURE__ */ new Map();
|
|
3092
3135
|
const placements = [];
|
|
3093
|
-
|
|
3136
|
+
const pageLandings = [];
|
|
3137
|
+
for (const [srcIndex, pageInstances] of pageUsageForFile) {
|
|
3138
|
+
for (const pageInstance of pageInstances) {
|
|
3139
|
+
const combined = pageIdToNewPageNumber.get(pageInstance.id);
|
|
3140
|
+
if (combined !== void 0) pageLandings.push({ source: srcIndex + 1, combined });
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
metadataSources.push({ metadata: sourceMetadata, fields: placements, pages: pageLandings });
|
|
3094
3144
|
console.log(`[PageAssembler] Extracting fields from "${fileName}"`);
|
|
3095
3145
|
const extractedFields = await extractFieldsFromPdf(pdfBytes2);
|
|
3096
3146
|
console.log(`[PageAssembler] Found ${extractedFields.length} fields in "${fileName}"`);
|
|
3097
|
-
const pageUsageForFile = filePageUsage.get(fileId) || /* @__PURE__ */ new Map();
|
|
3098
3147
|
for (const field of extractedFields) {
|
|
3099
3148
|
const originalPage = field.page;
|
|
3100
3149
|
const srcIndex = originalPage - 1;
|
|
@@ -3118,6 +3167,7 @@ var PageAssembler = forwardRef(
|
|
|
3118
3167
|
label: field.label,
|
|
3119
3168
|
required: field.required,
|
|
3120
3169
|
options: field.options,
|
|
3170
|
+
optionPositions: field.optionPositions,
|
|
3121
3171
|
placeholder: field.placeholder,
|
|
3122
3172
|
fieldId: field.fieldId,
|
|
3123
3173
|
assignedSignerEmail: field.signer
|
|
@@ -3222,6 +3272,7 @@ var PageAssembler = forwardRef(
|
|
|
3222
3272
|
label: field.label,
|
|
3223
3273
|
required: field.required,
|
|
3224
3274
|
options: field.options,
|
|
3275
|
+
optionPositions: field.optionPositions,
|
|
3225
3276
|
placeholder: field.placeholder,
|
|
3226
3277
|
assignedSignerEmail: field.assignedSignerEmail
|
|
3227
3278
|
}));
|