@signiphi/page-assembly 0.2.0-beta.13 → 0.2.0-beta.14
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 +68 -14
- package/dist/index.d.ts +68 -14
- package/dist/index.js +70 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -53
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +6 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -309,24 +309,78 @@ interface ExtractedField {
|
|
|
309
309
|
signer?: string;
|
|
310
310
|
}
|
|
311
311
|
/**
|
|
312
|
-
*
|
|
312
|
+
* One field's metadata entry, as stored in the block.
|
|
313
|
+
*/
|
|
314
|
+
interface SigniphiFieldMetadata {
|
|
315
|
+
fieldId?: string;
|
|
316
|
+
label?: string;
|
|
317
|
+
signer?: string;
|
|
318
|
+
placeholder?: string;
|
|
319
|
+
required?: boolean;
|
|
320
|
+
options?: string[];
|
|
321
|
+
acknowledgements?: Array<{
|
|
322
|
+
id: string;
|
|
323
|
+
title: string;
|
|
324
|
+
description: string;
|
|
325
|
+
}>;
|
|
326
|
+
/**
|
|
327
|
+
* TEXT_LABEL only: this entry's text is already in the page stream, so the
|
|
328
|
+
* sign-time pass must not redraw it. See `mergeSourceMetadata`, which sets it
|
|
329
|
+
* when a source that had baked its labels is merged with one that deferred.
|
|
330
|
+
*/
|
|
331
|
+
textLabelBaked?: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* Persisted geometry, written by `document-prepare` for RADIO and TEXT_LABEL
|
|
334
|
+
* fields only. Declared here for ONE reason: its `page` is the SOURCE
|
|
335
|
+
* document's page number, and the merge has to rewrite it to the combined
|
|
336
|
+
* one. Nothing in page-assembly reads x/y/width/height.
|
|
337
|
+
*/
|
|
338
|
+
position?: SigniphiFieldPosition;
|
|
339
|
+
/** Radio-only, per-option. Re-paged alongside {@link SigniphiFieldMetadata.position}. */
|
|
340
|
+
radioPositions?: SigniphiFieldPosition[];
|
|
341
|
+
/** Radio-only bound list (options ↔ circle positions); its pages are re-paged too. */
|
|
342
|
+
radioOptions?: Array<{
|
|
343
|
+
label: string;
|
|
344
|
+
exportValue: string;
|
|
345
|
+
position: SigniphiFieldPosition;
|
|
346
|
+
}>;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* A persisted field rect, as `document-prepare` writes it. `page` is 1-indexed
|
|
350
|
+
* and scoped to the document it was written in — which is exactly why the merge
|
|
351
|
+
* cannot carry it across unchanged.
|
|
352
|
+
*/
|
|
353
|
+
interface SigniphiFieldPosition {
|
|
354
|
+
x: number;
|
|
355
|
+
y: number;
|
|
356
|
+
width: number;
|
|
357
|
+
height: number;
|
|
358
|
+
page: number;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* SigniphiMetadata structure stored in PDF Info dictionary.
|
|
362
|
+
*
|
|
363
|
+
* Exported because `getSigniphiMetadata` / `setSigniphiMetadata` are public
|
|
364
|
+
* (`utils/index.ts`) and a caller cannot name their argument otherwise — which
|
|
365
|
+
* is what pushed the call sites here into `as never` casts that would have
|
|
366
|
+
* silenced any future shape mismatch.
|
|
313
367
|
*/
|
|
314
368
|
interface SigniphiMetadata {
|
|
315
369
|
version: string;
|
|
316
|
-
fields: Record<string,
|
|
317
|
-
fieldId?: string;
|
|
318
|
-
label?: string;
|
|
319
|
-
signer?: string;
|
|
320
|
-
placeholder?: string;
|
|
321
|
-
required?: boolean;
|
|
322
|
-
options?: string[];
|
|
323
|
-
acknowledgements?: Array<{
|
|
324
|
-
id: string;
|
|
325
|
-
title: string;
|
|
326
|
-
description: string;
|
|
327
|
-
}>;
|
|
328
|
-
}>;
|
|
370
|
+
fields: Record<string, SigniphiFieldMetadata>;
|
|
329
371
|
fieldIdIndex?: Record<string, string>;
|
|
372
|
+
/**
|
|
373
|
+
* Document-level: radio group and option text is already drawn into the page
|
|
374
|
+
* stream, so the sign-time pass must not redraw it. Absent on legacy PDFs,
|
|
375
|
+
* which pdf-signer reads as "draw".
|
|
376
|
+
*/
|
|
377
|
+
radioLabelsBaked?: boolean;
|
|
378
|
+
/**
|
|
379
|
+
* Document-level: `false` means TEXT_LABEL text was deliberately NOT baked
|
|
380
|
+
* (multi-signer), so the sign-time pass redraws it per signer. Absent/`true`
|
|
381
|
+
* means it is already in the page stream.
|
|
382
|
+
*/
|
|
383
|
+
textLabelsBaked?: boolean;
|
|
330
384
|
}
|
|
331
385
|
/**
|
|
332
386
|
* Extract SigniphiMetadata from a PDF document
|
package/dist/index.d.ts
CHANGED
|
@@ -309,24 +309,78 @@ interface ExtractedField {
|
|
|
309
309
|
signer?: string;
|
|
310
310
|
}
|
|
311
311
|
/**
|
|
312
|
-
*
|
|
312
|
+
* One field's metadata entry, as stored in the block.
|
|
313
|
+
*/
|
|
314
|
+
interface SigniphiFieldMetadata {
|
|
315
|
+
fieldId?: string;
|
|
316
|
+
label?: string;
|
|
317
|
+
signer?: string;
|
|
318
|
+
placeholder?: string;
|
|
319
|
+
required?: boolean;
|
|
320
|
+
options?: string[];
|
|
321
|
+
acknowledgements?: Array<{
|
|
322
|
+
id: string;
|
|
323
|
+
title: string;
|
|
324
|
+
description: string;
|
|
325
|
+
}>;
|
|
326
|
+
/**
|
|
327
|
+
* TEXT_LABEL only: this entry's text is already in the page stream, so the
|
|
328
|
+
* sign-time pass must not redraw it. See `mergeSourceMetadata`, which sets it
|
|
329
|
+
* when a source that had baked its labels is merged with one that deferred.
|
|
330
|
+
*/
|
|
331
|
+
textLabelBaked?: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* Persisted geometry, written by `document-prepare` for RADIO and TEXT_LABEL
|
|
334
|
+
* fields only. Declared here for ONE reason: its `page` is the SOURCE
|
|
335
|
+
* document's page number, and the merge has to rewrite it to the combined
|
|
336
|
+
* one. Nothing in page-assembly reads x/y/width/height.
|
|
337
|
+
*/
|
|
338
|
+
position?: SigniphiFieldPosition;
|
|
339
|
+
/** Radio-only, per-option. Re-paged alongside {@link SigniphiFieldMetadata.position}. */
|
|
340
|
+
radioPositions?: SigniphiFieldPosition[];
|
|
341
|
+
/** Radio-only bound list (options ↔ circle positions); its pages are re-paged too. */
|
|
342
|
+
radioOptions?: Array<{
|
|
343
|
+
label: string;
|
|
344
|
+
exportValue: string;
|
|
345
|
+
position: SigniphiFieldPosition;
|
|
346
|
+
}>;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* A persisted field rect, as `document-prepare` writes it. `page` is 1-indexed
|
|
350
|
+
* and scoped to the document it was written in — which is exactly why the merge
|
|
351
|
+
* cannot carry it across unchanged.
|
|
352
|
+
*/
|
|
353
|
+
interface SigniphiFieldPosition {
|
|
354
|
+
x: number;
|
|
355
|
+
y: number;
|
|
356
|
+
width: number;
|
|
357
|
+
height: number;
|
|
358
|
+
page: number;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* SigniphiMetadata structure stored in PDF Info dictionary.
|
|
362
|
+
*
|
|
363
|
+
* Exported because `getSigniphiMetadata` / `setSigniphiMetadata` are public
|
|
364
|
+
* (`utils/index.ts`) and a caller cannot name their argument otherwise — which
|
|
365
|
+
* is what pushed the call sites here into `as never` casts that would have
|
|
366
|
+
* silenced any future shape mismatch.
|
|
313
367
|
*/
|
|
314
368
|
interface SigniphiMetadata {
|
|
315
369
|
version: string;
|
|
316
|
-
fields: Record<string,
|
|
317
|
-
fieldId?: string;
|
|
318
|
-
label?: string;
|
|
319
|
-
signer?: string;
|
|
320
|
-
placeholder?: string;
|
|
321
|
-
required?: boolean;
|
|
322
|
-
options?: string[];
|
|
323
|
-
acknowledgements?: Array<{
|
|
324
|
-
id: string;
|
|
325
|
-
title: string;
|
|
326
|
-
description: string;
|
|
327
|
-
}>;
|
|
328
|
-
}>;
|
|
370
|
+
fields: Record<string, SigniphiFieldMetadata>;
|
|
329
371
|
fieldIdIndex?: Record<string, string>;
|
|
372
|
+
/**
|
|
373
|
+
* Document-level: radio group and option text is already drawn into the page
|
|
374
|
+
* stream, so the sign-time pass must not redraw it. Absent on legacy PDFs,
|
|
375
|
+
* which pdf-signer reads as "draw".
|
|
376
|
+
*/
|
|
377
|
+
radioLabelsBaked?: boolean;
|
|
378
|
+
/**
|
|
379
|
+
* Document-level: `false` means TEXT_LABEL text was deliberately NOT baked
|
|
380
|
+
* (multi-signer), so the sign-time pass redraws it per signer. Absent/`true`
|
|
381
|
+
* means it is already in the page stream.
|
|
382
|
+
*/
|
|
383
|
+
textLabelsBaked?: boolean;
|
|
330
384
|
}
|
|
331
385
|
/**
|
|
332
386
|
* Extract SigniphiMetadata from a PDF document
|
package/dist/index.js
CHANGED
|
@@ -1399,6 +1399,51 @@ function downloadPdf(pdfBytes, filename) {
|
|
|
1399
1399
|
a.click();
|
|
1400
1400
|
URL.revokeObjectURL(url);
|
|
1401
1401
|
}
|
|
1402
|
+
function toCombinedPage(entry, page) {
|
|
1403
|
+
if (!entry.position && !entry.radioPositions && !entry.radioOptions) return entry;
|
|
1404
|
+
const carried = { ...entry };
|
|
1405
|
+
if (entry.position) carried.position = { ...entry.position, page };
|
|
1406
|
+
if (entry.radioPositions) carried.radioPositions = entry.radioPositions.map((p) => ({ ...p, page }));
|
|
1407
|
+
if (entry.radioOptions) {
|
|
1408
|
+
carried.radioOptions = entry.radioOptions.map((o) => ({ ...o, position: { ...o.position, page } }));
|
|
1409
|
+
}
|
|
1410
|
+
return carried;
|
|
1411
|
+
}
|
|
1412
|
+
function mergeSourceMetadata(sources) {
|
|
1413
|
+
const merged = { version: "1.0", fields: {}, fieldIdIndex: {} };
|
|
1414
|
+
let radioLabelsBaked;
|
|
1415
|
+
let textLabelsBaked;
|
|
1416
|
+
for (const source of sources) {
|
|
1417
|
+
const meta = source.metadata;
|
|
1418
|
+
if (!meta || source.fields.length === 0) continue;
|
|
1419
|
+
if (meta.radioLabelsBaked === false) radioLabelsBaked = false;
|
|
1420
|
+
else if (meta.radioLabelsBaked === true && radioLabelsBaked === void 0) radioLabelsBaked = true;
|
|
1421
|
+
if (meta.textLabelsBaked === false) textLabelsBaked = false;
|
|
1422
|
+
else if (meta.textLabelsBaked === true && textLabelsBaked === void 0) textLabelsBaked = true;
|
|
1423
|
+
}
|
|
1424
|
+
for (const source of sources) {
|
|
1425
|
+
const meta = source.metadata;
|
|
1426
|
+
if (!meta) continue;
|
|
1427
|
+
const sourceTextLabelsBaked = meta.textLabelsBaked !== false;
|
|
1428
|
+
for (const { name, page } of source.fields) {
|
|
1429
|
+
const key = `${name}_p${page}`;
|
|
1430
|
+
const entry = meta.fields?.[name];
|
|
1431
|
+
if (entry) {
|
|
1432
|
+
const carried = toCombinedPage(entry, page);
|
|
1433
|
+
merged.fields[key] = textLabelsBaked === false && sourceTextLabelsBaked ? { ...carried, textLabelBaked: true } : carried;
|
|
1434
|
+
}
|
|
1435
|
+
const fieldId = meta.fieldIdIndex?.[name];
|
|
1436
|
+
if (fieldId) merged.fieldIdIndex[key] = fieldId;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (radioLabelsBaked !== void 0) merged.radioLabelsBaked = radioLabelsBaked;
|
|
1440
|
+
if (textLabelsBaked !== void 0) merged.textLabelsBaked = textLabelsBaked;
|
|
1441
|
+
const hasEntries = Object.keys(merged.fields).length > 0;
|
|
1442
|
+
const hasMarkers = radioLabelsBaked !== void 0 || textLabelsBaked !== void 0;
|
|
1443
|
+
if (!hasEntries && !hasMarkers) return null;
|
|
1444
|
+
if (Object.keys(merged.fieldIdIndex).length === 0) delete merged.fieldIdIndex;
|
|
1445
|
+
return merged;
|
|
1446
|
+
}
|
|
1402
1447
|
function getSigniphiMetadata(pdfDoc) {
|
|
1403
1448
|
try {
|
|
1404
1449
|
const infoRef = pdfDoc.context.trailerInfo.Info;
|
|
@@ -1414,12 +1459,17 @@ function getSigniphiMetadata(pdfDoc) {
|
|
|
1414
1459
|
if (!metadataObj) {
|
|
1415
1460
|
return null;
|
|
1416
1461
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
jsonStr = metadataStr
|
|
1462
|
+
let jsonStr;
|
|
1463
|
+
if (typeof metadataObj.decodeText === "function") {
|
|
1464
|
+
jsonStr = metadataObj.decodeText();
|
|
1465
|
+
} else {
|
|
1466
|
+
const metadataStr = metadataObj.toString();
|
|
1467
|
+
jsonStr = metadataStr;
|
|
1468
|
+
if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
|
|
1469
|
+
jsonStr = metadataStr.slice(1, -1);
|
|
1470
|
+
} else if (metadataStr.startsWith("<") && metadataStr.endsWith(">")) {
|
|
1471
|
+
jsonStr = metadataStr.slice(1, -1);
|
|
1472
|
+
}
|
|
1423
1473
|
}
|
|
1424
1474
|
const metadata = JSON.parse(jsonStr);
|
|
1425
1475
|
return metadata;
|
|
@@ -1612,7 +1662,7 @@ function setSigniphiMetadata(pdfDoc, metadata) {
|
|
|
1612
1662
|
}
|
|
1613
1663
|
const infoDict = infoObj;
|
|
1614
1664
|
const metadataString = JSON.stringify(metadata);
|
|
1615
|
-
infoDict.set(pdfLib.PDFName.of("SigniphiMetadata"), pdfLib.
|
|
1665
|
+
infoDict.set(pdfLib.PDFName.of("SigniphiMetadata"), pdfLib.PDFHexString.fromText(metadataString));
|
|
1616
1666
|
} catch (error) {
|
|
1617
1667
|
console.error("Failed to set Signiphi metadata:", error);
|
|
1618
1668
|
throw error;
|
|
@@ -2925,7 +2975,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
2925
2975
|
setIsLoading(true);
|
|
2926
2976
|
setProcessingStatus("Assembling PDF...");
|
|
2927
2977
|
try {
|
|
2928
|
-
const { PDFDocument: PDFDocument5, PDFName: PDFName4
|
|
2978
|
+
const { PDFDocument: PDFDocument5, PDFName: PDFName4 } = await import('pdf-lib');
|
|
2929
2979
|
const loadedPdfs = /* @__PURE__ */ new Map();
|
|
2930
2980
|
const fileMap = /* @__PURE__ */ new Map();
|
|
2931
2981
|
const pdfFiles = state.files.filter((f) => f.type === "pdf" && f.originalBytes?.length > 0);
|
|
@@ -3004,11 +3054,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
3004
3054
|
}
|
|
3005
3055
|
console.log("[PageAssembler] Starting multi-PDF assembly with field preservation");
|
|
3006
3056
|
const allExtractedFields = [];
|
|
3007
|
-
const
|
|
3008
|
-
version: "1.0",
|
|
3009
|
-
fields: {},
|
|
3010
|
-
fieldIdIndex: {}
|
|
3011
|
-
};
|
|
3057
|
+
const metadataSources = [];
|
|
3012
3058
|
const filePageUsage = /* @__PURE__ */ new Map();
|
|
3013
3059
|
for (const file of state.files) {
|
|
3014
3060
|
for (const page of file.pages) {
|
|
@@ -3033,28 +3079,12 @@ var PageAssembler = React7.forwardRef(
|
|
|
3033
3079
|
}
|
|
3034
3080
|
console.log("[PageAssembler] Page mapping:", Object.fromEntries(pageIdToNewPageNumber));
|
|
3035
3081
|
const extractAndMapFields = async (fileId, fileName, pdfBytes2, sourcePdf) => {
|
|
3036
|
-
|
|
3037
|
-
if (sourcePdf) {
|
|
3038
|
-
|
|
3039
|
-
const sourceInfoRef = sourcePdf.context.trailerInfo.Info;
|
|
3040
|
-
if (sourceInfoRef) {
|
|
3041
|
-
const sourceInfoDict = sourcePdf.context.lookup(sourceInfoRef);
|
|
3042
|
-
if (sourceInfoDict && typeof sourceInfoDict.get === "function") {
|
|
3043
|
-
const metadataObj = sourceInfoDict.get(PDFName4.of("SigniphiMetadata"));
|
|
3044
|
-
if (metadataObj) {
|
|
3045
|
-
const metadataStr = metadataObj.toString();
|
|
3046
|
-
let jsonStr = metadataStr;
|
|
3047
|
-
if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
|
|
3048
|
-
jsonStr = metadataStr.slice(1, -1);
|
|
3049
|
-
}
|
|
3050
|
-
sourceMetadata = JSON.parse(jsonStr);
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
|
-
}
|
|
3054
|
-
} catch (metadataError) {
|
|
3055
|
-
console.warn(`Could not read metadata from PDF ${fileName}:`, metadataError);
|
|
3056
|
-
}
|
|
3082
|
+
const sourceMetadata = sourcePdf ? getSigniphiMetadata(sourcePdf) : null;
|
|
3083
|
+
if (sourcePdf && !sourceMetadata) {
|
|
3084
|
+
console.warn(`[PageAssembler] No readable SigniphiMetadata in "${fileName}"`);
|
|
3057
3085
|
}
|
|
3086
|
+
const placements = [];
|
|
3087
|
+
metadataSources.push({ metadata: sourceMetadata, fields: placements });
|
|
3058
3088
|
console.log(`[PageAssembler] Extracting fields from "${fileName}"`);
|
|
3059
3089
|
const extractedFields = await extractFieldsFromPdf(pdfBytes2);
|
|
3060
3090
|
console.log(`[PageAssembler] Found ${extractedFields.length} fields in "${fileName}"`);
|
|
@@ -3088,13 +3118,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
3088
3118
|
};
|
|
3089
3119
|
allExtractedFields.push(trackedField);
|
|
3090
3120
|
console.log(`[PageAssembler] Mapped field "${field.name}" from page ${originalPage} to page ${newPageNumber}`);
|
|
3091
|
-
|
|
3092
|
-
if (sourceMetadata?.fields?.[field.name]) {
|
|
3093
|
-
mergedMetadata.fields[metadataKey] = sourceMetadata.fields[field.name];
|
|
3094
|
-
}
|
|
3095
|
-
if (sourceMetadata?.fieldIdIndex?.[field.name]) {
|
|
3096
|
-
mergedMetadata.fieldIdIndex[metadataKey] = sourceMetadata.fieldIdIndex[field.name];
|
|
3097
|
-
}
|
|
3121
|
+
placements.push({ name: field.name, page: newPageNumber });
|
|
3098
3122
|
}
|
|
3099
3123
|
}
|
|
3100
3124
|
}
|
|
@@ -3172,21 +3196,15 @@ var PageAssembler = React7.forwardRef(
|
|
|
3172
3196
|
}
|
|
3173
3197
|
}
|
|
3174
3198
|
}
|
|
3175
|
-
|
|
3199
|
+
const mergedMetadata = mergeSourceMetadata(metadataSources);
|
|
3200
|
+
if (mergedMetadata) {
|
|
3176
3201
|
try {
|
|
3177
|
-
|
|
3178
|
-
if (newInfoRef) {
|
|
3179
|
-
const newInfoDict = combinedDoc.context.lookup(newInfoRef);
|
|
3180
|
-
if (newInfoDict && typeof newInfoDict.set === "function") {
|
|
3181
|
-
newInfoDict.set(
|
|
3182
|
-
PDFName4.of("SigniphiMetadata"),
|
|
3183
|
-
PDFString4.of(JSON.stringify(mergedMetadata))
|
|
3184
|
-
);
|
|
3185
|
-
}
|
|
3186
|
-
}
|
|
3202
|
+
setSigniphiMetadata(combinedDoc, mergedMetadata);
|
|
3187
3203
|
} catch (metadataError) {
|
|
3188
3204
|
console.warn("Could not write merged metadata:", metadataError);
|
|
3189
3205
|
}
|
|
3206
|
+
} else {
|
|
3207
|
+
console.warn("[PageAssembler] No source metadata survived the merge; the assembled document will read as unprepared downstream");
|
|
3190
3208
|
}
|
|
3191
3209
|
let pdfBytes = await combinedDoc.save();
|
|
3192
3210
|
if (allExtractedFields.length > 0) {
|