@signiphi/page-assembly 0.2.0-beta.2 → 0.2.0-beta.20
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 +347 -1
- package/dist/index.d.ts +347 -1
- package/dist/index.js +1733 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1724 -59
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +238 -166
- package/dist/styles/index.d.ts +1 -0
- package/package.json +94 -85
- package/src/styles/index.css +164 -109
- package/src/styles/index.d.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import * as React7 from 'react';
|
|
|
2
2
|
import { forwardRef, useReducer, useState, useRef, useMemo, useEffect, useCallback, useImperativeHandle } from 'react';
|
|
3
3
|
import { DndProvider, useDrop, useDrag } from 'react-dnd';
|
|
4
4
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
5
|
-
import { ChevronDown, ChevronUp, Check, ChevronRight, Circle, Upload, FolderOpen, FileText, Image as Image$1, Trash2,
|
|
5
|
+
import { ChevronDown, ChevronUp, Check, ChevronRight, Circle, Upload, X, FolderOpen, FileText, Image as Image$1, Trash2, MoreHorizontal, GripVertical } from 'lucide-react';
|
|
6
6
|
import { Slot } from '@radix-ui/react-slot';
|
|
7
7
|
import { cva } from 'class-variance-authority';
|
|
8
8
|
import { clsx } from 'clsx';
|
|
@@ -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 } from 'pdf-lib';
|
|
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) {
|
|
@@ -220,8 +220,9 @@ var SelectContent = React7.forwardRef(({ className, children, position = "popper
|
|
|
220
220
|
position,
|
|
221
221
|
style: {
|
|
222
222
|
zIndex: 99999,
|
|
223
|
-
backgroundColor: "
|
|
224
|
-
|
|
223
|
+
backgroundColor: "hsl(var(--xpa-popover))",
|
|
224
|
+
color: "hsl(var(--xpa-popover-foreground))",
|
|
225
|
+
border: "1px solid hsl(var(--xpa-border))",
|
|
225
226
|
borderRadius: "0.5rem",
|
|
226
227
|
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
|
|
227
228
|
marginTop: "4px"
|
|
@@ -315,16 +316,23 @@ var AlertDialogOverlay = React7.forwardRef(({ className, ...props }, ref) => /*
|
|
|
315
316
|
}
|
|
316
317
|
));
|
|
317
318
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
318
|
-
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: [
|
|
319
326
|
/* @__PURE__ */ jsx(AlertDialogOverlay, {}),
|
|
320
327
|
/* @__PURE__ */ jsx(
|
|
321
328
|
AlertDialogPrimitive.Content,
|
|
322
329
|
{
|
|
323
330
|
ref,
|
|
324
331
|
className: cn(
|
|
325
|
-
"
|
|
332
|
+
"z-[9999] grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
|
|
326
333
|
className
|
|
327
334
|
),
|
|
335
|
+
style: { ...CENTERED, ...style },
|
|
328
336
|
...props
|
|
329
337
|
}
|
|
330
338
|
)
|
|
@@ -440,8 +448,9 @@ var DropdownMenuContent = React7.forwardRef(({ className, sideOffset = 4, ...pro
|
|
|
440
448
|
),
|
|
441
449
|
style: {
|
|
442
450
|
zIndex: 99999,
|
|
443
|
-
backgroundColor: "
|
|
444
|
-
|
|
451
|
+
backgroundColor: "hsl(var(--xpa-popover))",
|
|
452
|
+
color: "hsl(var(--xpa-popover-foreground))",
|
|
453
|
+
border: "1px solid hsl(var(--xpa-border))",
|
|
445
454
|
borderRadius: "0.5rem",
|
|
446
455
|
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
|
|
447
456
|
padding: "6px"
|
|
@@ -547,6 +556,623 @@ var TooltipContent = React7.forwardRef(({ className, sideOffset = 4, ...props },
|
|
|
547
556
|
}
|
|
548
557
|
));
|
|
549
558
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
559
|
+
var PdfPasswordRequiredError = class extends Error {
|
|
560
|
+
constructor() {
|
|
561
|
+
const message = "This PDF is password-protected. Please remove the password and upload it again.";
|
|
562
|
+
super(message);
|
|
563
|
+
this.code = "PDF_PASSWORD_REQUIRED";
|
|
564
|
+
this.recoverable = true;
|
|
565
|
+
this.name = "PdfPasswordRequiredError";
|
|
566
|
+
this.userMessage = message;
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
var UnsupportedPdfEncryptionError = class extends Error {
|
|
570
|
+
constructor(detail) {
|
|
571
|
+
super(`This PDF uses an unsupported encryption scheme (${detail}).`);
|
|
572
|
+
this.name = "UnsupportedPdfEncryptionError";
|
|
573
|
+
this.detail = detail;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
function concatBytes(...parts) {
|
|
577
|
+
let total = 0;
|
|
578
|
+
for (const p of parts) total += p.length;
|
|
579
|
+
const out = new Uint8Array(total);
|
|
580
|
+
let offset = 0;
|
|
581
|
+
for (const p of parts) {
|
|
582
|
+
out.set(p, offset);
|
|
583
|
+
offset += p.length;
|
|
584
|
+
}
|
|
585
|
+
return out;
|
|
586
|
+
}
|
|
587
|
+
var MD5_S = [
|
|
588
|
+
7,
|
|
589
|
+
12,
|
|
590
|
+
17,
|
|
591
|
+
22,
|
|
592
|
+
7,
|
|
593
|
+
12,
|
|
594
|
+
17,
|
|
595
|
+
22,
|
|
596
|
+
7,
|
|
597
|
+
12,
|
|
598
|
+
17,
|
|
599
|
+
22,
|
|
600
|
+
7,
|
|
601
|
+
12,
|
|
602
|
+
17,
|
|
603
|
+
22,
|
|
604
|
+
5,
|
|
605
|
+
9,
|
|
606
|
+
14,
|
|
607
|
+
20,
|
|
608
|
+
5,
|
|
609
|
+
9,
|
|
610
|
+
14,
|
|
611
|
+
20,
|
|
612
|
+
5,
|
|
613
|
+
9,
|
|
614
|
+
14,
|
|
615
|
+
20,
|
|
616
|
+
5,
|
|
617
|
+
9,
|
|
618
|
+
14,
|
|
619
|
+
20,
|
|
620
|
+
4,
|
|
621
|
+
11,
|
|
622
|
+
16,
|
|
623
|
+
23,
|
|
624
|
+
4,
|
|
625
|
+
11,
|
|
626
|
+
16,
|
|
627
|
+
23,
|
|
628
|
+
4,
|
|
629
|
+
11,
|
|
630
|
+
16,
|
|
631
|
+
23,
|
|
632
|
+
4,
|
|
633
|
+
11,
|
|
634
|
+
16,
|
|
635
|
+
23,
|
|
636
|
+
6,
|
|
637
|
+
10,
|
|
638
|
+
15,
|
|
639
|
+
21,
|
|
640
|
+
6,
|
|
641
|
+
10,
|
|
642
|
+
15,
|
|
643
|
+
21,
|
|
644
|
+
6,
|
|
645
|
+
10,
|
|
646
|
+
15,
|
|
647
|
+
21,
|
|
648
|
+
6,
|
|
649
|
+
10,
|
|
650
|
+
15,
|
|
651
|
+
21
|
|
652
|
+
];
|
|
653
|
+
var MD5_K = (() => {
|
|
654
|
+
const k = new Uint32Array(64);
|
|
655
|
+
for (let i = 0; i < 64; i++) k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 4294967296);
|
|
656
|
+
return k;
|
|
657
|
+
})();
|
|
658
|
+
function md5(input) {
|
|
659
|
+
const bitLen = input.length * 8;
|
|
660
|
+
const paddedLen = (input.length + 8 >> 6) + 1 << 6;
|
|
661
|
+
const buf = new Uint8Array(paddedLen);
|
|
662
|
+
buf.set(input);
|
|
663
|
+
buf[input.length] = 128;
|
|
664
|
+
const dv = new DataView(buf.buffer);
|
|
665
|
+
dv.setUint32(paddedLen - 8, bitLen >>> 0, true);
|
|
666
|
+
dv.setUint32(paddedLen - 4, Math.floor(bitLen / 4294967296), true);
|
|
667
|
+
let a0 = 1732584193;
|
|
668
|
+
let b0 = 4023233417;
|
|
669
|
+
let c0 = 2562383102;
|
|
670
|
+
let d0 = 271733878;
|
|
671
|
+
const M = new Uint32Array(16);
|
|
672
|
+
for (let offset = 0; offset < paddedLen; offset += 64) {
|
|
673
|
+
for (let i = 0; i < 16; i++) M[i] = dv.getUint32(offset + i * 4, true);
|
|
674
|
+
let A = a0;
|
|
675
|
+
let B = b0;
|
|
676
|
+
let C = c0;
|
|
677
|
+
let D = d0;
|
|
678
|
+
for (let i = 0; i < 64; i++) {
|
|
679
|
+
let F;
|
|
680
|
+
let g;
|
|
681
|
+
if (i < 16) {
|
|
682
|
+
F = B & C | ~B & D;
|
|
683
|
+
g = i;
|
|
684
|
+
} else if (i < 32) {
|
|
685
|
+
F = D & B | ~D & C;
|
|
686
|
+
g = 5 * i + 1 & 15;
|
|
687
|
+
} else if (i < 48) {
|
|
688
|
+
F = B ^ C ^ D;
|
|
689
|
+
g = 3 * i + 5 & 15;
|
|
690
|
+
} else {
|
|
691
|
+
F = C ^ (B | ~D);
|
|
692
|
+
g = 7 * i & 15;
|
|
693
|
+
}
|
|
694
|
+
const tmp = D;
|
|
695
|
+
D = C;
|
|
696
|
+
C = B;
|
|
697
|
+
const x = A + F + MD5_K[i] + M[g] | 0;
|
|
698
|
+
const s = MD5_S[i];
|
|
699
|
+
B = B + (x << s | x >>> 32 - s) | 0;
|
|
700
|
+
A = tmp;
|
|
701
|
+
}
|
|
702
|
+
a0 = a0 + A | 0;
|
|
703
|
+
b0 = b0 + B | 0;
|
|
704
|
+
c0 = c0 + C | 0;
|
|
705
|
+
d0 = d0 + D | 0;
|
|
706
|
+
}
|
|
707
|
+
const out = new Uint8Array(16);
|
|
708
|
+
const ov = new DataView(out.buffer);
|
|
709
|
+
ov.setUint32(0, a0 >>> 0, true);
|
|
710
|
+
ov.setUint32(4, b0 >>> 0, true);
|
|
711
|
+
ov.setUint32(8, c0 >>> 0, true);
|
|
712
|
+
ov.setUint32(12, d0 >>> 0, true);
|
|
713
|
+
return out;
|
|
714
|
+
}
|
|
715
|
+
function rc4(key, data) {
|
|
716
|
+
const S = new Uint8Array(256);
|
|
717
|
+
for (let i2 = 0; i2 < 256; i2++) S[i2] = i2;
|
|
718
|
+
let j = 0;
|
|
719
|
+
for (let i2 = 0; i2 < 256; i2++) {
|
|
720
|
+
j = j + S[i2] + key[i2 % key.length] & 255;
|
|
721
|
+
const t = S[i2];
|
|
722
|
+
S[i2] = S[j];
|
|
723
|
+
S[j] = t;
|
|
724
|
+
}
|
|
725
|
+
const out = new Uint8Array(data.length);
|
|
726
|
+
let i = 0;
|
|
727
|
+
j = 0;
|
|
728
|
+
for (let k = 0; k < data.length; k++) {
|
|
729
|
+
i = i + 1 & 255;
|
|
730
|
+
j = j + S[i] & 255;
|
|
731
|
+
const t = S[i];
|
|
732
|
+
S[i] = S[j];
|
|
733
|
+
S[j] = t;
|
|
734
|
+
out[k] = data[k] ^ S[S[i] + S[j] & 255];
|
|
735
|
+
}
|
|
736
|
+
return out;
|
|
737
|
+
}
|
|
738
|
+
function getSubtle() {
|
|
739
|
+
const subtle = globalThis.crypto?.subtle;
|
|
740
|
+
if (!subtle) throw new UnsupportedPdfEncryptionError("WebCrypto is unavailable in this environment");
|
|
741
|
+
return subtle;
|
|
742
|
+
}
|
|
743
|
+
async function sha2(algorithm, data) {
|
|
744
|
+
return new Uint8Array(await getSubtle().digest(algorithm, data));
|
|
745
|
+
}
|
|
746
|
+
async function importAesKey(rawKey, usages) {
|
|
747
|
+
return getSubtle().importKey("raw", rawKey, "AES-CBC", false, usages);
|
|
748
|
+
}
|
|
749
|
+
async function aesCbcEncryptNoPad(rawKey, iv, data) {
|
|
750
|
+
const key = await importAesKey(rawKey, ["encrypt"]);
|
|
751
|
+
const out = new Uint8Array(await getSubtle().encrypt({ name: "AES-CBC", iv }, key, data));
|
|
752
|
+
return out.slice(0, data.length);
|
|
753
|
+
}
|
|
754
|
+
async function aesCbcDecryptNoPad(rawKey, iv, data) {
|
|
755
|
+
const key = await importAesKey(rawKey, ["encrypt", "decrypt"]);
|
|
756
|
+
const lastBlock = data.slice(data.length - 16);
|
|
757
|
+
const padBlock = new Uint8Array(
|
|
758
|
+
await getSubtle().encrypt({ name: "AES-CBC", iv: lastBlock }, key, new Uint8Array(0))
|
|
759
|
+
);
|
|
760
|
+
const joined = concatBytes(data, padBlock);
|
|
761
|
+
return new Uint8Array(await getSubtle().decrypt({ name: "AES-CBC", iv }, key, joined));
|
|
762
|
+
}
|
|
763
|
+
async function aesDecryptPdfData(rawKey, data) {
|
|
764
|
+
if (data.length < 32) return new Uint8Array(0);
|
|
765
|
+
const iv = data.slice(0, 16);
|
|
766
|
+
let ct = data.slice(16);
|
|
767
|
+
if (ct.length % 16 !== 0) ct = ct.slice(0, ct.length - ct.length % 16);
|
|
768
|
+
try {
|
|
769
|
+
const key = await importAesKey(rawKey, ["decrypt"]);
|
|
770
|
+
return new Uint8Array(await getSubtle().decrypt({ name: "AES-CBC", iv }, key, ct));
|
|
771
|
+
} catch {
|
|
772
|
+
const plain = await aesCbcDecryptNoPad(rawKey, iv, ct);
|
|
773
|
+
const padLen = plain[plain.length - 1];
|
|
774
|
+
return padLen >= 1 && padLen <= 16 ? plain.slice(0, plain.length - padLen) : plain;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
var PAD = new Uint8Array([
|
|
778
|
+
40,
|
|
779
|
+
191,
|
|
780
|
+
78,
|
|
781
|
+
94,
|
|
782
|
+
78,
|
|
783
|
+
117,
|
|
784
|
+
138,
|
|
785
|
+
65,
|
|
786
|
+
100,
|
|
787
|
+
0,
|
|
788
|
+
78,
|
|
789
|
+
86,
|
|
790
|
+
255,
|
|
791
|
+
250,
|
|
792
|
+
1,
|
|
793
|
+
8,
|
|
794
|
+
46,
|
|
795
|
+
46,
|
|
796
|
+
0,
|
|
797
|
+
182,
|
|
798
|
+
208,
|
|
799
|
+
104,
|
|
800
|
+
62,
|
|
801
|
+
128,
|
|
802
|
+
47,
|
|
803
|
+
12,
|
|
804
|
+
169,
|
|
805
|
+
254,
|
|
806
|
+
100,
|
|
807
|
+
83,
|
|
808
|
+
105,
|
|
809
|
+
122
|
|
810
|
+
]);
|
|
811
|
+
var bytesEqual = (a, b) => {
|
|
812
|
+
if (a.length !== b.length) return false;
|
|
813
|
+
let diff = 0;
|
|
814
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
815
|
+
return diff === 0;
|
|
816
|
+
};
|
|
817
|
+
function legacyFileKey(paddedPwd, params) {
|
|
818
|
+
const { revision, keyLen, O, P, id0, encryptMetadata } = params;
|
|
819
|
+
const pBytes = new Uint8Array(4);
|
|
820
|
+
new DataView(pBytes.buffer).setInt32(0, P | 0, true);
|
|
821
|
+
const parts = [paddedPwd, O.slice(0, 32), pBytes, id0];
|
|
822
|
+
if (revision >= 4 && !encryptMetadata) parts.push(new Uint8Array([255, 255, 255, 255]));
|
|
823
|
+
let h = md5(concatBytes(...parts));
|
|
824
|
+
if (revision >= 3) for (let i = 0; i < 50; i++) h = md5(h.slice(0, keyLen));
|
|
825
|
+
return h.slice(0, keyLen);
|
|
826
|
+
}
|
|
827
|
+
function legacyComputeU(fileKey, revision, id0) {
|
|
828
|
+
if (revision === 2) return rc4(fileKey, PAD);
|
|
829
|
+
let d = md5(concatBytes(PAD, id0));
|
|
830
|
+
d = rc4(fileKey, d);
|
|
831
|
+
for (let i = 1; i <= 19; i++) d = rc4(fileKey.map((b) => b ^ i), d);
|
|
832
|
+
return d;
|
|
833
|
+
}
|
|
834
|
+
function legacyUMatches(fileKey, params) {
|
|
835
|
+
const computed = legacyComputeU(fileKey, params.revision, params.id0);
|
|
836
|
+
return params.revision === 2 ? bytesEqual(computed, params.U.slice(0, 32)) : bytesEqual(computed.slice(0, 16), params.U.slice(0, 16));
|
|
837
|
+
}
|
|
838
|
+
function legacyAuthenticateEmptyPassword(params) {
|
|
839
|
+
const userKey = legacyFileKey(PAD, params);
|
|
840
|
+
if (legacyUMatches(userKey, params)) return userKey;
|
|
841
|
+
throw new PdfPasswordRequiredError();
|
|
842
|
+
}
|
|
843
|
+
async function hash2B(password, salt, udata) {
|
|
844
|
+
let k = await sha2("SHA-256", concatBytes(password, salt, udata));
|
|
845
|
+
let e = new Uint8Array(0);
|
|
846
|
+
let i = 0;
|
|
847
|
+
do {
|
|
848
|
+
const block = concatBytes(password, k, udata);
|
|
849
|
+
const k1 = new Uint8Array(block.length * 64);
|
|
850
|
+
for (let rep = 0; rep < 64; rep++) k1.set(block, rep * block.length);
|
|
851
|
+
e = await aesCbcEncryptNoPad(k.slice(0, 16), k.slice(16, 32), k1);
|
|
852
|
+
let sum = 0;
|
|
853
|
+
for (let b = 0; b < 16; b++) sum += e[b];
|
|
854
|
+
const mod = sum % 3;
|
|
855
|
+
k = await sha2(mod === 0 ? "SHA-256" : mod === 1 ? "SHA-384" : "SHA-512", e);
|
|
856
|
+
i++;
|
|
857
|
+
} while (i < 64 || e[e.length - 1] > i - 32);
|
|
858
|
+
return k.slice(0, 32);
|
|
859
|
+
}
|
|
860
|
+
async function v5AuthenticateEmptyPassword(revision, U, UE) {
|
|
861
|
+
const pwd = new Uint8Array(0);
|
|
862
|
+
const zeroIv = new Uint8Array(16);
|
|
863
|
+
const hash = async (salt, udata) => revision === 5 ? sha2("SHA-256", concatBytes(pwd, salt, udata)) : hash2B(pwd, salt, udata);
|
|
864
|
+
const userHash = await hash(U.slice(32, 40), new Uint8Array(0));
|
|
865
|
+
if (bytesEqual(userHash, U.slice(0, 32))) {
|
|
866
|
+
const intermediate = await hash(U.slice(40, 48), new Uint8Array(0));
|
|
867
|
+
return aesCbcDecryptNoPad(intermediate, zeroIv, UE.slice(0, 32));
|
|
868
|
+
}
|
|
869
|
+
throw new PdfPasswordRequiredError();
|
|
870
|
+
}
|
|
871
|
+
var identityCipher = async (data) => data;
|
|
872
|
+
function rc4ObjectCipher(fileKey) {
|
|
873
|
+
return async (data, ref) => {
|
|
874
|
+
const ext = new Uint8Array([
|
|
875
|
+
ref.objectNumber & 255,
|
|
876
|
+
ref.objectNumber >> 8 & 255,
|
|
877
|
+
ref.objectNumber >> 16 & 255,
|
|
878
|
+
ref.generationNumber & 255,
|
|
879
|
+
ref.generationNumber >> 8 & 255
|
|
880
|
+
]);
|
|
881
|
+
const key = md5(concatBytes(fileKey, ext)).slice(0, Math.min(fileKey.length + 5, 16));
|
|
882
|
+
return rc4(key, data);
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
var AES_SALT = new Uint8Array([115, 65, 108, 84]);
|
|
886
|
+
function aes128ObjectCipher(fileKey) {
|
|
887
|
+
return async (data, ref) => {
|
|
888
|
+
const ext = new Uint8Array([
|
|
889
|
+
ref.objectNumber & 255,
|
|
890
|
+
ref.objectNumber >> 8 & 255,
|
|
891
|
+
ref.objectNumber >> 16 & 255,
|
|
892
|
+
ref.generationNumber & 255,
|
|
893
|
+
ref.generationNumber >> 8 & 255
|
|
894
|
+
]);
|
|
895
|
+
const key = md5(concatBytes(fileKey, ext, AES_SALT)).slice(0, 16);
|
|
896
|
+
return aesDecryptPdfData(key, data);
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function aes256ObjectCipher(fileKey) {
|
|
900
|
+
return async (data) => aesDecryptPdfData(fileKey, data);
|
|
901
|
+
}
|
|
902
|
+
function dictNumber(dict, key) {
|
|
903
|
+
const value = dict.lookup(PDFName.of(key));
|
|
904
|
+
return value instanceof PDFNumber ? value.asNumber() : void 0;
|
|
905
|
+
}
|
|
906
|
+
function dictBytes(dict, key) {
|
|
907
|
+
const value = dict.lookup(PDFName.of(key));
|
|
908
|
+
return value instanceof PDFString || value instanceof PDFHexString ? value.asBytes() : void 0;
|
|
909
|
+
}
|
|
910
|
+
async function buildDocumentCiphers(encDict, id0) {
|
|
911
|
+
const filter = encDict.lookup(PDFName.of("Filter"));
|
|
912
|
+
if (filter !== PDFName.of("Standard")) {
|
|
913
|
+
throw new UnsupportedPdfEncryptionError(`security handler ${String(filter)}`);
|
|
914
|
+
}
|
|
915
|
+
const V = dictNumber(encDict, "V") ?? 0;
|
|
916
|
+
const R = dictNumber(encDict, "R") ?? 0;
|
|
917
|
+
const O = dictBytes(encDict, "O");
|
|
918
|
+
const U = dictBytes(encDict, "U");
|
|
919
|
+
const P = dictNumber(encDict, "P") ?? -1;
|
|
920
|
+
const metadataValue = encDict.lookup(PDFName.of("EncryptMetadata"));
|
|
921
|
+
const encryptMetadata = metadataValue instanceof PDFBool ? metadataValue.asBoolean() : true;
|
|
922
|
+
if (!O || !U) throw new UnsupportedPdfEncryptionError("missing /O or /U");
|
|
923
|
+
if (V === 1 || V === 2) {
|
|
924
|
+
const keyLen = V === 1 ? 5 : (dictNumber(encDict, "Length") ?? 40) / 8;
|
|
925
|
+
const fileKey = legacyAuthenticateEmptyPassword({ revision: R, keyLen, O, U, P, id0, encryptMetadata });
|
|
926
|
+
const cipher = rc4ObjectCipher(fileKey);
|
|
927
|
+
return { stringCipher: cipher, streamCipher: cipher, encryptMetadata };
|
|
928
|
+
}
|
|
929
|
+
if (V === 4 || V === 5) {
|
|
930
|
+
const cf = encDict.lookup(PDFName.of("CF"));
|
|
931
|
+
const cfmOf = (filterKey) => {
|
|
932
|
+
const streamFilter = encDict.lookup(PDFName.of(filterKey));
|
|
933
|
+
if (!(streamFilter instanceof PDFName) || streamFilter === PDFName.of("Identity")) return void 0;
|
|
934
|
+
if (!(cf instanceof PDFDict)) throw new UnsupportedPdfEncryptionError("missing /CF");
|
|
935
|
+
const cfDict = cf.lookup(streamFilter);
|
|
936
|
+
if (!(cfDict instanceof PDFDict)) throw new UnsupportedPdfEncryptionError(`missing crypt filter ${streamFilter.toString()}`);
|
|
937
|
+
const cfm = cfDict.lookup(PDFName.of("CFM"));
|
|
938
|
+
return cfm instanceof PDFName ? cfm : void 0;
|
|
939
|
+
};
|
|
940
|
+
let fileKey;
|
|
941
|
+
if (V === 5) {
|
|
942
|
+
const UE = dictBytes(encDict, "UE");
|
|
943
|
+
if (!UE) throw new UnsupportedPdfEncryptionError("missing /UE");
|
|
944
|
+
fileKey = await v5AuthenticateEmptyPassword(R, U, UE);
|
|
945
|
+
} else {
|
|
946
|
+
const cryptFilterLengthBits = () => {
|
|
947
|
+
if (!(cf instanceof PDFDict)) return void 0;
|
|
948
|
+
for (const filterKey of ["StmF", "StrF"]) {
|
|
949
|
+
const filter2 = encDict.lookup(PDFName.of(filterKey));
|
|
950
|
+
if (!(filter2 instanceof PDFName) || filter2 === PDFName.of("Identity")) continue;
|
|
951
|
+
const cfDict = cf.lookup(filter2);
|
|
952
|
+
if (!(cfDict instanceof PDFDict)) continue;
|
|
953
|
+
const len = dictNumber(cfDict, "Length");
|
|
954
|
+
if (len === void 0) continue;
|
|
955
|
+
return len < 40 ? len * 8 : len;
|
|
956
|
+
}
|
|
957
|
+
return void 0;
|
|
958
|
+
};
|
|
959
|
+
const keyLenBits = cryptFilterLengthBits() ?? dictNumber(encDict, "Length") ?? 128;
|
|
960
|
+
fileKey = legacyAuthenticateEmptyPassword({
|
|
961
|
+
revision: R,
|
|
962
|
+
keyLen: keyLenBits / 8,
|
|
963
|
+
O,
|
|
964
|
+
U,
|
|
965
|
+
P,
|
|
966
|
+
id0,
|
|
967
|
+
encryptMetadata
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
const cipherFor = (cfm) => {
|
|
971
|
+
if (!cfm || cfm === PDFName.of("Identity")) return identityCipher;
|
|
972
|
+
if (cfm === PDFName.of("V2")) return rc4ObjectCipher(fileKey);
|
|
973
|
+
if (cfm === PDFName.of("AESV2")) return aes128ObjectCipher(fileKey);
|
|
974
|
+
if (cfm === PDFName.of("AESV3")) return aes256ObjectCipher(fileKey);
|
|
975
|
+
throw new UnsupportedPdfEncryptionError(`crypt filter method ${cfm.toString()}`);
|
|
976
|
+
};
|
|
977
|
+
return {
|
|
978
|
+
streamCipher: cipherFor(cfmOf("StmF")),
|
|
979
|
+
stringCipher: cipherFor(cfmOf("StrF")),
|
|
980
|
+
encryptMetadata
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
throw new UnsupportedPdfEncryptionError(`/V ${V}`);
|
|
984
|
+
}
|
|
985
|
+
var toHexString = (bytes) => {
|
|
986
|
+
let hex = "";
|
|
987
|
+
for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, "0");
|
|
988
|
+
return new PDFHexString(hex.toUpperCase());
|
|
989
|
+
};
|
|
990
|
+
function filterIncludesCrypt(dict) {
|
|
991
|
+
const filter = dict.lookup(PDFName.of("Filter"));
|
|
992
|
+
if (filter === PDFName.of("Crypt")) return true;
|
|
993
|
+
if (filter instanceof PDFArray) {
|
|
994
|
+
for (let i = 0; i < filter.size(); i++) {
|
|
995
|
+
if (filter.get(i) === PDFName.of("Crypt")) return true;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
function cryptFilterIsIdentity(dict) {
|
|
1001
|
+
const parms = dict.lookup(PDFName.of("DecodeParms")) ?? dict.lookup(PDFName.of("DP"));
|
|
1002
|
+
const identityParms = (p) => {
|
|
1003
|
+
if (!(p instanceof PDFDict)) return true;
|
|
1004
|
+
const name = p.lookup(PDFName.of("Name"));
|
|
1005
|
+
return !(name instanceof PDFName) || name === PDFName.of("Identity");
|
|
1006
|
+
};
|
|
1007
|
+
if (parms instanceof PDFArray) {
|
|
1008
|
+
for (let i = 0; i < parms.size(); i++) {
|
|
1009
|
+
if (!identityParms(parms.lookup(i))) return false;
|
|
1010
|
+
}
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
1013
|
+
return identityParms(parms);
|
|
1014
|
+
}
|
|
1015
|
+
async function decryptStringsWithin(obj, ref, stringCipher) {
|
|
1016
|
+
if (obj instanceof PDFDict) {
|
|
1017
|
+
const isSignature = obj.lookup(PDFName.of("Type")) === PDFName.of("Sig");
|
|
1018
|
+
for (const [key, value] of obj.entries()) {
|
|
1019
|
+
if (value instanceof PDFString || value instanceof PDFHexString) {
|
|
1020
|
+
if (isSignature && key === PDFName.of("Contents")) continue;
|
|
1021
|
+
obj.set(key, toHexString(await stringCipher(value.asBytes(), ref)));
|
|
1022
|
+
} else {
|
|
1023
|
+
await decryptStringsWithin(value, ref, stringCipher);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
} else if (obj instanceof PDFArray) {
|
|
1027
|
+
for (let i = 0; i < obj.size(); i++) {
|
|
1028
|
+
const value = obj.get(i);
|
|
1029
|
+
if (value instanceof PDFString || value instanceof PDFHexString) {
|
|
1030
|
+
obj.set(i, toHexString(await stringCipher(value.asBytes(), ref)));
|
|
1031
|
+
} else {
|
|
1032
|
+
await decryptStringsWithin(value, ref, stringCipher);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async function decryptLoadedDocument(doc) {
|
|
1038
|
+
const context = doc.context;
|
|
1039
|
+
const encryptRef = context.trailerInfo.Encrypt;
|
|
1040
|
+
const encDict = context.lookup(encryptRef);
|
|
1041
|
+
if (!(encDict instanceof PDFDict)) {
|
|
1042
|
+
throw new UnsupportedPdfEncryptionError("malformed /Encrypt dictionary");
|
|
1043
|
+
}
|
|
1044
|
+
const idArray = context.lookup(context.trailerInfo.ID);
|
|
1045
|
+
const idFirst = idArray instanceof PDFArray ? idArray.get(0) : void 0;
|
|
1046
|
+
const id0 = idFirst instanceof PDFString || idFirst instanceof PDFHexString ? idFirst.asBytes() : new Uint8Array(0);
|
|
1047
|
+
const { stringCipher, streamCipher, encryptMetadata } = await buildDocumentCiphers(encDict, id0);
|
|
1048
|
+
for (const [ref, obj] of context.enumerateIndirectObjects()) {
|
|
1049
|
+
if (!(obj instanceof PDFInvalidObject)) continue;
|
|
1050
|
+
const raw = new Uint8Array(obj.sizeInBytes());
|
|
1051
|
+
obj.copyBytesInto(raw, 0);
|
|
1052
|
+
try {
|
|
1053
|
+
const parsed = PDFObjectParser.forBytes(raw, context).parseObject();
|
|
1054
|
+
if (parsed instanceof PDFRawStream && parsed.dict.lookup(PDFName.of("Type")) === PDFName.of("ObjStm")) {
|
|
1055
|
+
context.assign(ref, parsed);
|
|
1056
|
+
}
|
|
1057
|
+
} catch {
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
const decryptedObjectStreams = [];
|
|
1061
|
+
const structuralStreamRefs = [];
|
|
1062
|
+
for (const [ref, obj] of context.enumerateIndirectObjects()) {
|
|
1063
|
+
if (ref === encryptRef || obj === encDict) continue;
|
|
1064
|
+
if (obj instanceof PDFRawStream) {
|
|
1065
|
+
const type = obj.dict.lookup(PDFName.of("Type"));
|
|
1066
|
+
if (type === PDFName.of("XRef")) {
|
|
1067
|
+
structuralStreamRefs.push(ref);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const isSkippedMetadata = !encryptMetadata && type === PDFName.of("Metadata");
|
|
1071
|
+
if (filterIncludesCrypt(obj.dict)) {
|
|
1072
|
+
if (!cryptFilterIsIdentity(obj.dict)) {
|
|
1073
|
+
throw new UnsupportedPdfEncryptionError("stream-specific /Crypt filter");
|
|
1074
|
+
}
|
|
1075
|
+
} else if (!isSkippedMetadata) {
|
|
1076
|
+
const decrypted = await streamCipher(obj.contents, ref);
|
|
1077
|
+
const replacement = PDFRawStream.of(obj.dict, decrypted);
|
|
1078
|
+
context.assign(ref, replacement);
|
|
1079
|
+
if (type === PDFName.of("ObjStm")) {
|
|
1080
|
+
decryptedObjectStreams.push([ref, replacement]);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
await decryptStringsWithin(obj.dict, ref, stringCipher);
|
|
1084
|
+
} else if (obj instanceof PDFString || obj instanceof PDFHexString) {
|
|
1085
|
+
context.assign(ref, toHexString(await stringCipher(obj.asBytes(), ref)));
|
|
1086
|
+
} else {
|
|
1087
|
+
await decryptStringsWithin(obj, ref, stringCipher);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
for (const [ref, stream] of decryptedObjectStreams) {
|
|
1091
|
+
await PDFObjectStreamParser.forStream(stream).parseIntoContext();
|
|
1092
|
+
context.delete(ref);
|
|
1093
|
+
}
|
|
1094
|
+
for (const ref of structuralStreamRefs) context.delete(ref);
|
|
1095
|
+
delete context.trailerInfo.Encrypt;
|
|
1096
|
+
if (encryptRef instanceof PDFRef) context.delete(encryptRef);
|
|
1097
|
+
return doc.save({
|
|
1098
|
+
useObjectStreams: false,
|
|
1099
|
+
addDefaultPage: false,
|
|
1100
|
+
updateFieldAppearances: false
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
async function decryptPdf(pdfBytes) {
|
|
1104
|
+
const doc = await PDFDocument.load(pdfBytes.slice(), {
|
|
1105
|
+
ignoreEncryption: true,
|
|
1106
|
+
updateMetadata: false
|
|
1107
|
+
});
|
|
1108
|
+
if (!doc.isEncrypted) return pdfBytes;
|
|
1109
|
+
return decryptLoadedDocument(doc);
|
|
1110
|
+
}
|
|
1111
|
+
var SLASH = 47;
|
|
1112
|
+
var HASH = 35;
|
|
1113
|
+
var ENCRYPT_NAME = "Encrypt";
|
|
1114
|
+
function hexDigit(byte) {
|
|
1115
|
+
if (byte === void 0) return -1;
|
|
1116
|
+
if (byte >= 48 && byte <= 57) return byte - 48;
|
|
1117
|
+
if (byte >= 65 && byte <= 70) return byte - 65 + 10;
|
|
1118
|
+
if (byte >= 97 && byte <= 102) return byte - 97 + 10;
|
|
1119
|
+
return -1;
|
|
1120
|
+
}
|
|
1121
|
+
function matchesEncryptNameAt(bytes, start) {
|
|
1122
|
+
let at = start;
|
|
1123
|
+
for (let k = 0; k < ENCRYPT_NAME.length; k++) {
|
|
1124
|
+
const want = ENCRYPT_NAME.charCodeAt(k);
|
|
1125
|
+
const byte = bytes[at];
|
|
1126
|
+
if (byte === want) {
|
|
1127
|
+
at += 1;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (byte === HASH) {
|
|
1131
|
+
const high = hexDigit(bytes[at + 1]);
|
|
1132
|
+
const low = hexDigit(bytes[at + 2]);
|
|
1133
|
+
if (high >= 0 && low >= 0 && (high << 4 | low) === want) {
|
|
1134
|
+
at += 3;
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
function containsEncryptMarker(bytes) {
|
|
1143
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1144
|
+
if (bytes[i] !== SLASH) continue;
|
|
1145
|
+
if (matchesEncryptNameAt(bytes, i + 1)) return true;
|
|
1146
|
+
}
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
async function normalizePdfEncryption(pdfBytes, options) {
|
|
1150
|
+
if (!containsEncryptMarker(pdfBytes)) return pdfBytes;
|
|
1151
|
+
const doc = await PDFDocument.load(pdfBytes.slice(), {
|
|
1152
|
+
ignoreEncryption: true,
|
|
1153
|
+
updateMetadata: false
|
|
1154
|
+
});
|
|
1155
|
+
if (!doc.isEncrypted) return pdfBytes;
|
|
1156
|
+
options?.logger?.info("[PDF_DECRYPT] Encrypted PDF detected during ingestion; decrypting");
|
|
1157
|
+
return decryptLoadedDocument(doc);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/utils/pdf-decrypt.ts
|
|
1161
|
+
var PdfPasswordRequiredError2 = PdfPasswordRequiredError;
|
|
1162
|
+
var UnsupportedPdfEncryptionError2 = UnsupportedPdfEncryptionError;
|
|
1163
|
+
var decryptPdf2 = decryptPdf;
|
|
1164
|
+
var consoleLogger = {
|
|
1165
|
+
info(message, ...args) {
|
|
1166
|
+
if (typeof process === "undefined" || process.env?.NODE_ENV !== "production") {
|
|
1167
|
+
console.info(`[PageAssembler] ${message}`, ...args);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
function normalizePdfEncryption2(pdfBytes) {
|
|
1172
|
+
return normalizePdfEncryption(pdfBytes, { logger: consoleLogger });
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/utils/pdf-helpers.ts
|
|
550
1176
|
if (typeof window !== "undefined") {
|
|
551
1177
|
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
552
1178
|
}
|
|
@@ -618,6 +1244,68 @@ async function pdfToImages(pdfBytes, options) {
|
|
|
618
1244
|
throw error;
|
|
619
1245
|
}
|
|
620
1246
|
}
|
|
1247
|
+
var MAX_RASTER_PAGE_COUNT = 500;
|
|
1248
|
+
var MAX_RASTER_CANVAS_PIXELS = 4096 * 4096;
|
|
1249
|
+
var PREFERRED_RASTER_SCALE = 2;
|
|
1250
|
+
function computeRasterScale(width, height) {
|
|
1251
|
+
const area = width * height;
|
|
1252
|
+
if (!Number.isFinite(area) || area <= 0) return PREFERRED_RASTER_SCALE;
|
|
1253
|
+
const preferredPixels = area * PREFERRED_RASTER_SCALE * PREFERRED_RASTER_SCALE;
|
|
1254
|
+
if (preferredPixels <= MAX_RASTER_CANVAS_PIXELS) return PREFERRED_RASTER_SCALE;
|
|
1255
|
+
return Math.sqrt(MAX_RASTER_CANVAS_PIXELS / area);
|
|
1256
|
+
}
|
|
1257
|
+
async function rasterizePdfToPdf(pdfBytes) {
|
|
1258
|
+
if (typeof window !== "undefined") {
|
|
1259
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
1260
|
+
}
|
|
1261
|
+
let sourceDoc;
|
|
1262
|
+
try {
|
|
1263
|
+
sourceDoc = await pdfjsLib.getDocument({ data: pdfBytes.slice(0), verbosity: 0 }).promise;
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
if (error?.name === "PasswordException") {
|
|
1266
|
+
throw new PdfPasswordRequiredError2();
|
|
1267
|
+
}
|
|
1268
|
+
throw error;
|
|
1269
|
+
}
|
|
1270
|
+
try {
|
|
1271
|
+
if (sourceDoc.numPages > MAX_RASTER_PAGE_COUNT) {
|
|
1272
|
+
throw new Error(
|
|
1273
|
+
`Cannot rebuild PDF: ${sourceDoc.numPages} pages exceeds the ${MAX_RASTER_PAGE_COUNT}-page limit`
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
const outDoc = await PDFDocument.create();
|
|
1277
|
+
for (let pageNum = 1; pageNum <= sourceDoc.numPages; pageNum++) {
|
|
1278
|
+
const page = await sourceDoc.getPage(pageNum);
|
|
1279
|
+
const base = page.getViewport({ scale: 1 });
|
|
1280
|
+
const scale = computeRasterScale(base.width, base.height);
|
|
1281
|
+
const viewport = page.getViewport({ scale });
|
|
1282
|
+
const canvas = document.createElement("canvas");
|
|
1283
|
+
canvas.width = viewport.width;
|
|
1284
|
+
canvas.height = viewport.height;
|
|
1285
|
+
const context = canvas.getContext("2d");
|
|
1286
|
+
await page.render({ canvasContext: context, viewport }).promise;
|
|
1287
|
+
const png = await outDoc.embedPng(canvas.toDataURL("image/png"));
|
|
1288
|
+
const outPage = outDoc.addPage([base.width, base.height]);
|
|
1289
|
+
outPage.drawImage(png, { x: 0, y: 0, width: base.width, height: base.height });
|
|
1290
|
+
}
|
|
1291
|
+
return new Uint8Array(await outDoc.save({ useObjectStreams: false }));
|
|
1292
|
+
} finally {
|
|
1293
|
+
await sourceDoc.destroy();
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
async function normalizeUploadedPdfBytes(pdfBytes) {
|
|
1297
|
+
try {
|
|
1298
|
+
const normalized = await normalizePdfEncryption2(pdfBytes);
|
|
1299
|
+
return normalized === pdfBytes ? pdfBytes : new Uint8Array(normalized);
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
if (error instanceof PdfPasswordRequiredError2) throw error;
|
|
1302
|
+
console.warn(
|
|
1303
|
+
"[PageAssembler] Structural decryption failed; rebuilding document from rendered pages:",
|
|
1304
|
+
error
|
|
1305
|
+
);
|
|
1306
|
+
return rasterizePdfToPdf(pdfBytes);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
621
1309
|
async function imageToPdf(imageFile) {
|
|
622
1310
|
return new Promise((resolve, reject) => {
|
|
623
1311
|
const img = new Image();
|
|
@@ -693,6 +1381,701 @@ function downloadPdf(pdfBytes, filename) {
|
|
|
693
1381
|
a.click();
|
|
694
1382
|
URL.revokeObjectURL(url);
|
|
695
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
|
+
}
|
|
1424
|
+
function toCombinedPage(entry, page) {
|
|
1425
|
+
if (!entry.position && !entry.radioPositions && !entry.radioOptions) return entry;
|
|
1426
|
+
const carried = { ...entry };
|
|
1427
|
+
if (entry.position) carried.position = { ...entry.position, page };
|
|
1428
|
+
if (entry.radioPositions) carried.radioPositions = entry.radioPositions.map((p) => ({ ...p, page }));
|
|
1429
|
+
if (entry.radioOptions) {
|
|
1430
|
+
carried.radioOptions = entry.radioOptions.map((o) => ({ ...o, position: { ...o.position, page } }));
|
|
1431
|
+
}
|
|
1432
|
+
return carried;
|
|
1433
|
+
}
|
|
1434
|
+
function assembleMetadata(sources) {
|
|
1435
|
+
const merged = { version: "1.0", fields: {}, fieldIdIndex: {} };
|
|
1436
|
+
let radioLabelsBaked;
|
|
1437
|
+
let textLabelsBaked;
|
|
1438
|
+
let checkboxLabelsBaked;
|
|
1439
|
+
let legacyRadioStripped;
|
|
1440
|
+
for (const source of sources) {
|
|
1441
|
+
const meta = source.metadata;
|
|
1442
|
+
if (!meta || source.fields.length === 0 && !source.pages?.length) continue;
|
|
1443
|
+
if (meta.radioLabelsBaked === false) radioLabelsBaked = false;
|
|
1444
|
+
else if (meta.radioLabelsBaked === true && radioLabelsBaked === void 0) radioLabelsBaked = true;
|
|
1445
|
+
if (meta.textLabelsBaked === false) textLabelsBaked = false;
|
|
1446
|
+
else if (meta.textLabelsBaked === true && textLabelsBaked === void 0) textLabelsBaked = true;
|
|
1447
|
+
if (meta.checkboxLabelsBaked === false) checkboxLabelsBaked = false;
|
|
1448
|
+
else if (meta.checkboxLabelsBaked === true && checkboxLabelsBaked === void 0) checkboxLabelsBaked = true;
|
|
1449
|
+
legacyRadioStripped = meta.legacyRadioStripped === true && legacyRadioStripped !== false;
|
|
1450
|
+
}
|
|
1451
|
+
for (const source of sources) {
|
|
1452
|
+
const meta = source.metadata;
|
|
1453
|
+
if (!meta) continue;
|
|
1454
|
+
const sourceTextLabelsBaked = meta.textLabelsBaked !== false;
|
|
1455
|
+
for (const { name, page } of [...source.fields, ...textLabelPlacements(source)]) {
|
|
1456
|
+
const key = `${name}_p${page}`;
|
|
1457
|
+
const entry = meta.fields?.[name];
|
|
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;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (radioLabelsBaked !== void 0) merged.radioLabelsBaked = radioLabelsBaked;
|
|
1465
|
+
if (textLabelsBaked !== void 0) merged.textLabelsBaked = textLabelsBaked;
|
|
1466
|
+
if (checkboxLabelsBaked !== void 0) merged.checkboxLabelsBaked = checkboxLabelsBaked;
|
|
1467
|
+
if (legacyRadioStripped) merged.legacyRadioStripped = true;
|
|
1468
|
+
const hasEntries = Object.keys(merged.fields).length > 0;
|
|
1469
|
+
const hasMarkers = radioLabelsBaked !== void 0 || textLabelsBaked !== void 0 || checkboxLabelsBaked !== void 0;
|
|
1470
|
+
if (!hasEntries && !hasMarkers) return null;
|
|
1471
|
+
if (Object.keys(merged.fieldIdIndex).length === 0) delete merged.fieldIdIndex;
|
|
1472
|
+
return merged;
|
|
1473
|
+
}
|
|
1474
|
+
function mergeSourceMetadata(sources) {
|
|
1475
|
+
return assembleMetadata(sources);
|
|
1476
|
+
}
|
|
1477
|
+
function getSigniphiMetadata(pdfDoc) {
|
|
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");
|
|
1481
|
+
}
|
|
1482
|
+
return metadata;
|
|
1483
|
+
}
|
|
1484
|
+
function getBaseFieldName(encodedName) {
|
|
1485
|
+
let baseName = encodedName;
|
|
1486
|
+
if (baseName.includes("__SIGNER__")) {
|
|
1487
|
+
baseName = baseName.split("__SIGNER__")[0];
|
|
1488
|
+
}
|
|
1489
|
+
if (baseName.includes("__LABEL__")) {
|
|
1490
|
+
baseName = baseName.split("__LABEL__")[0];
|
|
1491
|
+
}
|
|
1492
|
+
if (baseName.includes("__PLACEHOLDER__")) {
|
|
1493
|
+
baseName = baseName.split("__PLACEHOLDER__")[0];
|
|
1494
|
+
}
|
|
1495
|
+
return baseName;
|
|
1496
|
+
}
|
|
1497
|
+
function stripFieldTypeSuffix(value) {
|
|
1498
|
+
const stripped = value.replace(/_(signature|initials|date)$/i, "");
|
|
1499
|
+
return stripped || value;
|
|
1500
|
+
}
|
|
1501
|
+
function getSignerFromFieldName(encodedName) {
|
|
1502
|
+
if (encodedName.includes("__SIGNER__")) {
|
|
1503
|
+
const parts = encodedName.split("__SIGNER__");
|
|
1504
|
+
if (parts[1]) {
|
|
1505
|
+
const segment = parts[1].split("__")[0] || parts[1];
|
|
1506
|
+
const isTerminal = segment === parts[1];
|
|
1507
|
+
return isTerminal ? stripFieldTypeSuffix(segment) : segment;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
return void 0;
|
|
1511
|
+
}
|
|
1512
|
+
function determineFieldType(fieldName, widgetType) {
|
|
1513
|
+
const baseName = getBaseFieldName(fieldName);
|
|
1514
|
+
const nameLower = baseName.toLowerCase();
|
|
1515
|
+
if (nameLower.includes("_signature") || nameLower.includes("signature")) {
|
|
1516
|
+
return "signature";
|
|
1517
|
+
}
|
|
1518
|
+
if (nameLower.includes("_initials") || nameLower.includes("initials")) {
|
|
1519
|
+
return "initials";
|
|
1520
|
+
}
|
|
1521
|
+
if (nameLower.includes("_date") || nameLower.includes("date")) {
|
|
1522
|
+
return "date";
|
|
1523
|
+
}
|
|
1524
|
+
if (widgetType === "checkbox" || nameLower.includes("checkbox")) {
|
|
1525
|
+
return "checkbox";
|
|
1526
|
+
}
|
|
1527
|
+
if (widgetType === "radio" || nameLower.includes("radio")) {
|
|
1528
|
+
return "radio";
|
|
1529
|
+
}
|
|
1530
|
+
if (widgetType === "dropdown" || widgetType === "select" || nameLower.includes("dropdown")) {
|
|
1531
|
+
return "dropdown";
|
|
1532
|
+
}
|
|
1533
|
+
return "text";
|
|
1534
|
+
}
|
|
1535
|
+
async function extractFieldsFromPdf(pdfBytes) {
|
|
1536
|
+
const pdfDoc = await PDFDocument.load(pdfBytes);
|
|
1537
|
+
const fields = [];
|
|
1538
|
+
const metadata = getSigniphiMetadata(pdfDoc);
|
|
1539
|
+
const form = pdfDoc.getForm();
|
|
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;
|
|
1545
|
+
for (const field of pdfFields) {
|
|
1546
|
+
const fieldName = field.getName();
|
|
1547
|
+
const widgets = field.acroField.getWidgets();
|
|
1548
|
+
const baseName = getBaseFieldName(fieldName);
|
|
1549
|
+
const signerFromName = getSignerFromFieldName(fieldName);
|
|
1550
|
+
let required = false;
|
|
1551
|
+
let options;
|
|
1552
|
+
let widgetType;
|
|
1553
|
+
if (field instanceof PDFTextField) {
|
|
1554
|
+
required = field.isRequired();
|
|
1555
|
+
console.log(`[field-extraction] PDFTextField "${fieldName}" isRequired=${required}`);
|
|
1556
|
+
} else if (field instanceof PDFCheckBox) {
|
|
1557
|
+
widgetType = "checkbox";
|
|
1558
|
+
required = field.isRequired();
|
|
1559
|
+
console.log(`[field-extraction] PDFCheckBox "${fieldName}" isRequired=${required}`);
|
|
1560
|
+
} else if (field instanceof PDFDropdown) {
|
|
1561
|
+
widgetType = "dropdown";
|
|
1562
|
+
required = field.isRequired();
|
|
1563
|
+
options = field.getOptions();
|
|
1564
|
+
console.log(`[field-extraction] PDFDropdown "${fieldName}" isRequired=${required}`);
|
|
1565
|
+
} else if (field instanceof PDFRadioGroup) {
|
|
1566
|
+
widgetType = "radio";
|
|
1567
|
+
required = field.isRequired();
|
|
1568
|
+
options = field.getOptions();
|
|
1569
|
+
console.log(`[field-extraction] PDFRadioGroup "${fieldName}" isRequired=${required}`);
|
|
1570
|
+
} else {
|
|
1571
|
+
console.log(`[field-extraction] Unknown field type "${fieldName}" - defaulting required=false`);
|
|
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
|
+
}
|
|
1584
|
+
for (const widget of widgets) {
|
|
1585
|
+
const rect = widget.getRectangle();
|
|
1586
|
+
const pageNumber = pageNumberOf(widget.P());
|
|
1587
|
+
const pageQualifiedKey = `${baseName}_p${pageNumber}`;
|
|
1588
|
+
let fieldMetadata = metadata?.fields?.[pageQualifiedKey];
|
|
1589
|
+
if (!fieldMetadata) {
|
|
1590
|
+
fieldMetadata = metadata?.fields?.[fieldName];
|
|
1591
|
+
}
|
|
1592
|
+
if (!fieldMetadata && baseName !== fieldName) {
|
|
1593
|
+
fieldMetadata = metadata?.fields?.[baseName];
|
|
1594
|
+
}
|
|
1595
|
+
let fieldRequired = required;
|
|
1596
|
+
let fieldOptions = options;
|
|
1597
|
+
if (fieldMetadata?.required !== void 0) {
|
|
1598
|
+
fieldRequired = fieldMetadata.required;
|
|
1599
|
+
}
|
|
1600
|
+
if (fieldMetadata?.options?.length) {
|
|
1601
|
+
fieldOptions = fieldMetadata.options;
|
|
1602
|
+
}
|
|
1603
|
+
const pageHeight = pageHeights[pageNumber - 1];
|
|
1604
|
+
const extractedField = {
|
|
1605
|
+
name: baseName,
|
|
1606
|
+
// Use clean base name without __SIGNER__ encoding (signer stored in signer property)
|
|
1607
|
+
// Uses base name internally. A stored date_signed is a declaration the
|
|
1608
|
+
// name can't express, so it wins.
|
|
1609
|
+
type: fieldMetadata?.type === "date_signed" ? "date_signed" : determineFieldType(fieldName, widgetType),
|
|
1610
|
+
page: pageNumber,
|
|
1611
|
+
x: rect.x,
|
|
1612
|
+
y: pageHeight - rect.y - rect.height,
|
|
1613
|
+
// Convert from PDF coords (bottom-left) to top-left
|
|
1614
|
+
width: rect.width,
|
|
1615
|
+
height: rect.height,
|
|
1616
|
+
required: fieldRequired,
|
|
1617
|
+
options: fieldOptions,
|
|
1618
|
+
signer: signerFromName
|
|
1619
|
+
// Extract signer from encoded name
|
|
1620
|
+
};
|
|
1621
|
+
if (widgetType === "radio") {
|
|
1622
|
+
extractedField.optionPositions = optionPositionsByPage.get(pageNumber) ?? [];
|
|
1623
|
+
}
|
|
1624
|
+
if (fieldMetadata) {
|
|
1625
|
+
if (fieldMetadata.fieldId) {
|
|
1626
|
+
extractedField.fieldId = fieldMetadata.fieldId;
|
|
1627
|
+
}
|
|
1628
|
+
if (fieldMetadata.label) {
|
|
1629
|
+
extractedField.label = fieldMetadata.label;
|
|
1630
|
+
}
|
|
1631
|
+
if (!extractedField.signer && fieldMetadata.signer) {
|
|
1632
|
+
extractedField.signer = fieldMetadata.signer;
|
|
1633
|
+
}
|
|
1634
|
+
if (fieldMetadata.placeholder) {
|
|
1635
|
+
extractedField.placeholder = fieldMetadata.placeholder;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
fields.push(extractedField);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
const deduplicatedFields = fields.reduce((acc, field) => {
|
|
1642
|
+
const newBaseName = getBaseFieldName(field.name);
|
|
1643
|
+
const existingIndex = acc.findIndex((f) => {
|
|
1644
|
+
const existingBaseName = getBaseFieldName(f.name);
|
|
1645
|
+
return existingBaseName === newBaseName && f.page === field.page;
|
|
1646
|
+
});
|
|
1647
|
+
if (existingIndex === -1) {
|
|
1648
|
+
acc.push(field);
|
|
1649
|
+
} else {
|
|
1650
|
+
const existing = acc[existingIndex];
|
|
1651
|
+
acc[existingIndex] = {
|
|
1652
|
+
...existing,
|
|
1653
|
+
// Prefer non-empty values from either field
|
|
1654
|
+
label: existing.label || field.label,
|
|
1655
|
+
placeholder: existing.placeholder || field.placeholder,
|
|
1656
|
+
required: existing.required || field.required,
|
|
1657
|
+
options: existing.options?.length ? existing.options : field.options,
|
|
1658
|
+
fieldId: existing.fieldId || field.fieldId,
|
|
1659
|
+
// Prefer signer from the field that has it
|
|
1660
|
+
signer: existing.signer || field.signer
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
return acc;
|
|
1664
|
+
}, []);
|
|
1665
|
+
return deduplicatedFields;
|
|
1666
|
+
}
|
|
1667
|
+
function setSigniphiMetadata(pdfDoc, metadata) {
|
|
1668
|
+
try {
|
|
1669
|
+
writeMetadata(pdfDoc, metadata, { PDFName: PDFName, PDFHexString: PDFHexString });
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
console.error("Failed to set Signiphi metadata:", error);
|
|
1672
|
+
throw error;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
var SIGNIPHI_CHECKBOX_LABEL_MARKER = "SiPL_Check";
|
|
1676
|
+
var RADIO_BORDER_WIDTH = 1;
|
|
1677
|
+
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
1678
|
+
FormFieldType2["TEXT"] = "text";
|
|
1679
|
+
FormFieldType2["SIGNATURE"] = "signature";
|
|
1680
|
+
FormFieldType2["INITIALS"] = "initials";
|
|
1681
|
+
FormFieldType2["DATE"] = "date";
|
|
1682
|
+
FormFieldType2["CHECKBOX"] = "checkbox";
|
|
1683
|
+
FormFieldType2["RADIO"] = "radio";
|
|
1684
|
+
FormFieldType2["DROPDOWN"] = "dropdown";
|
|
1685
|
+
FormFieldType2["TEXT_LABEL"] = "text_label";
|
|
1686
|
+
FormFieldType2["DATE_SIGNED"] = "date_signed";
|
|
1687
|
+
return FormFieldType2;
|
|
1688
|
+
})(FormFieldType || {});
|
|
1689
|
+
function parseEncodedFieldName(encodedName) {
|
|
1690
|
+
let baseName = encodedName;
|
|
1691
|
+
let label;
|
|
1692
|
+
let signer;
|
|
1693
|
+
if (baseName.includes("__SIGNER__")) {
|
|
1694
|
+
const parts = baseName.split("__SIGNER__");
|
|
1695
|
+
baseName = parts[0];
|
|
1696
|
+
signer = parts[1];
|
|
1697
|
+
}
|
|
1698
|
+
if (baseName.includes("__LABEL__")) {
|
|
1699
|
+
const parts = baseName.split("__LABEL__");
|
|
1700
|
+
baseName = parts[0];
|
|
1701
|
+
label = parts[1];
|
|
1702
|
+
}
|
|
1703
|
+
if (baseName.includes("__PLACEHOLDER__")) {
|
|
1704
|
+
const parts = baseName.split("__PLACEHOLDER__");
|
|
1705
|
+
baseName = parts[0];
|
|
1706
|
+
}
|
|
1707
|
+
return { baseName, label, signer };
|
|
1708
|
+
}
|
|
1709
|
+
function encodeFieldNameWithSigner(fieldName, signerEmail) {
|
|
1710
|
+
const { baseName, signer: existingSigner } = parseEncodedFieldName(fieldName);
|
|
1711
|
+
const finalSigner = existingSigner || signerEmail;
|
|
1712
|
+
if (!finalSigner) return baseName;
|
|
1713
|
+
return `${baseName}__SIGNER__${finalSigner}`;
|
|
1714
|
+
}
|
|
1715
|
+
function encodeFieldNameWithLabelAndSigner(fieldName, label, signerEmail) {
|
|
1716
|
+
const { baseName, label: existingLabel, signer: existingSigner } = parseEncodedFieldName(fieldName);
|
|
1717
|
+
const finalLabel = existingLabel || label;
|
|
1718
|
+
const finalSigner = existingSigner || signerEmail;
|
|
1719
|
+
let name = baseName;
|
|
1720
|
+
if (finalLabel && finalLabel.trim()) {
|
|
1721
|
+
name = `${name}__LABEL__${finalLabel.trim()}`;
|
|
1722
|
+
}
|
|
1723
|
+
if (finalSigner) {
|
|
1724
|
+
name = `${name}__SIGNER__${finalSigner}`;
|
|
1725
|
+
}
|
|
1726
|
+
return name;
|
|
1727
|
+
}
|
|
1728
|
+
async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
|
|
1729
|
+
const { removeExistingFields = false, drawLabels = false } = options;
|
|
1730
|
+
const pdfDoc = await PDFDocument.load(pdfBytes);
|
|
1731
|
+
const form = pdfDoc.getForm();
|
|
1732
|
+
const pages = pdfDoc.getPages();
|
|
1733
|
+
if (removeExistingFields) {
|
|
1734
|
+
const existingFields = form.getFields();
|
|
1735
|
+
for (const field of existingFields) {
|
|
1736
|
+
try {
|
|
1737
|
+
const acroField = field.acroField;
|
|
1738
|
+
if (acroField && typeof acroField.getWidgets === "function") {
|
|
1739
|
+
let widgets = acroField.getWidgets();
|
|
1740
|
+
let safetyCounter = 0;
|
|
1741
|
+
const maxIterations = widgets.length + 5;
|
|
1742
|
+
while (widgets.length > 0 && safetyCounter < maxIterations) {
|
|
1743
|
+
try {
|
|
1744
|
+
acroField.removeWidget(widgets.length - 1);
|
|
1745
|
+
widgets = acroField.getWidgets();
|
|
1746
|
+
} catch {
|
|
1747
|
+
break;
|
|
1748
|
+
}
|
|
1749
|
+
safetyCounter++;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
form.removeField(field);
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
console.warn(`Failed to remove field ${field.getName()}:`, error);
|
|
1755
|
+
try {
|
|
1756
|
+
form.removeField(field);
|
|
1757
|
+
} catch {
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
1763
|
+
const bakedCheckboxCaptions = /* @__PURE__ */ new Map();
|
|
1764
|
+
const fieldNamePages = /* @__PURE__ */ new Map();
|
|
1765
|
+
for (const field of formFields) {
|
|
1766
|
+
const baseName = field.name;
|
|
1767
|
+
if (!fieldNamePages.has(baseName)) {
|
|
1768
|
+
fieldNamePages.set(baseName, /* @__PURE__ */ new Set());
|
|
1769
|
+
}
|
|
1770
|
+
fieldNamePages.get(baseName).add(field.position.page);
|
|
1771
|
+
}
|
|
1772
|
+
const conflictingNames = /* @__PURE__ */ new Set();
|
|
1773
|
+
for (const [name, pages2] of fieldNamePages) {
|
|
1774
|
+
if (pages2.size > 1) {
|
|
1775
|
+
conflictingNames.add(name);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
for (const field of formFields) {
|
|
1779
|
+
const pageIndex = field.position.page - 1;
|
|
1780
|
+
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
1781
|
+
console.warn(`Field ${field.name} references page ${field.position.page} but PDF only has ${pages.length} pages`);
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
const page = pages[pageIndex];
|
|
1785
|
+
const { height: pageHeight } = page.getSize();
|
|
1786
|
+
const pdfX = field.position.x;
|
|
1787
|
+
const pdfY = pageHeight - field.position.y - field.position.height;
|
|
1788
|
+
const uniqueFieldName = conflictingNames.has(field.name) ? `${field.name}_p${field.position.page}` : field.name;
|
|
1789
|
+
try {
|
|
1790
|
+
const fieldType = typeof field.type === "string" ? field.type : field.type;
|
|
1791
|
+
switch (fieldType) {
|
|
1792
|
+
case "text" /* TEXT */: {
|
|
1793
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1794
|
+
const textField = form.createTextField(encodedName);
|
|
1795
|
+
textField.addToPage(page, {
|
|
1796
|
+
x: pdfX,
|
|
1797
|
+
y: pdfY,
|
|
1798
|
+
width: field.position.width,
|
|
1799
|
+
height: field.position.height,
|
|
1800
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1801
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1802
|
+
});
|
|
1803
|
+
if (field.label && field.label.trim()) {
|
|
1804
|
+
textField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
|
|
1805
|
+
if (drawLabels) {
|
|
1806
|
+
const fontSize = Math.min(10, field.position.height * 0.4);
|
|
1807
|
+
const labelY = pdfY + field.position.height + 5;
|
|
1808
|
+
page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
if (field.defaultValue && field.defaultValue.trim()) {
|
|
1812
|
+
textField.setText(field.defaultValue);
|
|
1813
|
+
}
|
|
1814
|
+
if (field.fontSize && field.fontSize >= 8 && field.fontSize <= 72) {
|
|
1815
|
+
try {
|
|
1816
|
+
textField.setFontSize(field.fontSize);
|
|
1817
|
+
} catch {
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (field.multiline) {
|
|
1821
|
+
try {
|
|
1822
|
+
textField.enableMultiline();
|
|
1823
|
+
} catch {
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
if (field.maxLength && field.maxLength > 0) {
|
|
1827
|
+
try {
|
|
1828
|
+
textField.setMaxLength(field.maxLength);
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (field.required) {
|
|
1833
|
+
textField.enableRequired();
|
|
1834
|
+
}
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
case "checkbox" /* CHECKBOX */: {
|
|
1838
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1839
|
+
const checkBox = form.createCheckBox(encodedName);
|
|
1840
|
+
checkBox.addToPage(page, {
|
|
1841
|
+
x: pdfX,
|
|
1842
|
+
y: pdfY,
|
|
1843
|
+
width: field.position.width,
|
|
1844
|
+
height: field.position.height,
|
|
1845
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1846
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1847
|
+
});
|
|
1848
|
+
if (field.defaultValue === "true" || field.defaultValue === "checked") {
|
|
1849
|
+
checkBox.check();
|
|
1850
|
+
}
|
|
1851
|
+
if (field.required) {
|
|
1852
|
+
checkBox.enableRequired();
|
|
1853
|
+
}
|
|
1854
|
+
if (field.label && field.label.trim()) {
|
|
1855
|
+
try {
|
|
1856
|
+
checkBox.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
|
|
1857
|
+
} catch {
|
|
1858
|
+
}
|
|
1859
|
+
if (drawLabels) {
|
|
1860
|
+
const fontSize = Math.max(1, field.position.height * 0.4);
|
|
1861
|
+
const labelX = pdfX + field.position.width + 5;
|
|
1862
|
+
const labelY = pdfY + (field.position.height - fontSize) / 2;
|
|
1863
|
+
page.pushOperators(
|
|
1864
|
+
PDFOperator.of(PDFOperatorNames.BeginMarkedContent, [PDFName.of(SIGNIPHI_CHECKBOX_LABEL_MARKER)])
|
|
1865
|
+
);
|
|
1866
|
+
page.drawText(field.label, { x: labelX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
|
|
1867
|
+
page.pushOperators(PDFOperator.of(PDFOperatorNames.EndMarkedContent));
|
|
1868
|
+
bakedCheckboxCaptions.set(encodedName, field);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
break;
|
|
1872
|
+
}
|
|
1873
|
+
case "signature" /* SIGNATURE */: {
|
|
1874
|
+
const baseWithSuffix = field.name.endsWith("_signature") ? field.name : `${field.name}_signature`;
|
|
1875
|
+
const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
|
|
1876
|
+
const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
|
|
1877
|
+
const sigField = form.createTextField(encodedName);
|
|
1878
|
+
sigField.addToPage(page, {
|
|
1879
|
+
x: pdfX,
|
|
1880
|
+
y: pdfY,
|
|
1881
|
+
width: field.position.width,
|
|
1882
|
+
height: field.position.height,
|
|
1883
|
+
borderColor: rgb(0, 0, 1),
|
|
1884
|
+
backgroundColor: rgb(0.9, 0.9, 1)
|
|
1885
|
+
});
|
|
1886
|
+
if (field.label && field.label.trim()) {
|
|
1887
|
+
sigField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
|
|
1888
|
+
}
|
|
1889
|
+
sigField.enableReadOnly();
|
|
1890
|
+
if (field.required) {
|
|
1891
|
+
sigField.enableRequired();
|
|
1892
|
+
}
|
|
1893
|
+
break;
|
|
1894
|
+
}
|
|
1895
|
+
case "initials" /* INITIALS */: {
|
|
1896
|
+
const baseWithSuffix = field.name.endsWith("_initials") ? field.name : `${field.name}_initials`;
|
|
1897
|
+
const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
|
|
1898
|
+
const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
|
|
1899
|
+
const initField = form.createTextField(encodedName);
|
|
1900
|
+
initField.addToPage(page, {
|
|
1901
|
+
x: pdfX,
|
|
1902
|
+
y: pdfY,
|
|
1903
|
+
width: field.position.width,
|
|
1904
|
+
height: field.position.height,
|
|
1905
|
+
borderColor: rgb(0.5, 0, 0.5),
|
|
1906
|
+
backgroundColor: rgb(1, 0.95, 1)
|
|
1907
|
+
});
|
|
1908
|
+
if (field.label && field.label.trim()) {
|
|
1909
|
+
initField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
|
|
1910
|
+
}
|
|
1911
|
+
initField.enableReadOnly();
|
|
1912
|
+
if (field.required) {
|
|
1913
|
+
initField.enableRequired();
|
|
1914
|
+
}
|
|
1915
|
+
break;
|
|
1916
|
+
}
|
|
1917
|
+
// DATE_SIGNED is the same widget; its metadata `type` (carried by the
|
|
1918
|
+
// merge, not by this pass) is what tells pdf-signer to stamp it.
|
|
1919
|
+
case "date" /* DATE */:
|
|
1920
|
+
case "date_signed" /* DATE_SIGNED */: {
|
|
1921
|
+
const baseWithSuffix = field.name.endsWith("_date") ? field.name : `${field.name}_date`;
|
|
1922
|
+
const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
|
|
1923
|
+
const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
|
|
1924
|
+
const dateField = form.createTextField(encodedName);
|
|
1925
|
+
dateField.addToPage(page, {
|
|
1926
|
+
x: pdfX,
|
|
1927
|
+
y: pdfY,
|
|
1928
|
+
width: field.position.width,
|
|
1929
|
+
height: field.position.height,
|
|
1930
|
+
borderColor: rgb(0, 0.5, 0),
|
|
1931
|
+
backgroundColor: rgb(0.95, 1, 0.95)
|
|
1932
|
+
});
|
|
1933
|
+
if (field.label && field.label.trim()) {
|
|
1934
|
+
dateField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
|
|
1935
|
+
}
|
|
1936
|
+
if (field.required) {
|
|
1937
|
+
dateField.enableRequired();
|
|
1938
|
+
}
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1941
|
+
case "dropdown" /* DROPDOWN */: {
|
|
1942
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1943
|
+
const dropdown = form.createDropdown(encodedName);
|
|
1944
|
+
dropdown.addToPage(page, {
|
|
1945
|
+
x: pdfX,
|
|
1946
|
+
y: pdfY,
|
|
1947
|
+
width: field.position.width,
|
|
1948
|
+
height: field.position.height,
|
|
1949
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1950
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1951
|
+
});
|
|
1952
|
+
if (field.options && field.options.length > 0) {
|
|
1953
|
+
dropdown.addOptions(field.options);
|
|
1954
|
+
if (field.defaultValue && field.options.includes(field.defaultValue)) {
|
|
1955
|
+
dropdown.select(field.defaultValue);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
if (field.label && field.label.trim()) {
|
|
1959
|
+
try {
|
|
1960
|
+
dropdown.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
|
|
1961
|
+
} catch {
|
|
1962
|
+
}
|
|
1963
|
+
if (drawLabels) {
|
|
1964
|
+
const fontSize = Math.min(10, field.position.height * 0.4);
|
|
1965
|
+
const labelY = pdfY + field.position.height + 5;
|
|
1966
|
+
page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
if (field.required) {
|
|
1970
|
+
dropdown.enableRequired();
|
|
1971
|
+
}
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
case "radio" /* RADIO */: {
|
|
1975
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1976
|
+
const radioGroup = form.createRadioGroup(encodedName);
|
|
1977
|
+
if (field.options && field.options.length > 0 && field.optionPositions?.length === field.options.length) {
|
|
1978
|
+
const optionPositions = field.optionPositions;
|
|
1979
|
+
field.options.forEach((option, index) => {
|
|
1980
|
+
const at = optionPositions[index];
|
|
1981
|
+
radioGroup.addOptionToPage(option, page, {
|
|
1982
|
+
x: at.x + RADIO_BORDER_WIDTH / 2,
|
|
1983
|
+
y: pageHeight - at.y - at.height + RADIO_BORDER_WIDTH / 2,
|
|
1984
|
+
width: at.width - RADIO_BORDER_WIDTH,
|
|
1985
|
+
height: at.height - RADIO_BORDER_WIDTH,
|
|
1986
|
+
borderWidth: RADIO_BORDER_WIDTH,
|
|
1987
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1988
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1989
|
+
});
|
|
1990
|
+
});
|
|
1991
|
+
if (field.defaultValue && field.options.includes(field.defaultValue)) {
|
|
1992
|
+
radioGroup.select(field.defaultValue);
|
|
1993
|
+
}
|
|
1994
|
+
} else if (field.options && field.options.length > 0) {
|
|
1995
|
+
const optionHeight = Math.min(20, field.position.height / field.options.length);
|
|
1996
|
+
const spacing = field.position.height / field.options.length;
|
|
1997
|
+
field.options.forEach((option, index) => {
|
|
1998
|
+
const optionY = pdfY + field.position.height - (index + 1) * spacing + (spacing - optionHeight) / 2;
|
|
1999
|
+
radioGroup.addOptionToPage(option, page, {
|
|
2000
|
+
x: pdfX,
|
|
2001
|
+
y: optionY,
|
|
2002
|
+
width: optionHeight,
|
|
2003
|
+
height: optionHeight,
|
|
2004
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
2005
|
+
backgroundColor: rgb(1, 1, 1)
|
|
2006
|
+
});
|
|
2007
|
+
if (drawLabels) {
|
|
2008
|
+
const labelX = pdfX + optionHeight + 5;
|
|
2009
|
+
const labelY = optionY + optionHeight / 4;
|
|
2010
|
+
page.drawText(option, { x: labelX, y: labelY, size: 10, font, color: rgb(0, 0, 0) });
|
|
2011
|
+
}
|
|
2012
|
+
});
|
|
2013
|
+
if (field.defaultValue && field.options.includes(field.defaultValue)) {
|
|
2014
|
+
radioGroup.select(field.defaultValue);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
if (field.label && field.label.trim()) {
|
|
2018
|
+
try {
|
|
2019
|
+
radioGroup.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
|
|
2020
|
+
} catch {
|
|
2021
|
+
}
|
|
2022
|
+
if (drawLabels) {
|
|
2023
|
+
const fontSize = 10;
|
|
2024
|
+
const labelY = pdfY + field.position.height + 5;
|
|
2025
|
+
page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
if (field.required) {
|
|
2029
|
+
radioGroup.enableRequired();
|
|
2030
|
+
}
|
|
2031
|
+
break;
|
|
2032
|
+
}
|
|
2033
|
+
default:
|
|
2034
|
+
console.warn(`Unknown field type: ${fieldType} for field ${field.name}`);
|
|
2035
|
+
}
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
console.error(`Error adding field ${field.name}:`, error);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
recordBakedCheckboxCaptions(pdfDoc, bakedCheckboxCaptions);
|
|
2041
|
+
return await pdfDoc.save();
|
|
2042
|
+
}
|
|
2043
|
+
function recordBakedCheckboxCaptions(pdfDoc, captions) {
|
|
2044
|
+
if (captions.size === 0) return;
|
|
2045
|
+
try {
|
|
2046
|
+
const metadata = getSigniphiMetadata(pdfDoc);
|
|
2047
|
+
if (!metadata) return;
|
|
2048
|
+
metadata.fields ?? (metadata.fields = {});
|
|
2049
|
+
for (const [name, field] of captions) {
|
|
2050
|
+
metadata.fields[name] = {
|
|
2051
|
+
signer: field.assignedSignerEmail,
|
|
2052
|
+
label: field.label,
|
|
2053
|
+
...metadata.fields[name],
|
|
2054
|
+
checkboxCaptionBaked: true
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
setSigniphiMetadata(pdfDoc, metadata);
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
console.warn("Failed to record baked checkbox captions:", error);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
function mapFieldPositionsAfterAssembly(fields, pageMapping) {
|
|
2063
|
+
return fields.map((field) => {
|
|
2064
|
+
const mappingKey = String(field.position.page - 1);
|
|
2065
|
+
const newPageNumber = pageMapping.get(mappingKey);
|
|
2066
|
+
if (newPageNumber !== void 0) {
|
|
2067
|
+
return {
|
|
2068
|
+
...field,
|
|
2069
|
+
position: { ...field.position, page: newPageNumber }
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
return field;
|
|
2073
|
+
}).filter((field) => {
|
|
2074
|
+
const mappingKey = String(field.position.page - 1);
|
|
2075
|
+
return pageMapping.has(mappingKey) || pageMapping.size === 0;
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
var VISIBLE_PAGE_ROWS = 2;
|
|
696
2079
|
var isMultiSelectModifier = (event) => {
|
|
697
2080
|
return event.ctrlKey || event.metaKey;
|
|
698
2081
|
};
|
|
@@ -948,7 +2331,7 @@ var DraggablePageThumbnail = ({
|
|
|
948
2331
|
ref,
|
|
949
2332
|
"data-handler-id": handlerId,
|
|
950
2333
|
className: cn(
|
|
951
|
-
"relative group cursor-pointer border-2 rounded-lg overflow-hidden bg-
|
|
2334
|
+
"relative group cursor-pointer border-2 rounded-lg overflow-hidden bg-card transition-all",
|
|
952
2335
|
isDragging && "opacity-50 scale-95",
|
|
953
2336
|
isSelected ? "border-primary shadow-md" : "border-gray-200 hover:border-gray-300"
|
|
954
2337
|
),
|
|
@@ -976,7 +2359,7 @@ var DraggablePageThumbnail = ({
|
|
|
976
2359
|
}
|
|
977
2360
|
},
|
|
978
2361
|
onContextMenu: handleContextMenu,
|
|
979
|
-
className: "h-4 w-4 rounded border-
|
|
2362
|
+
className: "h-4 w-4 rounded border-border text-primary focus:ring-primary focus:ring-2 bg-card/90 cursor-pointer"
|
|
980
2363
|
}
|
|
981
2364
|
)
|
|
982
2365
|
}
|
|
@@ -1019,21 +2402,48 @@ var ReorderablePageGrid = ({
|
|
|
1019
2402
|
newPages.splice(hoverIndex, 0, draggedPage);
|
|
1020
2403
|
onReorderPages(file.id, newPages);
|
|
1021
2404
|
}, [file.id, file.pages, onReorderPages]);
|
|
1022
|
-
|
|
1023
|
-
|
|
2405
|
+
const gridRef = useRef(null);
|
|
2406
|
+
const [maxHeight, setMaxHeight] = useState(void 0);
|
|
2407
|
+
useEffect(() => {
|
|
2408
|
+
const grid = gridRef.current;
|
|
2409
|
+
if (!grid || typeof ResizeObserver === "undefined") return;
|
|
2410
|
+
const measure = () => {
|
|
2411
|
+
const firstThumbnail = grid.firstElementChild;
|
|
2412
|
+
if (!firstThumbnail) return;
|
|
2413
|
+
const rowHeight = firstThumbnail.getBoundingClientRect().height;
|
|
2414
|
+
if (rowHeight <= 0) return;
|
|
2415
|
+
const rowGap = parseFloat(getComputedStyle(grid).rowGap) || 0;
|
|
2416
|
+
setMaxHeight(Math.ceil(rowHeight * VISIBLE_PAGE_ROWS + rowGap * (VISIBLE_PAGE_ROWS - 1)));
|
|
2417
|
+
};
|
|
2418
|
+
measure();
|
|
2419
|
+
const observer = new ResizeObserver(measure);
|
|
2420
|
+
observer.observe(grid);
|
|
2421
|
+
if (grid.firstElementChild) observer.observe(grid.firstElementChild);
|
|
2422
|
+
return () => observer.disconnect();
|
|
2423
|
+
}, [file.pages]);
|
|
2424
|
+
return /* @__PURE__ */ jsx(
|
|
2425
|
+
"div",
|
|
1024
2426
|
{
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
2427
|
+
ref: gridRef,
|
|
2428
|
+
className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 overflow-y-auto overflow-x-hidden",
|
|
2429
|
+
style: { maxHeight },
|
|
2430
|
+
children: file.pages.map((page, index) => /* @__PURE__ */ jsx(
|
|
2431
|
+
DraggablePageThumbnail,
|
|
2432
|
+
{
|
|
2433
|
+
page,
|
|
2434
|
+
fileId: file.id,
|
|
2435
|
+
index,
|
|
2436
|
+
isSelected: selectedPageIds.includes(page.id),
|
|
2437
|
+
onSingleSelect: onSingleSelectPage,
|
|
2438
|
+
onToggleSelect: onTogglePageSelect,
|
|
2439
|
+
onRangeSelect: onRangeSelectPage,
|
|
2440
|
+
movePageInFile,
|
|
2441
|
+
isDraggingPageRef
|
|
2442
|
+
},
|
|
2443
|
+
page.id
|
|
2444
|
+
))
|
|
2445
|
+
}
|
|
2446
|
+
);
|
|
1037
2447
|
};
|
|
1038
2448
|
var FileDropZone = ({
|
|
1039
2449
|
targetIndex,
|
|
@@ -1089,6 +2499,7 @@ var FileCard = ({
|
|
|
1089
2499
|
const [newName, setNewName] = useState(file.name);
|
|
1090
2500
|
const fileSelectedPages = file.pages.filter((p) => selectedPageIds.includes(p.id));
|
|
1091
2501
|
const allPagesSelected = file.pages.length > 0 && fileSelectedPages.length === file.pages.length;
|
|
2502
|
+
const pendingDropdownAction = useRef(null);
|
|
1092
2503
|
const [{ isDraggingFile }, dragFile] = useDrag({
|
|
1093
2504
|
type: DND_TYPES.FILE,
|
|
1094
2505
|
item: () => {
|
|
@@ -1193,23 +2604,38 @@ var FileCard = ({
|
|
|
1193
2604
|
] }) }),
|
|
1194
2605
|
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
1195
2606
|
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "hover:bg-gray-100 rounded-lg", children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }),
|
|
1196
|
-
/* @__PURE__ */ jsxs(
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
2607
|
+
/* @__PURE__ */ jsxs(
|
|
2608
|
+
DropdownMenuContent,
|
|
2609
|
+
{
|
|
2610
|
+
align: "end",
|
|
2611
|
+
className: "w-52",
|
|
2612
|
+
onCloseAutoFocus: () => {
|
|
2613
|
+
pendingDropdownAction.current?.();
|
|
2614
|
+
pendingDropdownAction.current = null;
|
|
2615
|
+
},
|
|
2616
|
+
children: [
|
|
2617
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => {
|
|
2618
|
+
pendingDropdownAction.current = () => setIsRenaming(true);
|
|
2619
|
+
}, children: [
|
|
2620
|
+
/* @__PURE__ */ jsx("svg", { className: "h-4 w-4 mr-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) }),
|
|
2621
|
+
"Rename File"
|
|
2622
|
+
] }),
|
|
2623
|
+
/* @__PURE__ */ jsxs(
|
|
2624
|
+
DropdownMenuItem,
|
|
2625
|
+
{
|
|
2626
|
+
onSelect: () => {
|
|
2627
|
+
pendingDropdownAction.current = () => onRemoveFile(file.id);
|
|
2628
|
+
},
|
|
2629
|
+
className: "text-red-600 hover:text-red-700 hover:bg-red-50 focus:text-red-700 focus:bg-red-50",
|
|
2630
|
+
children: [
|
|
2631
|
+
/* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 mr-2" }),
|
|
2632
|
+
"Remove File"
|
|
2633
|
+
]
|
|
2634
|
+
}
|
|
2635
|
+
)
|
|
2636
|
+
]
|
|
2637
|
+
}
|
|
2638
|
+
)
|
|
1213
2639
|
] })
|
|
1214
2640
|
] })
|
|
1215
2641
|
] }) }),
|
|
@@ -1261,7 +2687,7 @@ var BulkActionBar = ({
|
|
|
1261
2687
|
/* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: "Move selected pages to another file" }) })
|
|
1262
2688
|
] }) }),
|
|
1263
2689
|
/* @__PURE__ */ jsxs(Select, { onValueChange: onMovePages, children: [
|
|
1264
|
-
/* @__PURE__ */ jsx(SelectTrigger, { id: "move-select", className: "w-48 bg-
|
|
2690
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: "move-select", className: "w-48 bg-card", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Choose file..." }) }),
|
|
1265
2691
|
/* @__PURE__ */ jsx(SelectContent, { children: files.map((file) => /* @__PURE__ */ jsx(SelectItem, { value: file.id, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1266
2692
|
file.type === "pdf" ? /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Image$1, { className: "h-4 w-4" }),
|
|
1267
2693
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: file.name })
|
|
@@ -1290,7 +2716,7 @@ var BulkActionBar = ({
|
|
|
1290
2716
|
variant: "outline",
|
|
1291
2717
|
size: "sm",
|
|
1292
2718
|
onClick: onClearSelection,
|
|
1293
|
-
className: "bg-
|
|
2719
|
+
className: "bg-card",
|
|
1294
2720
|
children: "Clear Selection"
|
|
1295
2721
|
}
|
|
1296
2722
|
)
|
|
@@ -1318,6 +2744,7 @@ var PageAssembler = forwardRef(
|
|
|
1318
2744
|
);
|
|
1319
2745
|
const [isLoading, setIsLoading] = useState(false);
|
|
1320
2746
|
const [processingStatus, setProcessingStatus] = useState("");
|
|
2747
|
+
const [uploadError, setUploadError] = useState(null);
|
|
1321
2748
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
1322
2749
|
const [showRemoveFileConfirm, setShowRemoveFileConfirm] = useState(false);
|
|
1323
2750
|
const [fileToRemove, setFileToRemove] = useState(null);
|
|
@@ -1325,6 +2752,7 @@ var PageAssembler = forwardRef(
|
|
|
1325
2752
|
const processedInitialPdfRef = useRef(null);
|
|
1326
2753
|
const isDraggingPage = useRef(false);
|
|
1327
2754
|
const onChangeRef = useRef(onChange);
|
|
2755
|
+
const originalBytesCache = useRef(/* @__PURE__ */ new Map());
|
|
1328
2756
|
const summary = useMemo(() => ({
|
|
1329
2757
|
totalFiles: state.files.length,
|
|
1330
2758
|
totalPages: state.files.reduce((sum, file) => sum + file.pages.length, 0)
|
|
@@ -1358,6 +2786,7 @@ var PageAssembler = forwardRef(
|
|
|
1358
2786
|
} else {
|
|
1359
2787
|
pdfBytes = new Uint8Array(pdf);
|
|
1360
2788
|
}
|
|
2789
|
+
pdfBytes = await normalizeUploadedPdfBytes(pdfBytes);
|
|
1361
2790
|
const defensiveCopy = new Uint8Array(pdfBytes);
|
|
1362
2791
|
const pages = await pdfToImages(defensiveCopy, { hideFormFields: true });
|
|
1363
2792
|
if (pages.length === 0) {
|
|
@@ -1394,6 +2823,7 @@ var PageAssembler = forwardRef(
|
|
|
1394
2823
|
originalFileId: fileId
|
|
1395
2824
|
}))
|
|
1396
2825
|
};
|
|
2826
|
+
originalBytesCache.current.set(fileId, originalBytes);
|
|
1397
2827
|
dispatch({ type: "ADD_FILE", payload: file });
|
|
1398
2828
|
} catch (error) {
|
|
1399
2829
|
console.error("Error processing initial PDF:", error);
|
|
@@ -1402,8 +2832,9 @@ var PageAssembler = forwardRef(
|
|
|
1402
2832
|
setProcessingStatus("");
|
|
1403
2833
|
}
|
|
1404
2834
|
}, []);
|
|
2835
|
+
const hasRestorableState = !!initialState && initialState.files.length > 0;
|
|
1405
2836
|
useEffect(() => {
|
|
1406
|
-
if (initialPdf && !
|
|
2837
|
+
if (initialPdf && state.files.length === 0 && !hasRestorableState) {
|
|
1407
2838
|
const pdfIdentifier = initialPdf instanceof Uint8Array ? `uint8array-${initialPdf.length}-${initialPdf[0]}-${initialPdf[initialPdf.length - 1]}` : `${initialPdf.constructor.name}-${initialPdf.size || initialPdf.byteLength}`;
|
|
1408
2839
|
if (processedInitialPdfRef.current === pdfIdentifier) {
|
|
1409
2840
|
return;
|
|
@@ -1411,15 +2842,16 @@ var PageAssembler = forwardRef(
|
|
|
1411
2842
|
processedInitialPdfRef.current = pdfIdentifier;
|
|
1412
2843
|
processInitialPdf(initialPdf);
|
|
1413
2844
|
}
|
|
1414
|
-
}, [initialPdf,
|
|
2845
|
+
}, [initialPdf, state.files.length, hasRestorableState, processInitialPdf]);
|
|
1415
2846
|
useEffect(() => {
|
|
1416
|
-
if (initialState) {
|
|
2847
|
+
if (initialState && initialState.files.length > 0) {
|
|
1417
2848
|
dispatch({ type: "RESET", payload: initialState });
|
|
1418
2849
|
}
|
|
1419
2850
|
}, [initialState]);
|
|
1420
2851
|
const processFiles = useCallback(async (files) => {
|
|
1421
2852
|
const fileArray = Array.from(files);
|
|
1422
2853
|
const maxFileSize = (limits?.maxFileSizeMB || 10) * 1024 * 1024;
|
|
2854
|
+
setUploadError(null);
|
|
1423
2855
|
setIsLoading(true);
|
|
1424
2856
|
setProcessingStatus(`Processing ${fileArray.length} file${fileArray.length !== 1 ? "s" : ""}...`);
|
|
1425
2857
|
for (const file of fileArray) {
|
|
@@ -1444,8 +2876,9 @@ var PageAssembler = forwardRef(
|
|
|
1444
2876
|
if (file.type === "application/pdf") {
|
|
1445
2877
|
setProcessingStatus(`Processing PDF: ${file.name}`);
|
|
1446
2878
|
const arrayBuffer = await file.arrayBuffer();
|
|
1447
|
-
const pdfBytes = new Uint8Array(arrayBuffer);
|
|
2879
|
+
const pdfBytes = await normalizeUploadedPdfBytes(new Uint8Array(arrayBuffer));
|
|
1448
2880
|
fileInfo.originalBytes = new Uint8Array(pdfBytes);
|
|
2881
|
+
originalBytesCache.current.set(fileInfo.id, fileInfo.originalBytes);
|
|
1449
2882
|
const defensiveCopy = new Uint8Array(pdfBytes);
|
|
1450
2883
|
const pages = await pdfToImages(defensiveCopy, { hideFormFields: true });
|
|
1451
2884
|
fileInfo.pages = pages.map((page, index) => ({
|
|
@@ -1462,6 +2895,7 @@ var PageAssembler = forwardRef(
|
|
|
1462
2895
|
const arrayBuffer = await file.arrayBuffer();
|
|
1463
2896
|
const imageBytes = new Uint8Array(arrayBuffer);
|
|
1464
2897
|
fileInfo.originalBytes = new Uint8Array(imageBytes);
|
|
2898
|
+
originalBytesCache.current.set(fileInfo.id, fileInfo.originalBytes);
|
|
1465
2899
|
const thumbUrl = URL.createObjectURL(file);
|
|
1466
2900
|
const img = new Image();
|
|
1467
2901
|
const dimensions = await new Promise((resolve) => {
|
|
@@ -1492,6 +2926,11 @@ var PageAssembler = forwardRef(
|
|
|
1492
2926
|
dispatch({ type: "ADD_FILE", payload: fileInfo });
|
|
1493
2927
|
} catch (error) {
|
|
1494
2928
|
console.error(`Error processing file ${file.name}:`, error);
|
|
2929
|
+
if (error instanceof PdfPasswordRequiredError2) {
|
|
2930
|
+
setUploadError(`${file.name}: ${error.userMessage}`);
|
|
2931
|
+
} else {
|
|
2932
|
+
setUploadError(`Failed to process "${file.name}". The file may be corrupted or unsupported.`);
|
|
2933
|
+
}
|
|
1495
2934
|
}
|
|
1496
2935
|
}
|
|
1497
2936
|
setIsLoading(false);
|
|
@@ -1589,21 +3028,207 @@ var PageAssembler = forwardRef(
|
|
|
1589
3028
|
setIsLoading(true);
|
|
1590
3029
|
setProcessingStatus("Assembling PDF...");
|
|
1591
3030
|
try {
|
|
1592
|
-
const { PDFDocument:
|
|
1593
|
-
const combinedDoc = await PDFDocument2.create();
|
|
3031
|
+
const { PDFDocument: PDFDocument5, PDFName: PDFName4 } = await import('pdf-lib');
|
|
1594
3032
|
const loadedPdfs = /* @__PURE__ */ new Map();
|
|
1595
3033
|
const fileMap = /* @__PURE__ */ new Map();
|
|
3034
|
+
const pdfFiles = state.files.filter((f) => f.type === "pdf" && f.originalBytes?.length > 0);
|
|
3035
|
+
const imageFiles = state.files.filter((f) => f.type === "image");
|
|
3036
|
+
const isSinglePdfScenario = pdfFiles.length === 1 && imageFiles.length === 0;
|
|
1596
3037
|
for (const file of state.files) {
|
|
1597
3038
|
fileMap.set(file.id, file);
|
|
1598
3039
|
if (file.type === "pdf" && file.originalBytes && file.originalBytes.length > 0) {
|
|
1599
3040
|
try {
|
|
1600
|
-
const sourcePdf = await
|
|
3041
|
+
const sourcePdf = await PDFDocument5.load(file.originalBytes);
|
|
1601
3042
|
loadedPdfs.set(file.id, sourcePdf);
|
|
1602
3043
|
} catch (error) {
|
|
1603
3044
|
console.error(`Error loading PDF for file ${file.id}:`, error);
|
|
1604
3045
|
}
|
|
1605
3046
|
}
|
|
1606
3047
|
}
|
|
3048
|
+
const referencedFileIds = /* @__PURE__ */ new Set();
|
|
3049
|
+
for (const file of state.files) {
|
|
3050
|
+
for (const page of file.pages) {
|
|
3051
|
+
const originalFileId = page.originalFileId || file.id;
|
|
3052
|
+
if (!loadedPdfs.has(originalFileId)) {
|
|
3053
|
+
referencedFileIds.add(originalFileId);
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
for (const fileId of referencedFileIds) {
|
|
3058
|
+
const cachedBytes = originalBytesCache.current.get(fileId);
|
|
3059
|
+
if (cachedBytes && cachedBytes.length > 0) {
|
|
3060
|
+
try {
|
|
3061
|
+
console.log(`[PageAssembler] Loading PDF from cache for deleted file ${fileId}`);
|
|
3062
|
+
const sourcePdf = await PDFDocument5.load(cachedBytes);
|
|
3063
|
+
loadedPdfs.set(fileId, sourcePdf);
|
|
3064
|
+
} catch (error) {
|
|
3065
|
+
console.error(`Error loading cached PDF for file ${fileId}:`, error);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
console.log("[PageAssembler] Checking single PDF scenario:", {
|
|
3070
|
+
isSinglePdfScenario,
|
|
3071
|
+
pdfFilesCount: pdfFiles.length,
|
|
3072
|
+
imageFilesCount: imageFiles.length
|
|
3073
|
+
});
|
|
3074
|
+
if (isSinglePdfScenario && pdfFiles[0]) {
|
|
3075
|
+
const sourceFile = pdfFiles[0];
|
|
3076
|
+
const sourcePdf = loadedPdfs.get(sourceFile.id);
|
|
3077
|
+
console.log("[PageAssembler] Single PDF check:", {
|
|
3078
|
+
sourcePdfLoaded: !!sourcePdf,
|
|
3079
|
+
pagesCount: sourceFile.pages.length,
|
|
3080
|
+
originalBytesLength: sourceFile.originalBytes?.length
|
|
3081
|
+
});
|
|
3082
|
+
if (sourcePdf) {
|
|
3083
|
+
const desiredPageIndices = sourceFile.pages.map((p) => p.srcIndex);
|
|
3084
|
+
const originalPageCount = sourcePdf.getPageCount();
|
|
3085
|
+
const allPagesFromSameFile = sourceFile.pages.every(
|
|
3086
|
+
(p) => (p.originalFileId || sourceFile.id) === sourceFile.id
|
|
3087
|
+
);
|
|
3088
|
+
const isOriginalOrder = desiredPageIndices.length === originalPageCount && desiredPageIndices.every((idx, i) => idx === i);
|
|
3089
|
+
console.log("[PageAssembler] Original bytes check:", {
|
|
3090
|
+
allPagesFromSameFile,
|
|
3091
|
+
isOriginalOrder,
|
|
3092
|
+
desiredPageIndices,
|
|
3093
|
+
originalPageCount
|
|
3094
|
+
});
|
|
3095
|
+
if (allPagesFromSameFile && isOriginalOrder) {
|
|
3096
|
+
console.log("[PageAssembler] \u2705 Returning ORIGINAL bytes (form fields preserved)");
|
|
3097
|
+
const arrayBuffer2 = sourceFile.originalBytes.buffer.slice(
|
|
3098
|
+
sourceFile.originalBytes.byteOffset,
|
|
3099
|
+
sourceFile.originalBytes.byteOffset + sourceFile.originalBytes.byteLength
|
|
3100
|
+
);
|
|
3101
|
+
const blob2 = new Blob([arrayBuffer2], { type: "application/pdf" });
|
|
3102
|
+
return blob2;
|
|
3103
|
+
} else {
|
|
3104
|
+
console.log("[PageAssembler] \u26A0\uFE0F Pages modified, will use copyPages (form fields may be lost)");
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
console.log("[PageAssembler] Starting multi-PDF assembly with field preservation");
|
|
3109
|
+
const allExtractedFields = [];
|
|
3110
|
+
const metadataSources = [];
|
|
3111
|
+
const filePageUsage = /* @__PURE__ */ new Map();
|
|
3112
|
+
for (const file of state.files) {
|
|
3113
|
+
for (const page of file.pages) {
|
|
3114
|
+
const originalFileId = page.originalFileId || file.id;
|
|
3115
|
+
if (!filePageUsage.has(originalFileId)) {
|
|
3116
|
+
filePageUsage.set(originalFileId, /* @__PURE__ */ new Map());
|
|
3117
|
+
}
|
|
3118
|
+
const pageUsage = filePageUsage.get(originalFileId);
|
|
3119
|
+
if (!pageUsage.has(page.srcIndex)) {
|
|
3120
|
+
pageUsage.set(page.srcIndex, []);
|
|
3121
|
+
}
|
|
3122
|
+
pageUsage.get(page.srcIndex).push(page);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
let combinedPageIndex = 0;
|
|
3126
|
+
const pageIdToNewPageNumber = /* @__PURE__ */ new Map();
|
|
3127
|
+
for (const file of state.files) {
|
|
3128
|
+
for (const page of file.pages) {
|
|
3129
|
+
combinedPageIndex++;
|
|
3130
|
+
pageIdToNewPageNumber.set(page.id, combinedPageIndex);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
console.log("[PageAssembler] Page mapping:", Object.fromEntries(pageIdToNewPageNumber));
|
|
3134
|
+
const extractAndMapFields = async (fileId, fileName, pdfBytes2, sourcePdf) => {
|
|
3135
|
+
const sourceMetadata = sourcePdf ? getSigniphiMetadata(sourcePdf) : null;
|
|
3136
|
+
if (sourcePdf && !sourceMetadata) {
|
|
3137
|
+
console.warn(`[PageAssembler] No readable SigniphiMetadata in "${fileName}"`);
|
|
3138
|
+
}
|
|
3139
|
+
const pageUsageForFile = filePageUsage.get(fileId) || /* @__PURE__ */ new Map();
|
|
3140
|
+
const placements = [];
|
|
3141
|
+
const pageLandings = [];
|
|
3142
|
+
for (const [srcIndex, pageInstances] of pageUsageForFile) {
|
|
3143
|
+
for (const pageInstance of pageInstances) {
|
|
3144
|
+
const combined = pageIdToNewPageNumber.get(pageInstance.id);
|
|
3145
|
+
if (combined !== void 0) pageLandings.push({ source: srcIndex + 1, combined });
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
metadataSources.push({ metadata: sourceMetadata, fields: placements, pages: pageLandings });
|
|
3149
|
+
console.log(`[PageAssembler] Extracting fields from "${fileName}"`);
|
|
3150
|
+
const extractedFields = await extractFieldsFromPdf(pdfBytes2);
|
|
3151
|
+
console.log(`[PageAssembler] Found ${extractedFields.length} fields in "${fileName}"`);
|
|
3152
|
+
for (const field of extractedFields) {
|
|
3153
|
+
const originalPage = field.page;
|
|
3154
|
+
const srcIndex = originalPage - 1;
|
|
3155
|
+
const pageInstances = pageUsageForFile.get(srcIndex) || [];
|
|
3156
|
+
for (const pageInstance of pageInstances) {
|
|
3157
|
+
const newPageNumber = pageIdToNewPageNumber.get(pageInstance.id);
|
|
3158
|
+
if (newPageNumber !== void 0) {
|
|
3159
|
+
const trackedField = {
|
|
3160
|
+
name: field.name,
|
|
3161
|
+
type: field.type,
|
|
3162
|
+
position: {
|
|
3163
|
+
x: field.x,
|
|
3164
|
+
y: field.y,
|
|
3165
|
+
width: field.width,
|
|
3166
|
+
height: field.height,
|
|
3167
|
+
page: newPageNumber
|
|
3168
|
+
// Updated to new page number
|
|
3169
|
+
},
|
|
3170
|
+
pageId: pageInstance.id,
|
|
3171
|
+
originalFileId: fileId,
|
|
3172
|
+
label: field.label,
|
|
3173
|
+
required: field.required,
|
|
3174
|
+
options: field.options,
|
|
3175
|
+
optionPositions: field.optionPositions,
|
|
3176
|
+
placeholder: field.placeholder,
|
|
3177
|
+
fieldId: field.fieldId,
|
|
3178
|
+
assignedSignerEmail: field.signer
|
|
3179
|
+
};
|
|
3180
|
+
allExtractedFields.push(trackedField);
|
|
3181
|
+
console.log(`[PageAssembler] Mapped field "${field.name}" from page ${originalPage} to page ${newPageNumber}`);
|
|
3182
|
+
placements.push({ name: field.name, page: newPageNumber });
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3187
|
+
const processedFileIds = /* @__PURE__ */ new Set();
|
|
3188
|
+
for (const file of state.files) {
|
|
3189
|
+
if (file.type !== "pdf" || !file.originalBytes || file.originalBytes.length === 0) continue;
|
|
3190
|
+
try {
|
|
3191
|
+
await extractAndMapFields(file.id, file.name, file.originalBytes, loadedPdfs.get(file.id));
|
|
3192
|
+
processedFileIds.add(file.id);
|
|
3193
|
+
} catch (extractError) {
|
|
3194
|
+
console.error(`Error extracting fields from ${file.name}:`, extractError);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
for (const fileId of referencedFileIds) {
|
|
3198
|
+
if (processedFileIds.has(fileId)) continue;
|
|
3199
|
+
const cachedBytes = originalBytesCache.current.get(fileId);
|
|
3200
|
+
if (cachedBytes && cachedBytes.length > 0) {
|
|
3201
|
+
try {
|
|
3202
|
+
console.log(`[PageAssembler] Extracting fields from cached file ${fileId}`);
|
|
3203
|
+
await extractAndMapFields(fileId, `Cached file ${fileId}`, cachedBytes, loadedPdfs.get(fileId));
|
|
3204
|
+
} catch (extractError) {
|
|
3205
|
+
console.error(`Error extracting fields from cached file ${fileId}:`, extractError);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
console.log(`[PageAssembler] Total extracted fields: ${allExtractedFields.length}`);
|
|
3210
|
+
console.log(`[PageAssembler] Field names:`, allExtractedFields.map((f) => `${f.name} (page ${f.position.page})`));
|
|
3211
|
+
const combinedDoc = await PDFDocument5.create();
|
|
3212
|
+
const copyOrderBySource = /* @__PURE__ */ new Map();
|
|
3213
|
+
for (const file of state.files) {
|
|
3214
|
+
for (const page of file.pages) {
|
|
3215
|
+
const originalFileId = page.originalFileId || file.id;
|
|
3216
|
+
const sourcePdf = fileMap.get(originalFileId)?.type === "pdf" ? loadedPdfs.get(originalFileId) : void 0;
|
|
3217
|
+
if (!sourcePdf || page.srcIndex < 0 || page.srcIndex >= sourcePdf.getPageCount()) continue;
|
|
3218
|
+
const order = copyOrderBySource.get(originalFileId) ?? [];
|
|
3219
|
+
order.push(page.srcIndex);
|
|
3220
|
+
copyOrderBySource.set(originalFileId, order);
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
const copiedPagesBySource = /* @__PURE__ */ new Map();
|
|
3224
|
+
for (const [fileId, order] of copyOrderBySource) {
|
|
3225
|
+
try {
|
|
3226
|
+
copiedPagesBySource.set(fileId, await combinedDoc.copyPages(loadedPdfs.get(fileId), order));
|
|
3227
|
+
} catch (copyError) {
|
|
3228
|
+
console.error(`Error copying pages from ${fileId}:`, copyError);
|
|
3229
|
+
throw new Error(`Failed to copy page: ${copyError instanceof Error ? copyError.message : String(copyError)}`);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
1607
3232
|
for (const file of state.files) {
|
|
1608
3233
|
if (file.pages.length === 0) continue;
|
|
1609
3234
|
for (const page of file.pages) {
|
|
@@ -1621,18 +3246,21 @@ var PageAssembler = forwardRef(
|
|
|
1621
3246
|
}
|
|
1622
3247
|
const sourcePageCount = sourcePdf.getPageCount();
|
|
1623
3248
|
if (page.srcIndex >= 0 && page.srcIndex < sourcePageCount) {
|
|
3249
|
+
const copiedPage = copiedPagesBySource.get(originalFileId).shift();
|
|
1624
3250
|
try {
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
3251
|
+
const annotsRef = copiedPage.node.get(PDFName4.of("Annots"));
|
|
3252
|
+
if (annotsRef) {
|
|
3253
|
+
copiedPage.node.delete(PDFName4.of("Annots"));
|
|
3254
|
+
}
|
|
3255
|
+
} catch (annotError) {
|
|
3256
|
+
console.warn("Could not remove annotations from copied page:", annotError);
|
|
1630
3257
|
}
|
|
3258
|
+
combinedDoc.addPage(copiedPage);
|
|
1631
3259
|
}
|
|
1632
3260
|
} else if (sourceFile.type === "image" && sourceFile.originalFile) {
|
|
1633
3261
|
try {
|
|
1634
3262
|
const imagePdfBytes = await imageToPdf(sourceFile.originalFile);
|
|
1635
|
-
const imagePdf = await
|
|
3263
|
+
const imagePdf = await PDFDocument5.load(imagePdfBytes);
|
|
1636
3264
|
const copiedPages = await combinedDoc.copyPages(imagePdf, [0]);
|
|
1637
3265
|
copiedPages.forEach((copiedPage) => combinedDoc.addPage(copiedPage));
|
|
1638
3266
|
} catch (imageError) {
|
|
@@ -1642,7 +3270,41 @@ var PageAssembler = forwardRef(
|
|
|
1642
3270
|
}
|
|
1643
3271
|
}
|
|
1644
3272
|
}
|
|
1645
|
-
const
|
|
3273
|
+
const mergedMetadata = mergeSourceMetadata(metadataSources);
|
|
3274
|
+
if (mergedMetadata) {
|
|
3275
|
+
try {
|
|
3276
|
+
setSigniphiMetadata(combinedDoc, mergedMetadata);
|
|
3277
|
+
} catch (metadataError) {
|
|
3278
|
+
console.warn("Could not write merged metadata:", metadataError);
|
|
3279
|
+
}
|
|
3280
|
+
} else {
|
|
3281
|
+
console.warn("[PageAssembler] No source metadata survived the merge; the assembled document will read as unprepared downstream");
|
|
3282
|
+
}
|
|
3283
|
+
let pdfBytes = await combinedDoc.save();
|
|
3284
|
+
if (allExtractedFields.length > 0) {
|
|
3285
|
+
console.log(`[PageAssembler] Re-adding ${allExtractedFields.length} fields to combined document`);
|
|
3286
|
+
const fieldsForPdf = allExtractedFields.map((field) => ({
|
|
3287
|
+
name: field.name,
|
|
3288
|
+
type: field.type,
|
|
3289
|
+
position: field.position,
|
|
3290
|
+
label: field.label,
|
|
3291
|
+
required: field.required,
|
|
3292
|
+
options: field.options,
|
|
3293
|
+
optionPositions: field.optionPositions,
|
|
3294
|
+
placeholder: field.placeholder,
|
|
3295
|
+
assignedSignerEmail: field.assignedSignerEmail
|
|
3296
|
+
}));
|
|
3297
|
+
try {
|
|
3298
|
+
pdfBytes = await addFormFieldsToPdf(pdfBytes, fieldsForPdf, {
|
|
3299
|
+
removeExistingFields: true,
|
|
3300
|
+
// Remove any orphaned field references
|
|
3301
|
+
drawLabels: false
|
|
3302
|
+
});
|
|
3303
|
+
console.log("[PageAssembler] \u2705 Successfully re-added form fields to combined PDF");
|
|
3304
|
+
} catch (addFieldsError) {
|
|
3305
|
+
console.error("[PageAssembler] Error re-adding fields:", addFieldsError);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
1646
3308
|
const arrayBuffer = pdfBytes.buffer.slice(pdfBytes.byteOffset, pdfBytes.byteOffset + pdfBytes.byteLength);
|
|
1647
3309
|
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
1648
3310
|
return blob;
|
|
@@ -1697,6 +3359,10 @@ var PageAssembler = forwardRef(
|
|
|
1697
3359
|
/* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-primary" }),
|
|
1698
3360
|
/* @__PURE__ */ jsx("span", { className: "text-sm", children: processingStatus })
|
|
1699
3361
|
] }) }),
|
|
3362
|
+
uploadError && !isLoading && /* @__PURE__ */ jsx(Card, { className: "p-4 border-destructive/50 bg-destructive/5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3363
|
+
/* @__PURE__ */ jsx(X, { className: "h-4 w-4 text-destructive flex-shrink-0" }),
|
|
3364
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-destructive", children: uploadError })
|
|
3365
|
+
] }) }),
|
|
1700
3366
|
/* @__PURE__ */ jsx(
|
|
1701
3367
|
BulkActionBar,
|
|
1702
3368
|
{
|
|
@@ -1831,8 +3497,7 @@ function debounce(func, wait) {
|
|
|
1831
3497
|
timeout = setTimeout(() => func(...args), wait);
|
|
1832
3498
|
});
|
|
1833
3499
|
}
|
|
1834
|
-
var PageAssembler_default = PageAssembler;
|
|
1835
3500
|
|
|
1836
|
-
export { Button, PageAssembler, createPdfBlobUrl,
|
|
3501
|
+
export { Button, FormFieldType, PageAssembler, PdfPasswordRequiredError2 as PdfPasswordRequiredError, UnsupportedPdfEncryptionError2 as UnsupportedPdfEncryptionError, addFormFieldsToPdf, createPdfBlobUrl, decryptPdf2 as decryptPdf, downloadPdf, extractFieldsFromPdf, getSigniphiMetadata, imageToPdf, mapFieldPositionsAfterAssembly, normalizePdfEncryption2 as normalizePdfEncryption, normalizeUploadedPdfBytes, pdfToImages, rasterizePdfToPdf, setSigniphiMetadata };
|
|
1837
3502
|
//# sourceMappingURL=index.mjs.map
|
|
1838
3503
|
//# sourceMappingURL=index.mjs.map
|