@signiphi/page-assembly 0.2.0-beta.1 → 0.2.0-beta.11
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/README.md +283 -92
- package/dist/index.d.mts +239 -1
- package/dist/index.d.ts +239 -1
- package/dist/index.js +1557 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1548 -36
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +88 -27
- package/dist/styles/index.d.ts +1 -0
- package/package.json +14 -6
- package/src/styles/index.d.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
3
|
var React7 = require('react');
|
|
6
4
|
var reactDnd = require('react-dnd');
|
|
7
5
|
var reactDndHtml5Backend = require('react-dnd-html5-backend');
|
|
@@ -576,6 +574,623 @@ var TooltipContent = React7__namespace.forwardRef(({ className, sideOffset = 4,
|
|
|
576
574
|
}
|
|
577
575
|
));
|
|
578
576
|
TooltipContent.displayName = TooltipPrimitive__namespace.Content.displayName;
|
|
577
|
+
var PdfPasswordRequiredError = class extends Error {
|
|
578
|
+
constructor() {
|
|
579
|
+
const message = "This PDF is password-protected. Please remove the password and upload it again.";
|
|
580
|
+
super(message);
|
|
581
|
+
this.code = "PDF_PASSWORD_REQUIRED";
|
|
582
|
+
this.recoverable = true;
|
|
583
|
+
this.name = "PdfPasswordRequiredError";
|
|
584
|
+
this.userMessage = message;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
var UnsupportedPdfEncryptionError = class extends Error {
|
|
588
|
+
constructor(detail) {
|
|
589
|
+
super(`This PDF uses an unsupported encryption scheme (${detail}).`);
|
|
590
|
+
this.name = "UnsupportedPdfEncryptionError";
|
|
591
|
+
this.detail = detail;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
function concatBytes(...parts) {
|
|
595
|
+
let total = 0;
|
|
596
|
+
for (const p of parts) total += p.length;
|
|
597
|
+
const out = new Uint8Array(total);
|
|
598
|
+
let offset = 0;
|
|
599
|
+
for (const p of parts) {
|
|
600
|
+
out.set(p, offset);
|
|
601
|
+
offset += p.length;
|
|
602
|
+
}
|
|
603
|
+
return out;
|
|
604
|
+
}
|
|
605
|
+
var MD5_S = [
|
|
606
|
+
7,
|
|
607
|
+
12,
|
|
608
|
+
17,
|
|
609
|
+
22,
|
|
610
|
+
7,
|
|
611
|
+
12,
|
|
612
|
+
17,
|
|
613
|
+
22,
|
|
614
|
+
7,
|
|
615
|
+
12,
|
|
616
|
+
17,
|
|
617
|
+
22,
|
|
618
|
+
7,
|
|
619
|
+
12,
|
|
620
|
+
17,
|
|
621
|
+
22,
|
|
622
|
+
5,
|
|
623
|
+
9,
|
|
624
|
+
14,
|
|
625
|
+
20,
|
|
626
|
+
5,
|
|
627
|
+
9,
|
|
628
|
+
14,
|
|
629
|
+
20,
|
|
630
|
+
5,
|
|
631
|
+
9,
|
|
632
|
+
14,
|
|
633
|
+
20,
|
|
634
|
+
5,
|
|
635
|
+
9,
|
|
636
|
+
14,
|
|
637
|
+
20,
|
|
638
|
+
4,
|
|
639
|
+
11,
|
|
640
|
+
16,
|
|
641
|
+
23,
|
|
642
|
+
4,
|
|
643
|
+
11,
|
|
644
|
+
16,
|
|
645
|
+
23,
|
|
646
|
+
4,
|
|
647
|
+
11,
|
|
648
|
+
16,
|
|
649
|
+
23,
|
|
650
|
+
4,
|
|
651
|
+
11,
|
|
652
|
+
16,
|
|
653
|
+
23,
|
|
654
|
+
6,
|
|
655
|
+
10,
|
|
656
|
+
15,
|
|
657
|
+
21,
|
|
658
|
+
6,
|
|
659
|
+
10,
|
|
660
|
+
15,
|
|
661
|
+
21,
|
|
662
|
+
6,
|
|
663
|
+
10,
|
|
664
|
+
15,
|
|
665
|
+
21,
|
|
666
|
+
6,
|
|
667
|
+
10,
|
|
668
|
+
15,
|
|
669
|
+
21
|
|
670
|
+
];
|
|
671
|
+
var MD5_K = (() => {
|
|
672
|
+
const k = new Uint32Array(64);
|
|
673
|
+
for (let i = 0; i < 64; i++) k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 4294967296);
|
|
674
|
+
return k;
|
|
675
|
+
})();
|
|
676
|
+
function md5(input) {
|
|
677
|
+
const bitLen = input.length * 8;
|
|
678
|
+
const paddedLen = (input.length + 8 >> 6) + 1 << 6;
|
|
679
|
+
const buf = new Uint8Array(paddedLen);
|
|
680
|
+
buf.set(input);
|
|
681
|
+
buf[input.length] = 128;
|
|
682
|
+
const dv = new DataView(buf.buffer);
|
|
683
|
+
dv.setUint32(paddedLen - 8, bitLen >>> 0, true);
|
|
684
|
+
dv.setUint32(paddedLen - 4, Math.floor(bitLen / 4294967296), true);
|
|
685
|
+
let a0 = 1732584193;
|
|
686
|
+
let b0 = 4023233417;
|
|
687
|
+
let c0 = 2562383102;
|
|
688
|
+
let d0 = 271733878;
|
|
689
|
+
const M = new Uint32Array(16);
|
|
690
|
+
for (let offset = 0; offset < paddedLen; offset += 64) {
|
|
691
|
+
for (let i = 0; i < 16; i++) M[i] = dv.getUint32(offset + i * 4, true);
|
|
692
|
+
let A = a0;
|
|
693
|
+
let B = b0;
|
|
694
|
+
let C = c0;
|
|
695
|
+
let D = d0;
|
|
696
|
+
for (let i = 0; i < 64; i++) {
|
|
697
|
+
let F;
|
|
698
|
+
let g;
|
|
699
|
+
if (i < 16) {
|
|
700
|
+
F = B & C | ~B & D;
|
|
701
|
+
g = i;
|
|
702
|
+
} else if (i < 32) {
|
|
703
|
+
F = D & B | ~D & C;
|
|
704
|
+
g = 5 * i + 1 & 15;
|
|
705
|
+
} else if (i < 48) {
|
|
706
|
+
F = B ^ C ^ D;
|
|
707
|
+
g = 3 * i + 5 & 15;
|
|
708
|
+
} else {
|
|
709
|
+
F = C ^ (B | ~D);
|
|
710
|
+
g = 7 * i & 15;
|
|
711
|
+
}
|
|
712
|
+
const tmp = D;
|
|
713
|
+
D = C;
|
|
714
|
+
C = B;
|
|
715
|
+
const x = A + F + MD5_K[i] + M[g] | 0;
|
|
716
|
+
const s = MD5_S[i];
|
|
717
|
+
B = B + (x << s | x >>> 32 - s) | 0;
|
|
718
|
+
A = tmp;
|
|
719
|
+
}
|
|
720
|
+
a0 = a0 + A | 0;
|
|
721
|
+
b0 = b0 + B | 0;
|
|
722
|
+
c0 = c0 + C | 0;
|
|
723
|
+
d0 = d0 + D | 0;
|
|
724
|
+
}
|
|
725
|
+
const out = new Uint8Array(16);
|
|
726
|
+
const ov = new DataView(out.buffer);
|
|
727
|
+
ov.setUint32(0, a0 >>> 0, true);
|
|
728
|
+
ov.setUint32(4, b0 >>> 0, true);
|
|
729
|
+
ov.setUint32(8, c0 >>> 0, true);
|
|
730
|
+
ov.setUint32(12, d0 >>> 0, true);
|
|
731
|
+
return out;
|
|
732
|
+
}
|
|
733
|
+
function rc4(key, data) {
|
|
734
|
+
const S = new Uint8Array(256);
|
|
735
|
+
for (let i2 = 0; i2 < 256; i2++) S[i2] = i2;
|
|
736
|
+
let j = 0;
|
|
737
|
+
for (let i2 = 0; i2 < 256; i2++) {
|
|
738
|
+
j = j + S[i2] + key[i2 % key.length] & 255;
|
|
739
|
+
const t = S[i2];
|
|
740
|
+
S[i2] = S[j];
|
|
741
|
+
S[j] = t;
|
|
742
|
+
}
|
|
743
|
+
const out = new Uint8Array(data.length);
|
|
744
|
+
let i = 0;
|
|
745
|
+
j = 0;
|
|
746
|
+
for (let k = 0; k < data.length; k++) {
|
|
747
|
+
i = i + 1 & 255;
|
|
748
|
+
j = j + S[i] & 255;
|
|
749
|
+
const t = S[i];
|
|
750
|
+
S[i] = S[j];
|
|
751
|
+
S[j] = t;
|
|
752
|
+
out[k] = data[k] ^ S[S[i] + S[j] & 255];
|
|
753
|
+
}
|
|
754
|
+
return out;
|
|
755
|
+
}
|
|
756
|
+
function getSubtle() {
|
|
757
|
+
const subtle = globalThis.crypto?.subtle;
|
|
758
|
+
if (!subtle) throw new UnsupportedPdfEncryptionError("WebCrypto is unavailable in this environment");
|
|
759
|
+
return subtle;
|
|
760
|
+
}
|
|
761
|
+
async function sha2(algorithm, data) {
|
|
762
|
+
return new Uint8Array(await getSubtle().digest(algorithm, data));
|
|
763
|
+
}
|
|
764
|
+
async function importAesKey(rawKey, usages) {
|
|
765
|
+
return getSubtle().importKey("raw", rawKey, "AES-CBC", false, usages);
|
|
766
|
+
}
|
|
767
|
+
async function aesCbcEncryptNoPad(rawKey, iv, data) {
|
|
768
|
+
const key = await importAesKey(rawKey, ["encrypt"]);
|
|
769
|
+
const out = new Uint8Array(await getSubtle().encrypt({ name: "AES-CBC", iv }, key, data));
|
|
770
|
+
return out.slice(0, data.length);
|
|
771
|
+
}
|
|
772
|
+
async function aesCbcDecryptNoPad(rawKey, iv, data) {
|
|
773
|
+
const key = await importAesKey(rawKey, ["encrypt", "decrypt"]);
|
|
774
|
+
const lastBlock = data.slice(data.length - 16);
|
|
775
|
+
const padBlock = new Uint8Array(
|
|
776
|
+
await getSubtle().encrypt({ name: "AES-CBC", iv: lastBlock }, key, new Uint8Array(0))
|
|
777
|
+
);
|
|
778
|
+
const joined = concatBytes(data, padBlock);
|
|
779
|
+
return new Uint8Array(await getSubtle().decrypt({ name: "AES-CBC", iv }, key, joined));
|
|
780
|
+
}
|
|
781
|
+
async function aesDecryptPdfData(rawKey, data) {
|
|
782
|
+
if (data.length < 32) return new Uint8Array(0);
|
|
783
|
+
const iv = data.slice(0, 16);
|
|
784
|
+
let ct = data.slice(16);
|
|
785
|
+
if (ct.length % 16 !== 0) ct = ct.slice(0, ct.length - ct.length % 16);
|
|
786
|
+
try {
|
|
787
|
+
const key = await importAesKey(rawKey, ["decrypt"]);
|
|
788
|
+
return new Uint8Array(await getSubtle().decrypt({ name: "AES-CBC", iv }, key, ct));
|
|
789
|
+
} catch {
|
|
790
|
+
const plain = await aesCbcDecryptNoPad(rawKey, iv, ct);
|
|
791
|
+
const padLen = plain[plain.length - 1];
|
|
792
|
+
return padLen >= 1 && padLen <= 16 ? plain.slice(0, plain.length - padLen) : plain;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
var PAD = new Uint8Array([
|
|
796
|
+
40,
|
|
797
|
+
191,
|
|
798
|
+
78,
|
|
799
|
+
94,
|
|
800
|
+
78,
|
|
801
|
+
117,
|
|
802
|
+
138,
|
|
803
|
+
65,
|
|
804
|
+
100,
|
|
805
|
+
0,
|
|
806
|
+
78,
|
|
807
|
+
86,
|
|
808
|
+
255,
|
|
809
|
+
250,
|
|
810
|
+
1,
|
|
811
|
+
8,
|
|
812
|
+
46,
|
|
813
|
+
46,
|
|
814
|
+
0,
|
|
815
|
+
182,
|
|
816
|
+
208,
|
|
817
|
+
104,
|
|
818
|
+
62,
|
|
819
|
+
128,
|
|
820
|
+
47,
|
|
821
|
+
12,
|
|
822
|
+
169,
|
|
823
|
+
254,
|
|
824
|
+
100,
|
|
825
|
+
83,
|
|
826
|
+
105,
|
|
827
|
+
122
|
|
828
|
+
]);
|
|
829
|
+
var bytesEqual = (a, b) => {
|
|
830
|
+
if (a.length !== b.length) return false;
|
|
831
|
+
let diff = 0;
|
|
832
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
833
|
+
return diff === 0;
|
|
834
|
+
};
|
|
835
|
+
function legacyFileKey(paddedPwd, params) {
|
|
836
|
+
const { revision, keyLen, O, P, id0, encryptMetadata } = params;
|
|
837
|
+
const pBytes = new Uint8Array(4);
|
|
838
|
+
new DataView(pBytes.buffer).setInt32(0, P | 0, true);
|
|
839
|
+
const parts = [paddedPwd, O.slice(0, 32), pBytes, id0];
|
|
840
|
+
if (revision >= 4 && !encryptMetadata) parts.push(new Uint8Array([255, 255, 255, 255]));
|
|
841
|
+
let h = md5(concatBytes(...parts));
|
|
842
|
+
if (revision >= 3) for (let i = 0; i < 50; i++) h = md5(h.slice(0, keyLen));
|
|
843
|
+
return h.slice(0, keyLen);
|
|
844
|
+
}
|
|
845
|
+
function legacyComputeU(fileKey, revision, id0) {
|
|
846
|
+
if (revision === 2) return rc4(fileKey, PAD);
|
|
847
|
+
let d = md5(concatBytes(PAD, id0));
|
|
848
|
+
d = rc4(fileKey, d);
|
|
849
|
+
for (let i = 1; i <= 19; i++) d = rc4(fileKey.map((b) => b ^ i), d);
|
|
850
|
+
return d;
|
|
851
|
+
}
|
|
852
|
+
function legacyUMatches(fileKey, params) {
|
|
853
|
+
const computed = legacyComputeU(fileKey, params.revision, params.id0);
|
|
854
|
+
return params.revision === 2 ? bytesEqual(computed, params.U.slice(0, 32)) : bytesEqual(computed.slice(0, 16), params.U.slice(0, 16));
|
|
855
|
+
}
|
|
856
|
+
function legacyAuthenticateEmptyPassword(params) {
|
|
857
|
+
const userKey = legacyFileKey(PAD, params);
|
|
858
|
+
if (legacyUMatches(userKey, params)) return userKey;
|
|
859
|
+
throw new PdfPasswordRequiredError();
|
|
860
|
+
}
|
|
861
|
+
async function hash2B(password, salt, udata) {
|
|
862
|
+
let k = await sha2("SHA-256", concatBytes(password, salt, udata));
|
|
863
|
+
let e = new Uint8Array(0);
|
|
864
|
+
let i = 0;
|
|
865
|
+
do {
|
|
866
|
+
const block = concatBytes(password, k, udata);
|
|
867
|
+
const k1 = new Uint8Array(block.length * 64);
|
|
868
|
+
for (let rep = 0; rep < 64; rep++) k1.set(block, rep * block.length);
|
|
869
|
+
e = await aesCbcEncryptNoPad(k.slice(0, 16), k.slice(16, 32), k1);
|
|
870
|
+
let sum = 0;
|
|
871
|
+
for (let b = 0; b < 16; b++) sum += e[b];
|
|
872
|
+
const mod = sum % 3;
|
|
873
|
+
k = await sha2(mod === 0 ? "SHA-256" : mod === 1 ? "SHA-384" : "SHA-512", e);
|
|
874
|
+
i++;
|
|
875
|
+
} while (i < 64 || e[e.length - 1] > i - 32);
|
|
876
|
+
return k.slice(0, 32);
|
|
877
|
+
}
|
|
878
|
+
async function v5AuthenticateEmptyPassword(revision, U, UE) {
|
|
879
|
+
const pwd = new Uint8Array(0);
|
|
880
|
+
const zeroIv = new Uint8Array(16);
|
|
881
|
+
const hash = async (salt, udata) => revision === 5 ? sha2("SHA-256", concatBytes(pwd, salt, udata)) : hash2B(pwd, salt, udata);
|
|
882
|
+
const userHash = await hash(U.slice(32, 40), new Uint8Array(0));
|
|
883
|
+
if (bytesEqual(userHash, U.slice(0, 32))) {
|
|
884
|
+
const intermediate = await hash(U.slice(40, 48), new Uint8Array(0));
|
|
885
|
+
return aesCbcDecryptNoPad(intermediate, zeroIv, UE.slice(0, 32));
|
|
886
|
+
}
|
|
887
|
+
throw new PdfPasswordRequiredError();
|
|
888
|
+
}
|
|
889
|
+
var identityCipher = async (data) => data;
|
|
890
|
+
function rc4ObjectCipher(fileKey) {
|
|
891
|
+
return async (data, ref) => {
|
|
892
|
+
const ext = new Uint8Array([
|
|
893
|
+
ref.objectNumber & 255,
|
|
894
|
+
ref.objectNumber >> 8 & 255,
|
|
895
|
+
ref.objectNumber >> 16 & 255,
|
|
896
|
+
ref.generationNumber & 255,
|
|
897
|
+
ref.generationNumber >> 8 & 255
|
|
898
|
+
]);
|
|
899
|
+
const key = md5(concatBytes(fileKey, ext)).slice(0, Math.min(fileKey.length + 5, 16));
|
|
900
|
+
return rc4(key, data);
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
var AES_SALT = new Uint8Array([115, 65, 108, 84]);
|
|
904
|
+
function aes128ObjectCipher(fileKey) {
|
|
905
|
+
return async (data, ref) => {
|
|
906
|
+
const ext = new Uint8Array([
|
|
907
|
+
ref.objectNumber & 255,
|
|
908
|
+
ref.objectNumber >> 8 & 255,
|
|
909
|
+
ref.objectNumber >> 16 & 255,
|
|
910
|
+
ref.generationNumber & 255,
|
|
911
|
+
ref.generationNumber >> 8 & 255
|
|
912
|
+
]);
|
|
913
|
+
const key = md5(concatBytes(fileKey, ext, AES_SALT)).slice(0, 16);
|
|
914
|
+
return aesDecryptPdfData(key, data);
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function aes256ObjectCipher(fileKey) {
|
|
918
|
+
return async (data) => aesDecryptPdfData(fileKey, data);
|
|
919
|
+
}
|
|
920
|
+
function dictNumber(dict, key) {
|
|
921
|
+
const value = dict.lookup(pdfLib.PDFName.of(key));
|
|
922
|
+
return value instanceof pdfLib.PDFNumber ? value.asNumber() : void 0;
|
|
923
|
+
}
|
|
924
|
+
function dictBytes(dict, key) {
|
|
925
|
+
const value = dict.lookup(pdfLib.PDFName.of(key));
|
|
926
|
+
return value instanceof pdfLib.PDFString || value instanceof pdfLib.PDFHexString ? value.asBytes() : void 0;
|
|
927
|
+
}
|
|
928
|
+
async function buildDocumentCiphers(encDict, id0) {
|
|
929
|
+
const filter = encDict.lookup(pdfLib.PDFName.of("Filter"));
|
|
930
|
+
if (filter !== pdfLib.PDFName.of("Standard")) {
|
|
931
|
+
throw new UnsupportedPdfEncryptionError(`security handler ${String(filter)}`);
|
|
932
|
+
}
|
|
933
|
+
const V = dictNumber(encDict, "V") ?? 0;
|
|
934
|
+
const R = dictNumber(encDict, "R") ?? 0;
|
|
935
|
+
const O = dictBytes(encDict, "O");
|
|
936
|
+
const U = dictBytes(encDict, "U");
|
|
937
|
+
const P = dictNumber(encDict, "P") ?? -1;
|
|
938
|
+
const metadataValue = encDict.lookup(pdfLib.PDFName.of("EncryptMetadata"));
|
|
939
|
+
const encryptMetadata = metadataValue instanceof pdfLib.PDFBool ? metadataValue.asBoolean() : true;
|
|
940
|
+
if (!O || !U) throw new UnsupportedPdfEncryptionError("missing /O or /U");
|
|
941
|
+
if (V === 1 || V === 2) {
|
|
942
|
+
const keyLen = V === 1 ? 5 : (dictNumber(encDict, "Length") ?? 40) / 8;
|
|
943
|
+
const fileKey = legacyAuthenticateEmptyPassword({ revision: R, keyLen, O, U, P, id0, encryptMetadata });
|
|
944
|
+
const cipher = rc4ObjectCipher(fileKey);
|
|
945
|
+
return { stringCipher: cipher, streamCipher: cipher, encryptMetadata };
|
|
946
|
+
}
|
|
947
|
+
if (V === 4 || V === 5) {
|
|
948
|
+
const cf = encDict.lookup(pdfLib.PDFName.of("CF"));
|
|
949
|
+
const cfmOf = (filterKey) => {
|
|
950
|
+
const streamFilter = encDict.lookup(pdfLib.PDFName.of(filterKey));
|
|
951
|
+
if (!(streamFilter instanceof pdfLib.PDFName) || streamFilter === pdfLib.PDFName.of("Identity")) return void 0;
|
|
952
|
+
if (!(cf instanceof pdfLib.PDFDict)) throw new UnsupportedPdfEncryptionError("missing /CF");
|
|
953
|
+
const cfDict = cf.lookup(streamFilter);
|
|
954
|
+
if (!(cfDict instanceof pdfLib.PDFDict)) throw new UnsupportedPdfEncryptionError(`missing crypt filter ${streamFilter.toString()}`);
|
|
955
|
+
const cfm = cfDict.lookup(pdfLib.PDFName.of("CFM"));
|
|
956
|
+
return cfm instanceof pdfLib.PDFName ? cfm : void 0;
|
|
957
|
+
};
|
|
958
|
+
let fileKey;
|
|
959
|
+
if (V === 5) {
|
|
960
|
+
const UE = dictBytes(encDict, "UE");
|
|
961
|
+
if (!UE) throw new UnsupportedPdfEncryptionError("missing /UE");
|
|
962
|
+
fileKey = await v5AuthenticateEmptyPassword(R, U, UE);
|
|
963
|
+
} else {
|
|
964
|
+
const cryptFilterLengthBits = () => {
|
|
965
|
+
if (!(cf instanceof pdfLib.PDFDict)) return void 0;
|
|
966
|
+
for (const filterKey of ["StmF", "StrF"]) {
|
|
967
|
+
const filter2 = encDict.lookup(pdfLib.PDFName.of(filterKey));
|
|
968
|
+
if (!(filter2 instanceof pdfLib.PDFName) || filter2 === pdfLib.PDFName.of("Identity")) continue;
|
|
969
|
+
const cfDict = cf.lookup(filter2);
|
|
970
|
+
if (!(cfDict instanceof pdfLib.PDFDict)) continue;
|
|
971
|
+
const len = dictNumber(cfDict, "Length");
|
|
972
|
+
if (len === void 0) continue;
|
|
973
|
+
return len < 40 ? len * 8 : len;
|
|
974
|
+
}
|
|
975
|
+
return void 0;
|
|
976
|
+
};
|
|
977
|
+
const keyLenBits = cryptFilterLengthBits() ?? dictNumber(encDict, "Length") ?? 128;
|
|
978
|
+
fileKey = legacyAuthenticateEmptyPassword({
|
|
979
|
+
revision: R,
|
|
980
|
+
keyLen: keyLenBits / 8,
|
|
981
|
+
O,
|
|
982
|
+
U,
|
|
983
|
+
P,
|
|
984
|
+
id0,
|
|
985
|
+
encryptMetadata
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
const cipherFor = (cfm) => {
|
|
989
|
+
if (!cfm || cfm === pdfLib.PDFName.of("Identity")) return identityCipher;
|
|
990
|
+
if (cfm === pdfLib.PDFName.of("V2")) return rc4ObjectCipher(fileKey);
|
|
991
|
+
if (cfm === pdfLib.PDFName.of("AESV2")) return aes128ObjectCipher(fileKey);
|
|
992
|
+
if (cfm === pdfLib.PDFName.of("AESV3")) return aes256ObjectCipher(fileKey);
|
|
993
|
+
throw new UnsupportedPdfEncryptionError(`crypt filter method ${cfm.toString()}`);
|
|
994
|
+
};
|
|
995
|
+
return {
|
|
996
|
+
streamCipher: cipherFor(cfmOf("StmF")),
|
|
997
|
+
stringCipher: cipherFor(cfmOf("StrF")),
|
|
998
|
+
encryptMetadata
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
throw new UnsupportedPdfEncryptionError(`/V ${V}`);
|
|
1002
|
+
}
|
|
1003
|
+
var toHexString = (bytes) => {
|
|
1004
|
+
let hex = "";
|
|
1005
|
+
for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, "0");
|
|
1006
|
+
return new pdfLib.PDFHexString(hex.toUpperCase());
|
|
1007
|
+
};
|
|
1008
|
+
function filterIncludesCrypt(dict) {
|
|
1009
|
+
const filter = dict.lookup(pdfLib.PDFName.of("Filter"));
|
|
1010
|
+
if (filter === pdfLib.PDFName.of("Crypt")) return true;
|
|
1011
|
+
if (filter instanceof pdfLib.PDFArray) {
|
|
1012
|
+
for (let i = 0; i < filter.size(); i++) {
|
|
1013
|
+
if (filter.get(i) === pdfLib.PDFName.of("Crypt")) return true;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
function cryptFilterIsIdentity(dict) {
|
|
1019
|
+
const parms = dict.lookup(pdfLib.PDFName.of("DecodeParms")) ?? dict.lookup(pdfLib.PDFName.of("DP"));
|
|
1020
|
+
const identityParms = (p) => {
|
|
1021
|
+
if (!(p instanceof pdfLib.PDFDict)) return true;
|
|
1022
|
+
const name = p.lookup(pdfLib.PDFName.of("Name"));
|
|
1023
|
+
return !(name instanceof pdfLib.PDFName) || name === pdfLib.PDFName.of("Identity");
|
|
1024
|
+
};
|
|
1025
|
+
if (parms instanceof pdfLib.PDFArray) {
|
|
1026
|
+
for (let i = 0; i < parms.size(); i++) {
|
|
1027
|
+
if (!identityParms(parms.lookup(i))) return false;
|
|
1028
|
+
}
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
return identityParms(parms);
|
|
1032
|
+
}
|
|
1033
|
+
async function decryptStringsWithin(obj, ref, stringCipher) {
|
|
1034
|
+
if (obj instanceof pdfLib.PDFDict) {
|
|
1035
|
+
const isSignature = obj.lookup(pdfLib.PDFName.of("Type")) === pdfLib.PDFName.of("Sig");
|
|
1036
|
+
for (const [key, value] of obj.entries()) {
|
|
1037
|
+
if (value instanceof pdfLib.PDFString || value instanceof pdfLib.PDFHexString) {
|
|
1038
|
+
if (isSignature && key === pdfLib.PDFName.of("Contents")) continue;
|
|
1039
|
+
obj.set(key, toHexString(await stringCipher(value.asBytes(), ref)));
|
|
1040
|
+
} else {
|
|
1041
|
+
await decryptStringsWithin(value, ref, stringCipher);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
} else if (obj instanceof pdfLib.PDFArray) {
|
|
1045
|
+
for (let i = 0; i < obj.size(); i++) {
|
|
1046
|
+
const value = obj.get(i);
|
|
1047
|
+
if (value instanceof pdfLib.PDFString || value instanceof pdfLib.PDFHexString) {
|
|
1048
|
+
obj.set(i, toHexString(await stringCipher(value.asBytes(), ref)));
|
|
1049
|
+
} else {
|
|
1050
|
+
await decryptStringsWithin(value, ref, stringCipher);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
async function decryptLoadedDocument(doc) {
|
|
1056
|
+
const context = doc.context;
|
|
1057
|
+
const encryptRef = context.trailerInfo.Encrypt;
|
|
1058
|
+
const encDict = context.lookup(encryptRef);
|
|
1059
|
+
if (!(encDict instanceof pdfLib.PDFDict)) {
|
|
1060
|
+
throw new UnsupportedPdfEncryptionError("malformed /Encrypt dictionary");
|
|
1061
|
+
}
|
|
1062
|
+
const idArray = context.lookup(context.trailerInfo.ID);
|
|
1063
|
+
const idFirst = idArray instanceof pdfLib.PDFArray ? idArray.get(0) : void 0;
|
|
1064
|
+
const id0 = idFirst instanceof pdfLib.PDFString || idFirst instanceof pdfLib.PDFHexString ? idFirst.asBytes() : new Uint8Array(0);
|
|
1065
|
+
const { stringCipher, streamCipher, encryptMetadata } = await buildDocumentCiphers(encDict, id0);
|
|
1066
|
+
for (const [ref, obj] of context.enumerateIndirectObjects()) {
|
|
1067
|
+
if (!(obj instanceof pdfLib.PDFInvalidObject)) continue;
|
|
1068
|
+
const raw = new Uint8Array(obj.sizeInBytes());
|
|
1069
|
+
obj.copyBytesInto(raw, 0);
|
|
1070
|
+
try {
|
|
1071
|
+
const parsed = pdfLib.PDFObjectParser.forBytes(raw, context).parseObject();
|
|
1072
|
+
if (parsed instanceof pdfLib.PDFRawStream && parsed.dict.lookup(pdfLib.PDFName.of("Type")) === pdfLib.PDFName.of("ObjStm")) {
|
|
1073
|
+
context.assign(ref, parsed);
|
|
1074
|
+
}
|
|
1075
|
+
} catch {
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const decryptedObjectStreams = [];
|
|
1079
|
+
const structuralStreamRefs = [];
|
|
1080
|
+
for (const [ref, obj] of context.enumerateIndirectObjects()) {
|
|
1081
|
+
if (ref === encryptRef || obj === encDict) continue;
|
|
1082
|
+
if (obj instanceof pdfLib.PDFRawStream) {
|
|
1083
|
+
const type = obj.dict.lookup(pdfLib.PDFName.of("Type"));
|
|
1084
|
+
if (type === pdfLib.PDFName.of("XRef")) {
|
|
1085
|
+
structuralStreamRefs.push(ref);
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const isSkippedMetadata = !encryptMetadata && type === pdfLib.PDFName.of("Metadata");
|
|
1089
|
+
if (filterIncludesCrypt(obj.dict)) {
|
|
1090
|
+
if (!cryptFilterIsIdentity(obj.dict)) {
|
|
1091
|
+
throw new UnsupportedPdfEncryptionError("stream-specific /Crypt filter");
|
|
1092
|
+
}
|
|
1093
|
+
} else if (!isSkippedMetadata) {
|
|
1094
|
+
const decrypted = await streamCipher(obj.contents, ref);
|
|
1095
|
+
const replacement = pdfLib.PDFRawStream.of(obj.dict, decrypted);
|
|
1096
|
+
context.assign(ref, replacement);
|
|
1097
|
+
if (type === pdfLib.PDFName.of("ObjStm")) {
|
|
1098
|
+
decryptedObjectStreams.push([ref, replacement]);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
await decryptStringsWithin(obj.dict, ref, stringCipher);
|
|
1102
|
+
} else if (obj instanceof pdfLib.PDFString || obj instanceof pdfLib.PDFHexString) {
|
|
1103
|
+
context.assign(ref, toHexString(await stringCipher(obj.asBytes(), ref)));
|
|
1104
|
+
} else {
|
|
1105
|
+
await decryptStringsWithin(obj, ref, stringCipher);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
for (const [ref, stream] of decryptedObjectStreams) {
|
|
1109
|
+
await pdfLib.PDFObjectStreamParser.forStream(stream).parseIntoContext();
|
|
1110
|
+
context.delete(ref);
|
|
1111
|
+
}
|
|
1112
|
+
for (const ref of structuralStreamRefs) context.delete(ref);
|
|
1113
|
+
delete context.trailerInfo.Encrypt;
|
|
1114
|
+
if (encryptRef instanceof pdfLib.PDFRef) context.delete(encryptRef);
|
|
1115
|
+
return doc.save({
|
|
1116
|
+
useObjectStreams: false,
|
|
1117
|
+
addDefaultPage: false,
|
|
1118
|
+
updateFieldAppearances: false
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
async function decryptPdf(pdfBytes) {
|
|
1122
|
+
const doc = await pdfLib.PDFDocument.load(pdfBytes.slice(), {
|
|
1123
|
+
ignoreEncryption: true,
|
|
1124
|
+
updateMetadata: false
|
|
1125
|
+
});
|
|
1126
|
+
if (!doc.isEncrypted) return pdfBytes;
|
|
1127
|
+
return decryptLoadedDocument(doc);
|
|
1128
|
+
}
|
|
1129
|
+
var SLASH = 47;
|
|
1130
|
+
var HASH = 35;
|
|
1131
|
+
var ENCRYPT_NAME = "Encrypt";
|
|
1132
|
+
function hexDigit(byte) {
|
|
1133
|
+
if (byte === void 0) return -1;
|
|
1134
|
+
if (byte >= 48 && byte <= 57) return byte - 48;
|
|
1135
|
+
if (byte >= 65 && byte <= 70) return byte - 65 + 10;
|
|
1136
|
+
if (byte >= 97 && byte <= 102) return byte - 97 + 10;
|
|
1137
|
+
return -1;
|
|
1138
|
+
}
|
|
1139
|
+
function matchesEncryptNameAt(bytes, start) {
|
|
1140
|
+
let at = start;
|
|
1141
|
+
for (let k = 0; k < ENCRYPT_NAME.length; k++) {
|
|
1142
|
+
const want = ENCRYPT_NAME.charCodeAt(k);
|
|
1143
|
+
const byte = bytes[at];
|
|
1144
|
+
if (byte === want) {
|
|
1145
|
+
at += 1;
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
if (byte === HASH) {
|
|
1149
|
+
const high = hexDigit(bytes[at + 1]);
|
|
1150
|
+
const low = hexDigit(bytes[at + 2]);
|
|
1151
|
+
if (high >= 0 && low >= 0 && (high << 4 | low) === want) {
|
|
1152
|
+
at += 3;
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
return true;
|
|
1159
|
+
}
|
|
1160
|
+
function containsEncryptMarker(bytes) {
|
|
1161
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1162
|
+
if (bytes[i] !== SLASH) continue;
|
|
1163
|
+
if (matchesEncryptNameAt(bytes, i + 1)) return true;
|
|
1164
|
+
}
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
async function normalizePdfEncryption(pdfBytes, options) {
|
|
1168
|
+
if (!containsEncryptMarker(pdfBytes)) return pdfBytes;
|
|
1169
|
+
const doc = await pdfLib.PDFDocument.load(pdfBytes.slice(), {
|
|
1170
|
+
ignoreEncryption: true,
|
|
1171
|
+
updateMetadata: false
|
|
1172
|
+
});
|
|
1173
|
+
if (!doc.isEncrypted) return pdfBytes;
|
|
1174
|
+
options?.logger?.info("[PDF_DECRYPT] Encrypted PDF detected during ingestion; decrypting");
|
|
1175
|
+
return decryptLoadedDocument(doc);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/utils/pdf-decrypt.ts
|
|
1179
|
+
var PdfPasswordRequiredError2 = PdfPasswordRequiredError;
|
|
1180
|
+
var UnsupportedPdfEncryptionError2 = UnsupportedPdfEncryptionError;
|
|
1181
|
+
var decryptPdf2 = decryptPdf;
|
|
1182
|
+
var consoleLogger = {
|
|
1183
|
+
info(message, ...args) {
|
|
1184
|
+
if (typeof process === "undefined" || process.env?.NODE_ENV !== "production") {
|
|
1185
|
+
console.info(`[PageAssembler] ${message}`, ...args);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
function normalizePdfEncryption2(pdfBytes) {
|
|
1190
|
+
return normalizePdfEncryption(pdfBytes, { logger: consoleLogger });
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/utils/pdf-helpers.ts
|
|
579
1194
|
if (typeof window !== "undefined") {
|
|
580
1195
|
pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
581
1196
|
}
|
|
@@ -647,6 +1262,68 @@ async function pdfToImages(pdfBytes, options) {
|
|
|
647
1262
|
throw error;
|
|
648
1263
|
}
|
|
649
1264
|
}
|
|
1265
|
+
var MAX_RASTER_PAGE_COUNT = 500;
|
|
1266
|
+
var MAX_RASTER_CANVAS_PIXELS = 4096 * 4096;
|
|
1267
|
+
var PREFERRED_RASTER_SCALE = 2;
|
|
1268
|
+
function computeRasterScale(width, height) {
|
|
1269
|
+
const area = width * height;
|
|
1270
|
+
if (!Number.isFinite(area) || area <= 0) return PREFERRED_RASTER_SCALE;
|
|
1271
|
+
const preferredPixels = area * PREFERRED_RASTER_SCALE * PREFERRED_RASTER_SCALE;
|
|
1272
|
+
if (preferredPixels <= MAX_RASTER_CANVAS_PIXELS) return PREFERRED_RASTER_SCALE;
|
|
1273
|
+
return Math.sqrt(MAX_RASTER_CANVAS_PIXELS / area);
|
|
1274
|
+
}
|
|
1275
|
+
async function rasterizePdfToPdf(pdfBytes) {
|
|
1276
|
+
if (typeof window !== "undefined") {
|
|
1277
|
+
pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
1278
|
+
}
|
|
1279
|
+
let sourceDoc;
|
|
1280
|
+
try {
|
|
1281
|
+
sourceDoc = await pdfjsLib__namespace.getDocument({ data: pdfBytes.slice(0), verbosity: 0 }).promise;
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
if (error?.name === "PasswordException") {
|
|
1284
|
+
throw new PdfPasswordRequiredError2();
|
|
1285
|
+
}
|
|
1286
|
+
throw error;
|
|
1287
|
+
}
|
|
1288
|
+
try {
|
|
1289
|
+
if (sourceDoc.numPages > MAX_RASTER_PAGE_COUNT) {
|
|
1290
|
+
throw new Error(
|
|
1291
|
+
`Cannot rebuild PDF: ${sourceDoc.numPages} pages exceeds the ${MAX_RASTER_PAGE_COUNT}-page limit`
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
const outDoc = await pdfLib.PDFDocument.create();
|
|
1295
|
+
for (let pageNum = 1; pageNum <= sourceDoc.numPages; pageNum++) {
|
|
1296
|
+
const page = await sourceDoc.getPage(pageNum);
|
|
1297
|
+
const base = page.getViewport({ scale: 1 });
|
|
1298
|
+
const scale = computeRasterScale(base.width, base.height);
|
|
1299
|
+
const viewport = page.getViewport({ scale });
|
|
1300
|
+
const canvas = document.createElement("canvas");
|
|
1301
|
+
canvas.width = viewport.width;
|
|
1302
|
+
canvas.height = viewport.height;
|
|
1303
|
+
const context = canvas.getContext("2d");
|
|
1304
|
+
await page.render({ canvasContext: context, viewport }).promise;
|
|
1305
|
+
const png = await outDoc.embedPng(canvas.toDataURL("image/png"));
|
|
1306
|
+
const outPage = outDoc.addPage([base.width, base.height]);
|
|
1307
|
+
outPage.drawImage(png, { x: 0, y: 0, width: base.width, height: base.height });
|
|
1308
|
+
}
|
|
1309
|
+
return new Uint8Array(await outDoc.save({ useObjectStreams: false }));
|
|
1310
|
+
} finally {
|
|
1311
|
+
await sourceDoc.destroy();
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
async function normalizeUploadedPdfBytes(pdfBytes) {
|
|
1315
|
+
try {
|
|
1316
|
+
const normalized = await normalizePdfEncryption2(pdfBytes);
|
|
1317
|
+
return normalized === pdfBytes ? pdfBytes : new Uint8Array(normalized);
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
if (error instanceof PdfPasswordRequiredError2) throw error;
|
|
1320
|
+
console.warn(
|
|
1321
|
+
"[PageAssembler] Structural decryption failed; rebuilding document from rendered pages:",
|
|
1322
|
+
error
|
|
1323
|
+
);
|
|
1324
|
+
return rasterizePdfToPdf(pdfBytes);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
650
1327
|
async function imageToPdf(imageFile) {
|
|
651
1328
|
return new Promise((resolve, reject) => {
|
|
652
1329
|
const img = new Image();
|
|
@@ -722,6 +1399,573 @@ function downloadPdf(pdfBytes, filename) {
|
|
|
722
1399
|
a.click();
|
|
723
1400
|
URL.revokeObjectURL(url);
|
|
724
1401
|
}
|
|
1402
|
+
function getSigniphiMetadata(pdfDoc) {
|
|
1403
|
+
try {
|
|
1404
|
+
const infoRef = pdfDoc.context.trailerInfo.Info;
|
|
1405
|
+
if (!infoRef) {
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
const infoObj = pdfDoc.context.lookup(infoRef);
|
|
1409
|
+
if (!infoObj || typeof infoObj.get !== "function") {
|
|
1410
|
+
return null;
|
|
1411
|
+
}
|
|
1412
|
+
const infoDict = infoObj;
|
|
1413
|
+
const metadataObj = infoDict.get(pdfLib.PDFName.of("SigniphiMetadata"));
|
|
1414
|
+
if (!metadataObj) {
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
const metadataStr = metadataObj.toString();
|
|
1418
|
+
let jsonStr = metadataStr;
|
|
1419
|
+
if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
|
|
1420
|
+
jsonStr = metadataStr.slice(1, -1);
|
|
1421
|
+
} else if (metadataStr.startsWith("<") && metadataStr.endsWith(">")) {
|
|
1422
|
+
jsonStr = metadataStr.slice(1, -1);
|
|
1423
|
+
}
|
|
1424
|
+
const metadata = JSON.parse(jsonStr);
|
|
1425
|
+
return metadata;
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
console.error("Failed to load Signiphi metadata:", error);
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function getBaseFieldName(encodedName) {
|
|
1432
|
+
let baseName = encodedName;
|
|
1433
|
+
if (baseName.includes("__SIGNER__")) {
|
|
1434
|
+
baseName = baseName.split("__SIGNER__")[0];
|
|
1435
|
+
}
|
|
1436
|
+
if (baseName.includes("__LABEL__")) {
|
|
1437
|
+
baseName = baseName.split("__LABEL__")[0];
|
|
1438
|
+
}
|
|
1439
|
+
if (baseName.includes("__PLACEHOLDER__")) {
|
|
1440
|
+
baseName = baseName.split("__PLACEHOLDER__")[0];
|
|
1441
|
+
}
|
|
1442
|
+
return baseName;
|
|
1443
|
+
}
|
|
1444
|
+
function getSignerFromFieldName(encodedName) {
|
|
1445
|
+
if (encodedName.includes("__SIGNER__")) {
|
|
1446
|
+
const parts = encodedName.split("__SIGNER__");
|
|
1447
|
+
if (parts[1]) {
|
|
1448
|
+
return parts[1].split("__")[0] || parts[1];
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return void 0;
|
|
1452
|
+
}
|
|
1453
|
+
function determineFieldType(fieldName, widgetType) {
|
|
1454
|
+
const baseName = getBaseFieldName(fieldName);
|
|
1455
|
+
const nameLower = baseName.toLowerCase();
|
|
1456
|
+
if (nameLower.includes("_signature") || nameLower.includes("signature")) {
|
|
1457
|
+
return "signature";
|
|
1458
|
+
}
|
|
1459
|
+
if (nameLower.includes("_initials") || nameLower.includes("initials")) {
|
|
1460
|
+
return "initials";
|
|
1461
|
+
}
|
|
1462
|
+
if (nameLower.includes("_date") || nameLower.includes("date")) {
|
|
1463
|
+
return "date";
|
|
1464
|
+
}
|
|
1465
|
+
if (widgetType === "checkbox" || nameLower.includes("checkbox")) {
|
|
1466
|
+
return "checkbox";
|
|
1467
|
+
}
|
|
1468
|
+
if (widgetType === "radio" || nameLower.includes("radio")) {
|
|
1469
|
+
return "radio";
|
|
1470
|
+
}
|
|
1471
|
+
if (widgetType === "dropdown" || widgetType === "select" || nameLower.includes("dropdown")) {
|
|
1472
|
+
return "dropdown";
|
|
1473
|
+
}
|
|
1474
|
+
return "text";
|
|
1475
|
+
}
|
|
1476
|
+
async function extractFieldsFromPdf(pdfBytes) {
|
|
1477
|
+
const pdfDoc = await pdfLib.PDFDocument.load(pdfBytes);
|
|
1478
|
+
const fields = [];
|
|
1479
|
+
const metadata = getSigniphiMetadata(pdfDoc);
|
|
1480
|
+
const form = pdfDoc.getForm();
|
|
1481
|
+
const pdfFields = form.getFields();
|
|
1482
|
+
for (const field of pdfFields) {
|
|
1483
|
+
const fieldName = field.getName();
|
|
1484
|
+
const widgets = field.acroField.getWidgets();
|
|
1485
|
+
const baseName = getBaseFieldName(fieldName);
|
|
1486
|
+
const signerFromName = getSignerFromFieldName(fieldName);
|
|
1487
|
+
let required = false;
|
|
1488
|
+
let options;
|
|
1489
|
+
let widgetType;
|
|
1490
|
+
if (field instanceof pdfLib.PDFTextField) {
|
|
1491
|
+
required = field.isRequired();
|
|
1492
|
+
console.log(`[field-extraction] PDFTextField "${fieldName}" isRequired=${required}`);
|
|
1493
|
+
} else if (field instanceof pdfLib.PDFCheckBox) {
|
|
1494
|
+
widgetType = "checkbox";
|
|
1495
|
+
required = field.isRequired();
|
|
1496
|
+
console.log(`[field-extraction] PDFCheckBox "${fieldName}" isRequired=${required}`);
|
|
1497
|
+
} else if (field instanceof pdfLib.PDFDropdown) {
|
|
1498
|
+
widgetType = "dropdown";
|
|
1499
|
+
required = field.isRequired();
|
|
1500
|
+
options = field.getOptions();
|
|
1501
|
+
console.log(`[field-extraction] PDFDropdown "${fieldName}" isRequired=${required}`);
|
|
1502
|
+
} else if (field instanceof pdfLib.PDFRadioGroup) {
|
|
1503
|
+
widgetType = "radio";
|
|
1504
|
+
required = field.isRequired();
|
|
1505
|
+
options = field.getOptions();
|
|
1506
|
+
console.log(`[field-extraction] PDFRadioGroup "${fieldName}" isRequired=${required}`);
|
|
1507
|
+
} else {
|
|
1508
|
+
console.log(`[field-extraction] Unknown field type "${fieldName}" - defaulting required=false`);
|
|
1509
|
+
}
|
|
1510
|
+
for (const widget of widgets) {
|
|
1511
|
+
const rect = widget.getRectangle();
|
|
1512
|
+
const pageRef = widget.P();
|
|
1513
|
+
let pageNumber = 1;
|
|
1514
|
+
if (pageRef) {
|
|
1515
|
+
const pages = pdfDoc.getPages();
|
|
1516
|
+
for (let i = 0; i < pages.length; i++) {
|
|
1517
|
+
if (pages[i].ref === pageRef) {
|
|
1518
|
+
pageNumber = i + 1;
|
|
1519
|
+
break;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
const pageQualifiedKey = `${baseName}_p${pageNumber}`;
|
|
1524
|
+
let fieldMetadata = metadata?.fields?.[pageQualifiedKey];
|
|
1525
|
+
if (!fieldMetadata) {
|
|
1526
|
+
fieldMetadata = metadata?.fields?.[fieldName];
|
|
1527
|
+
}
|
|
1528
|
+
if (!fieldMetadata && baseName !== fieldName) {
|
|
1529
|
+
fieldMetadata = metadata?.fields?.[baseName];
|
|
1530
|
+
}
|
|
1531
|
+
let fieldRequired = required;
|
|
1532
|
+
let fieldOptions = options;
|
|
1533
|
+
if (fieldMetadata?.required !== void 0) {
|
|
1534
|
+
fieldRequired = fieldMetadata.required;
|
|
1535
|
+
}
|
|
1536
|
+
if (fieldMetadata?.options?.length) {
|
|
1537
|
+
fieldOptions = fieldMetadata.options;
|
|
1538
|
+
}
|
|
1539
|
+
const page = pdfDoc.getPage(pageNumber - 1);
|
|
1540
|
+
const pageHeight = page.getHeight();
|
|
1541
|
+
const extractedField = {
|
|
1542
|
+
name: baseName,
|
|
1543
|
+
// Use clean base name without __SIGNER__ encoding (signer stored in signer property)
|
|
1544
|
+
type: determineFieldType(fieldName, widgetType),
|
|
1545
|
+
// Uses base name internally
|
|
1546
|
+
page: pageNumber,
|
|
1547
|
+
x: rect.x,
|
|
1548
|
+
y: pageHeight - rect.y - rect.height,
|
|
1549
|
+
// Convert from PDF coords (bottom-left) to top-left
|
|
1550
|
+
width: rect.width,
|
|
1551
|
+
height: rect.height,
|
|
1552
|
+
required: fieldRequired,
|
|
1553
|
+
options: fieldOptions,
|
|
1554
|
+
signer: signerFromName
|
|
1555
|
+
// Extract signer from encoded name
|
|
1556
|
+
};
|
|
1557
|
+
if (fieldMetadata) {
|
|
1558
|
+
if (fieldMetadata.fieldId) {
|
|
1559
|
+
extractedField.fieldId = fieldMetadata.fieldId;
|
|
1560
|
+
}
|
|
1561
|
+
if (fieldMetadata.label) {
|
|
1562
|
+
extractedField.label = fieldMetadata.label;
|
|
1563
|
+
}
|
|
1564
|
+
if (!extractedField.signer && fieldMetadata.signer) {
|
|
1565
|
+
extractedField.signer = fieldMetadata.signer;
|
|
1566
|
+
}
|
|
1567
|
+
if (fieldMetadata.placeholder) {
|
|
1568
|
+
extractedField.placeholder = fieldMetadata.placeholder;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
fields.push(extractedField);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
const deduplicatedFields = fields.reduce((acc, field) => {
|
|
1575
|
+
const newBaseName = getBaseFieldName(field.name);
|
|
1576
|
+
const existingIndex = acc.findIndex((f) => {
|
|
1577
|
+
const existingBaseName = getBaseFieldName(f.name);
|
|
1578
|
+
return existingBaseName === newBaseName && f.page === field.page;
|
|
1579
|
+
});
|
|
1580
|
+
if (existingIndex === -1) {
|
|
1581
|
+
acc.push(field);
|
|
1582
|
+
} else {
|
|
1583
|
+
const existing = acc[existingIndex];
|
|
1584
|
+
acc[existingIndex] = {
|
|
1585
|
+
...existing,
|
|
1586
|
+
// Prefer non-empty values from either field
|
|
1587
|
+
label: existing.label || field.label,
|
|
1588
|
+
placeholder: existing.placeholder || field.placeholder,
|
|
1589
|
+
required: existing.required || field.required,
|
|
1590
|
+
options: existing.options?.length ? existing.options : field.options,
|
|
1591
|
+
fieldId: existing.fieldId || field.fieldId,
|
|
1592
|
+
// Prefer signer from the field that has it
|
|
1593
|
+
signer: existing.signer || field.signer
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
return acc;
|
|
1597
|
+
}, []);
|
|
1598
|
+
return deduplicatedFields;
|
|
1599
|
+
}
|
|
1600
|
+
function setSigniphiMetadata(pdfDoc, metadata) {
|
|
1601
|
+
try {
|
|
1602
|
+
const infoRef = pdfDoc.context.trailerInfo.Info;
|
|
1603
|
+
const infoObj = pdfDoc.context.lookup(infoRef);
|
|
1604
|
+
if (!infoObj || typeof infoObj.set !== "function") {
|
|
1605
|
+
throw new Error("Info object is not a PDFDict or does not have set method");
|
|
1606
|
+
}
|
|
1607
|
+
const infoDict = infoObj;
|
|
1608
|
+
const metadataString = JSON.stringify(metadata);
|
|
1609
|
+
infoDict.set(pdfLib.PDFName.of("SigniphiMetadata"), pdfLib.PDFString.of(metadataString));
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
console.error("Failed to set Signiphi metadata:", error);
|
|
1612
|
+
throw error;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
1616
|
+
FormFieldType2["TEXT"] = "text";
|
|
1617
|
+
FormFieldType2["SIGNATURE"] = "signature";
|
|
1618
|
+
FormFieldType2["INITIALS"] = "initials";
|
|
1619
|
+
FormFieldType2["DATE"] = "date";
|
|
1620
|
+
FormFieldType2["CHECKBOX"] = "checkbox";
|
|
1621
|
+
FormFieldType2["RADIO"] = "radio";
|
|
1622
|
+
FormFieldType2["DROPDOWN"] = "dropdown";
|
|
1623
|
+
FormFieldType2["TEXT_LABEL"] = "text_label";
|
|
1624
|
+
return FormFieldType2;
|
|
1625
|
+
})(FormFieldType || {});
|
|
1626
|
+
function parseEncodedFieldName(encodedName) {
|
|
1627
|
+
let baseName = encodedName;
|
|
1628
|
+
let label;
|
|
1629
|
+
let signer;
|
|
1630
|
+
if (baseName.includes("__SIGNER__")) {
|
|
1631
|
+
const parts = baseName.split("__SIGNER__");
|
|
1632
|
+
baseName = parts[0];
|
|
1633
|
+
signer = parts[1];
|
|
1634
|
+
}
|
|
1635
|
+
if (baseName.includes("__LABEL__")) {
|
|
1636
|
+
const parts = baseName.split("__LABEL__");
|
|
1637
|
+
baseName = parts[0];
|
|
1638
|
+
label = parts[1];
|
|
1639
|
+
}
|
|
1640
|
+
if (baseName.includes("__PLACEHOLDER__")) {
|
|
1641
|
+
const parts = baseName.split("__PLACEHOLDER__");
|
|
1642
|
+
baseName = parts[0];
|
|
1643
|
+
}
|
|
1644
|
+
return { baseName, label, signer };
|
|
1645
|
+
}
|
|
1646
|
+
function encodeFieldNameWithSigner(fieldName, signerEmail) {
|
|
1647
|
+
const { baseName, signer: existingSigner } = parseEncodedFieldName(fieldName);
|
|
1648
|
+
const finalSigner = existingSigner || signerEmail;
|
|
1649
|
+
if (!finalSigner) return baseName;
|
|
1650
|
+
return `${baseName}__SIGNER__${finalSigner}`;
|
|
1651
|
+
}
|
|
1652
|
+
function encodeFieldNameWithLabelAndSigner(fieldName, label, signerEmail) {
|
|
1653
|
+
const { baseName, label: existingLabel, signer: existingSigner } = parseEncodedFieldName(fieldName);
|
|
1654
|
+
const finalLabel = existingLabel || label;
|
|
1655
|
+
const finalSigner = existingSigner || signerEmail;
|
|
1656
|
+
let name = baseName;
|
|
1657
|
+
if (finalLabel && finalLabel.trim()) {
|
|
1658
|
+
name = `${name}__LABEL__${finalLabel.trim()}`;
|
|
1659
|
+
}
|
|
1660
|
+
if (finalSigner) {
|
|
1661
|
+
name = `${name}__SIGNER__${finalSigner}`;
|
|
1662
|
+
}
|
|
1663
|
+
return name;
|
|
1664
|
+
}
|
|
1665
|
+
async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
|
|
1666
|
+
const { removeExistingFields = false, drawLabels = false } = options;
|
|
1667
|
+
const pdfDoc = await pdfLib.PDFDocument.load(pdfBytes);
|
|
1668
|
+
const form = pdfDoc.getForm();
|
|
1669
|
+
const pages = pdfDoc.getPages();
|
|
1670
|
+
if (removeExistingFields) {
|
|
1671
|
+
const existingFields = form.getFields();
|
|
1672
|
+
for (const field of existingFields) {
|
|
1673
|
+
try {
|
|
1674
|
+
const acroField = field.acroField;
|
|
1675
|
+
if (acroField && typeof acroField.getWidgets === "function") {
|
|
1676
|
+
let widgets = acroField.getWidgets();
|
|
1677
|
+
let safetyCounter = 0;
|
|
1678
|
+
const maxIterations = widgets.length + 5;
|
|
1679
|
+
while (widgets.length > 0 && safetyCounter < maxIterations) {
|
|
1680
|
+
try {
|
|
1681
|
+
acroField.removeWidget(widgets.length - 1);
|
|
1682
|
+
widgets = acroField.getWidgets();
|
|
1683
|
+
} catch {
|
|
1684
|
+
break;
|
|
1685
|
+
}
|
|
1686
|
+
safetyCounter++;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
form.removeField(field);
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
console.warn(`Failed to remove field ${field.getName()}:`, error);
|
|
1692
|
+
try {
|
|
1693
|
+
form.removeField(field);
|
|
1694
|
+
} catch {
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
const font = await pdfDoc.embedFont(pdfLib.StandardFonts.Helvetica);
|
|
1700
|
+
const fieldNamePages = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (const field of formFields) {
|
|
1702
|
+
const baseName = field.name;
|
|
1703
|
+
if (!fieldNamePages.has(baseName)) {
|
|
1704
|
+
fieldNamePages.set(baseName, /* @__PURE__ */ new Set());
|
|
1705
|
+
}
|
|
1706
|
+
fieldNamePages.get(baseName).add(field.position.page);
|
|
1707
|
+
}
|
|
1708
|
+
const conflictingNames = /* @__PURE__ */ new Set();
|
|
1709
|
+
for (const [name, pages2] of fieldNamePages) {
|
|
1710
|
+
if (pages2.size > 1) {
|
|
1711
|
+
conflictingNames.add(name);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
for (const field of formFields) {
|
|
1715
|
+
const pageIndex = field.position.page - 1;
|
|
1716
|
+
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
1717
|
+
console.warn(`Field ${field.name} references page ${field.position.page} but PDF only has ${pages.length} pages`);
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
const page = pages[pageIndex];
|
|
1721
|
+
const { height: pageHeight } = page.getSize();
|
|
1722
|
+
const pdfX = field.position.x;
|
|
1723
|
+
const pdfY = pageHeight - field.position.y - field.position.height;
|
|
1724
|
+
const uniqueFieldName = conflictingNames.has(field.name) ? `${field.name}_p${field.position.page}` : field.name;
|
|
1725
|
+
try {
|
|
1726
|
+
const fieldType = typeof field.type === "string" ? field.type : field.type;
|
|
1727
|
+
switch (fieldType) {
|
|
1728
|
+
case "text" /* TEXT */: {
|
|
1729
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1730
|
+
const textField = form.createTextField(encodedName);
|
|
1731
|
+
textField.addToPage(page, {
|
|
1732
|
+
x: pdfX,
|
|
1733
|
+
y: pdfY,
|
|
1734
|
+
width: field.position.width,
|
|
1735
|
+
height: field.position.height,
|
|
1736
|
+
borderColor: pdfLib.rgb(0.5, 0.5, 0.5),
|
|
1737
|
+
backgroundColor: pdfLib.rgb(1, 1, 1)
|
|
1738
|
+
});
|
|
1739
|
+
if (field.label && field.label.trim()) {
|
|
1740
|
+
textField.acroField.dict.set(pdfLib.PDFName.of("TU"), pdfLib.PDFString.of(field.label));
|
|
1741
|
+
if (drawLabels) {
|
|
1742
|
+
const fontSize = Math.min(10, field.position.height * 0.4);
|
|
1743
|
+
const labelY = pdfY + field.position.height + 5;
|
|
1744
|
+
page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: pdfLib.rgb(0, 0, 0) });
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (field.defaultValue && field.defaultValue.trim()) {
|
|
1748
|
+
textField.setText(field.defaultValue);
|
|
1749
|
+
}
|
|
1750
|
+
if (field.fontSize && field.fontSize >= 8 && field.fontSize <= 72) {
|
|
1751
|
+
try {
|
|
1752
|
+
textField.setFontSize(field.fontSize);
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
if (field.multiline) {
|
|
1757
|
+
try {
|
|
1758
|
+
textField.enableMultiline();
|
|
1759
|
+
} catch {
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
if (field.maxLength && field.maxLength > 0) {
|
|
1763
|
+
try {
|
|
1764
|
+
textField.setMaxLength(field.maxLength);
|
|
1765
|
+
} catch {
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
if (field.required) {
|
|
1769
|
+
textField.enableRequired();
|
|
1770
|
+
}
|
|
1771
|
+
break;
|
|
1772
|
+
}
|
|
1773
|
+
case "checkbox" /* CHECKBOX */: {
|
|
1774
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1775
|
+
const checkBox = form.createCheckBox(encodedName);
|
|
1776
|
+
checkBox.addToPage(page, {
|
|
1777
|
+
x: pdfX,
|
|
1778
|
+
y: pdfY,
|
|
1779
|
+
width: field.position.width,
|
|
1780
|
+
height: field.position.height,
|
|
1781
|
+
borderColor: pdfLib.rgb(0.5, 0.5, 0.5),
|
|
1782
|
+
backgroundColor: pdfLib.rgb(1, 1, 1)
|
|
1783
|
+
});
|
|
1784
|
+
if (field.defaultValue === "true" || field.defaultValue === "checked") {
|
|
1785
|
+
checkBox.check();
|
|
1786
|
+
}
|
|
1787
|
+
if (field.required) {
|
|
1788
|
+
checkBox.enableRequired();
|
|
1789
|
+
}
|
|
1790
|
+
if (field.label && field.label.trim()) {
|
|
1791
|
+
try {
|
|
1792
|
+
checkBox.acroField.dict.set(pdfLib.PDFName.of("TU"), pdfLib.PDFString.of(field.label));
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
if (drawLabels) {
|
|
1796
|
+
const fontSize = Math.min(12, field.position.height * 0.6);
|
|
1797
|
+
const labelX = pdfX + field.position.width + 5;
|
|
1798
|
+
const labelY = pdfY + (field.position.height - fontSize) / 2;
|
|
1799
|
+
page.drawText(field.label, { x: labelX, y: labelY, size: fontSize, font, color: pdfLib.rgb(0, 0, 0) });
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
break;
|
|
1803
|
+
}
|
|
1804
|
+
case "signature" /* SIGNATURE */: {
|
|
1805
|
+
const baseWithSuffix = field.name.endsWith("_signature") ? field.name : `${field.name}_signature`;
|
|
1806
|
+
const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
|
|
1807
|
+
const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
|
|
1808
|
+
const sigField = form.createTextField(encodedName);
|
|
1809
|
+
sigField.addToPage(page, {
|
|
1810
|
+
x: pdfX,
|
|
1811
|
+
y: pdfY,
|
|
1812
|
+
width: field.position.width,
|
|
1813
|
+
height: field.position.height,
|
|
1814
|
+
borderColor: pdfLib.rgb(0, 0, 1),
|
|
1815
|
+
backgroundColor: pdfLib.rgb(0.9, 0.9, 1)
|
|
1816
|
+
});
|
|
1817
|
+
if (field.label && field.label.trim()) {
|
|
1818
|
+
sigField.acroField.dict.set(pdfLib.PDFName.of("TU"), pdfLib.PDFString.of(field.label));
|
|
1819
|
+
}
|
|
1820
|
+
sigField.enableReadOnly();
|
|
1821
|
+
if (field.required) {
|
|
1822
|
+
sigField.enableRequired();
|
|
1823
|
+
}
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
case "initials" /* INITIALS */: {
|
|
1827
|
+
const baseWithSuffix = field.name.endsWith("_initials") ? field.name : `${field.name}_initials`;
|
|
1828
|
+
const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
|
|
1829
|
+
const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
|
|
1830
|
+
const initField = form.createTextField(encodedName);
|
|
1831
|
+
initField.addToPage(page, {
|
|
1832
|
+
x: pdfX,
|
|
1833
|
+
y: pdfY,
|
|
1834
|
+
width: field.position.width,
|
|
1835
|
+
height: field.position.height,
|
|
1836
|
+
borderColor: pdfLib.rgb(0.5, 0, 0.5),
|
|
1837
|
+
backgroundColor: pdfLib.rgb(1, 0.95, 1)
|
|
1838
|
+
});
|
|
1839
|
+
if (field.label && field.label.trim()) {
|
|
1840
|
+
initField.acroField.dict.set(pdfLib.PDFName.of("TU"), pdfLib.PDFString.of(field.label));
|
|
1841
|
+
}
|
|
1842
|
+
initField.enableReadOnly();
|
|
1843
|
+
if (field.required) {
|
|
1844
|
+
initField.enableRequired();
|
|
1845
|
+
}
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
case "date" /* DATE */: {
|
|
1849
|
+
const baseWithSuffix = field.name.endsWith("_date") ? field.name : `${field.name}_date`;
|
|
1850
|
+
const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
|
|
1851
|
+
const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
|
|
1852
|
+
const dateField = form.createTextField(encodedName);
|
|
1853
|
+
dateField.addToPage(page, {
|
|
1854
|
+
x: pdfX,
|
|
1855
|
+
y: pdfY,
|
|
1856
|
+
width: field.position.width,
|
|
1857
|
+
height: field.position.height,
|
|
1858
|
+
borderColor: pdfLib.rgb(0, 0.5, 0),
|
|
1859
|
+
backgroundColor: pdfLib.rgb(0.95, 1, 0.95)
|
|
1860
|
+
});
|
|
1861
|
+
if (field.label && field.label.trim()) {
|
|
1862
|
+
dateField.acroField.dict.set(pdfLib.PDFName.of("TU"), pdfLib.PDFString.of(field.label));
|
|
1863
|
+
}
|
|
1864
|
+
if (field.required) {
|
|
1865
|
+
dateField.enableRequired();
|
|
1866
|
+
}
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
case "dropdown" /* DROPDOWN */: {
|
|
1870
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1871
|
+
const dropdown = form.createDropdown(encodedName);
|
|
1872
|
+
dropdown.addToPage(page, {
|
|
1873
|
+
x: pdfX,
|
|
1874
|
+
y: pdfY,
|
|
1875
|
+
width: field.position.width,
|
|
1876
|
+
height: field.position.height,
|
|
1877
|
+
borderColor: pdfLib.rgb(0.5, 0.5, 0.5),
|
|
1878
|
+
backgroundColor: pdfLib.rgb(1, 1, 1)
|
|
1879
|
+
});
|
|
1880
|
+
if (field.options && field.options.length > 0) {
|
|
1881
|
+
dropdown.addOptions(field.options);
|
|
1882
|
+
if (field.defaultValue && field.options.includes(field.defaultValue)) {
|
|
1883
|
+
dropdown.select(field.defaultValue);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
if (field.label && field.label.trim()) {
|
|
1887
|
+
try {
|
|
1888
|
+
dropdown.acroField.dict.set(pdfLib.PDFName.of("TU"), pdfLib.PDFString.of(field.label));
|
|
1889
|
+
} catch {
|
|
1890
|
+
}
|
|
1891
|
+
if (drawLabels) {
|
|
1892
|
+
const fontSize = Math.min(10, field.position.height * 0.4);
|
|
1893
|
+
const labelY = pdfY + field.position.height + 5;
|
|
1894
|
+
page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: pdfLib.rgb(0, 0, 0) });
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
if (field.required) {
|
|
1898
|
+
dropdown.enableRequired();
|
|
1899
|
+
}
|
|
1900
|
+
break;
|
|
1901
|
+
}
|
|
1902
|
+
case "radio" /* RADIO */: {
|
|
1903
|
+
const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
|
|
1904
|
+
const radioGroup = form.createRadioGroup(encodedName);
|
|
1905
|
+
if (field.options && field.options.length > 0) {
|
|
1906
|
+
const optionHeight = Math.min(20, field.position.height / field.options.length);
|
|
1907
|
+
const spacing = field.position.height / field.options.length;
|
|
1908
|
+
field.options.forEach((option, index) => {
|
|
1909
|
+
const optionY = pdfY + field.position.height - (index + 1) * spacing + (spacing - optionHeight) / 2;
|
|
1910
|
+
radioGroup.addOptionToPage(option, page, {
|
|
1911
|
+
x: pdfX,
|
|
1912
|
+
y: optionY,
|
|
1913
|
+
width: optionHeight,
|
|
1914
|
+
height: optionHeight,
|
|
1915
|
+
borderColor: pdfLib.rgb(0.5, 0.5, 0.5),
|
|
1916
|
+
backgroundColor: pdfLib.rgb(1, 1, 1)
|
|
1917
|
+
});
|
|
1918
|
+
if (drawLabels) {
|
|
1919
|
+
const labelX = pdfX + optionHeight + 5;
|
|
1920
|
+
const labelY = optionY + optionHeight / 4;
|
|
1921
|
+
page.drawText(option, { x: labelX, y: labelY, size: 10, font, color: pdfLib.rgb(0, 0, 0) });
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
if (field.defaultValue && field.options.includes(field.defaultValue)) {
|
|
1925
|
+
radioGroup.select(field.defaultValue);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
if (field.label && field.label.trim()) {
|
|
1929
|
+
try {
|
|
1930
|
+
radioGroup.acroField.dict.set(pdfLib.PDFName.of("TU"), pdfLib.PDFString.of(field.label));
|
|
1931
|
+
} catch {
|
|
1932
|
+
}
|
|
1933
|
+
if (drawLabels) {
|
|
1934
|
+
const fontSize = 10;
|
|
1935
|
+
const labelY = pdfY + field.position.height + 5;
|
|
1936
|
+
page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: pdfLib.rgb(0, 0, 0) });
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
if (field.required) {
|
|
1940
|
+
radioGroup.enableRequired();
|
|
1941
|
+
}
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
default:
|
|
1945
|
+
console.warn(`Unknown field type: ${fieldType} for field ${field.name}`);
|
|
1946
|
+
}
|
|
1947
|
+
} catch (error) {
|
|
1948
|
+
console.error(`Error adding field ${field.name}:`, error);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
return await pdfDoc.save();
|
|
1952
|
+
}
|
|
1953
|
+
function mapFieldPositionsAfterAssembly(fields, pageMapping) {
|
|
1954
|
+
return fields.map((field) => {
|
|
1955
|
+
const mappingKey = String(field.position.page - 1);
|
|
1956
|
+
const newPageNumber = pageMapping.get(mappingKey);
|
|
1957
|
+
if (newPageNumber !== void 0) {
|
|
1958
|
+
return {
|
|
1959
|
+
...field,
|
|
1960
|
+
position: { ...field.position, page: newPageNumber }
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
return field;
|
|
1964
|
+
}).filter((field) => {
|
|
1965
|
+
const mappingKey = String(field.position.page - 1);
|
|
1966
|
+
return pageMapping.has(mappingKey) || pageMapping.size === 0;
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
725
1969
|
var isMultiSelectModifier = (event) => {
|
|
726
1970
|
return event.ctrlKey || event.metaKey;
|
|
727
1971
|
};
|
|
@@ -1118,6 +2362,7 @@ var FileCard = ({
|
|
|
1118
2362
|
const [newName, setNewName] = React7.useState(file.name);
|
|
1119
2363
|
const fileSelectedPages = file.pages.filter((p) => selectedPageIds.includes(p.id));
|
|
1120
2364
|
const allPagesSelected = file.pages.length > 0 && fileSelectedPages.length === file.pages.length;
|
|
2365
|
+
const pendingDropdownAction = React7.useRef(null);
|
|
1121
2366
|
const [{ isDraggingFile }, dragFile] = reactDnd.useDrag({
|
|
1122
2367
|
type: DND_TYPES.FILE,
|
|
1123
2368
|
item: () => {
|
|
@@ -1222,23 +2467,38 @@ var FileCard = ({
|
|
|
1222
2467
|
] }) }),
|
|
1223
2468
|
/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
1224
2469
|
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "hover:bg-gray-100 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-4 w-4" }) }) }),
|
|
1225
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
2470
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2471
|
+
DropdownMenuContent,
|
|
2472
|
+
{
|
|
2473
|
+
align: "end",
|
|
2474
|
+
className: "w-52",
|
|
2475
|
+
onCloseAutoFocus: () => {
|
|
2476
|
+
pendingDropdownAction.current?.();
|
|
2477
|
+
pendingDropdownAction.current = null;
|
|
2478
|
+
},
|
|
2479
|
+
children: [
|
|
2480
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { onSelect: () => {
|
|
2481
|
+
pendingDropdownAction.current = () => setIsRenaming(true);
|
|
2482
|
+
}, children: [
|
|
2483
|
+
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-4 w-4 mr-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.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" }) }),
|
|
2484
|
+
"Rename File"
|
|
2485
|
+
] }),
|
|
2486
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2487
|
+
DropdownMenuItem,
|
|
2488
|
+
{
|
|
2489
|
+
onSelect: () => {
|
|
2490
|
+
pendingDropdownAction.current = () => onRemoveFile(file.id);
|
|
2491
|
+
},
|
|
2492
|
+
className: "text-red-600 hover:text-red-700 hover:bg-red-50 focus:text-red-700 focus:bg-red-50",
|
|
2493
|
+
children: [
|
|
2494
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4 mr-2" }),
|
|
2495
|
+
"Remove File"
|
|
2496
|
+
]
|
|
2497
|
+
}
|
|
2498
|
+
)
|
|
2499
|
+
]
|
|
2500
|
+
}
|
|
2501
|
+
)
|
|
1242
2502
|
] })
|
|
1243
2503
|
] })
|
|
1244
2504
|
] }) }),
|
|
@@ -1347,6 +2607,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
1347
2607
|
);
|
|
1348
2608
|
const [isLoading, setIsLoading] = React7.useState(false);
|
|
1349
2609
|
const [processingStatus, setProcessingStatus] = React7.useState("");
|
|
2610
|
+
const [uploadError, setUploadError] = React7.useState(null);
|
|
1350
2611
|
const [showDeleteConfirm, setShowDeleteConfirm] = React7.useState(false);
|
|
1351
2612
|
const [showRemoveFileConfirm, setShowRemoveFileConfirm] = React7.useState(false);
|
|
1352
2613
|
const [fileToRemove, setFileToRemove] = React7.useState(null);
|
|
@@ -1354,6 +2615,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
1354
2615
|
const processedInitialPdfRef = React7.useRef(null);
|
|
1355
2616
|
const isDraggingPage = React7.useRef(false);
|
|
1356
2617
|
const onChangeRef = React7.useRef(onChange);
|
|
2618
|
+
const originalBytesCache = React7.useRef(/* @__PURE__ */ new Map());
|
|
1357
2619
|
const summary = React7.useMemo(() => ({
|
|
1358
2620
|
totalFiles: state.files.length,
|
|
1359
2621
|
totalPages: state.files.reduce((sum, file) => sum + file.pages.length, 0)
|
|
@@ -1387,6 +2649,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
1387
2649
|
} else {
|
|
1388
2650
|
pdfBytes = new Uint8Array(pdf);
|
|
1389
2651
|
}
|
|
2652
|
+
pdfBytes = await normalizeUploadedPdfBytes(pdfBytes);
|
|
1390
2653
|
const defensiveCopy = new Uint8Array(pdfBytes);
|
|
1391
2654
|
const pages = await pdfToImages(defensiveCopy, { hideFormFields: true });
|
|
1392
2655
|
if (pages.length === 0) {
|
|
@@ -1423,6 +2686,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
1423
2686
|
originalFileId: fileId
|
|
1424
2687
|
}))
|
|
1425
2688
|
};
|
|
2689
|
+
originalBytesCache.current.set(fileId, originalBytes);
|
|
1426
2690
|
dispatch({ type: "ADD_FILE", payload: file });
|
|
1427
2691
|
} catch (error) {
|
|
1428
2692
|
console.error("Error processing initial PDF:", error);
|
|
@@ -1431,8 +2695,9 @@ var PageAssembler = React7.forwardRef(
|
|
|
1431
2695
|
setProcessingStatus("");
|
|
1432
2696
|
}
|
|
1433
2697
|
}, []);
|
|
2698
|
+
const hasRestorableState = !!initialState && initialState.files.length > 0;
|
|
1434
2699
|
React7.useEffect(() => {
|
|
1435
|
-
if (initialPdf && !
|
|
2700
|
+
if (initialPdf && state.files.length === 0 && !hasRestorableState) {
|
|
1436
2701
|
const pdfIdentifier = initialPdf instanceof Uint8Array ? `uint8array-${initialPdf.length}-${initialPdf[0]}-${initialPdf[initialPdf.length - 1]}` : `${initialPdf.constructor.name}-${initialPdf.size || initialPdf.byteLength}`;
|
|
1437
2702
|
if (processedInitialPdfRef.current === pdfIdentifier) {
|
|
1438
2703
|
return;
|
|
@@ -1440,15 +2705,16 @@ var PageAssembler = React7.forwardRef(
|
|
|
1440
2705
|
processedInitialPdfRef.current = pdfIdentifier;
|
|
1441
2706
|
processInitialPdf(initialPdf);
|
|
1442
2707
|
}
|
|
1443
|
-
}, [initialPdf,
|
|
2708
|
+
}, [initialPdf, state.files.length, hasRestorableState, processInitialPdf]);
|
|
1444
2709
|
React7.useEffect(() => {
|
|
1445
|
-
if (initialState) {
|
|
2710
|
+
if (initialState && initialState.files.length > 0) {
|
|
1446
2711
|
dispatch({ type: "RESET", payload: initialState });
|
|
1447
2712
|
}
|
|
1448
2713
|
}, [initialState]);
|
|
1449
2714
|
const processFiles = React7.useCallback(async (files) => {
|
|
1450
2715
|
const fileArray = Array.from(files);
|
|
1451
2716
|
const maxFileSize = (limits?.maxFileSizeMB || 10) * 1024 * 1024;
|
|
2717
|
+
setUploadError(null);
|
|
1452
2718
|
setIsLoading(true);
|
|
1453
2719
|
setProcessingStatus(`Processing ${fileArray.length} file${fileArray.length !== 1 ? "s" : ""}...`);
|
|
1454
2720
|
for (const file of fileArray) {
|
|
@@ -1473,8 +2739,9 @@ var PageAssembler = React7.forwardRef(
|
|
|
1473
2739
|
if (file.type === "application/pdf") {
|
|
1474
2740
|
setProcessingStatus(`Processing PDF: ${file.name}`);
|
|
1475
2741
|
const arrayBuffer = await file.arrayBuffer();
|
|
1476
|
-
const pdfBytes = new Uint8Array(arrayBuffer);
|
|
2742
|
+
const pdfBytes = await normalizeUploadedPdfBytes(new Uint8Array(arrayBuffer));
|
|
1477
2743
|
fileInfo.originalBytes = new Uint8Array(pdfBytes);
|
|
2744
|
+
originalBytesCache.current.set(fileInfo.id, fileInfo.originalBytes);
|
|
1478
2745
|
const defensiveCopy = new Uint8Array(pdfBytes);
|
|
1479
2746
|
const pages = await pdfToImages(defensiveCopy, { hideFormFields: true });
|
|
1480
2747
|
fileInfo.pages = pages.map((page, index) => ({
|
|
@@ -1491,6 +2758,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
1491
2758
|
const arrayBuffer = await file.arrayBuffer();
|
|
1492
2759
|
const imageBytes = new Uint8Array(arrayBuffer);
|
|
1493
2760
|
fileInfo.originalBytes = new Uint8Array(imageBytes);
|
|
2761
|
+
originalBytesCache.current.set(fileInfo.id, fileInfo.originalBytes);
|
|
1494
2762
|
const thumbUrl = URL.createObjectURL(file);
|
|
1495
2763
|
const img = new Image();
|
|
1496
2764
|
const dimensions = await new Promise((resolve) => {
|
|
@@ -1521,6 +2789,11 @@ var PageAssembler = React7.forwardRef(
|
|
|
1521
2789
|
dispatch({ type: "ADD_FILE", payload: fileInfo });
|
|
1522
2790
|
} catch (error) {
|
|
1523
2791
|
console.error(`Error processing file ${file.name}:`, error);
|
|
2792
|
+
if (error instanceof PdfPasswordRequiredError2) {
|
|
2793
|
+
setUploadError(`${file.name}: ${error.userMessage}`);
|
|
2794
|
+
} else {
|
|
2795
|
+
setUploadError(`Failed to process "${file.name}". The file may be corrupted or unsupported.`);
|
|
2796
|
+
}
|
|
1524
2797
|
}
|
|
1525
2798
|
}
|
|
1526
2799
|
setIsLoading(false);
|
|
@@ -1618,21 +2891,205 @@ var PageAssembler = React7.forwardRef(
|
|
|
1618
2891
|
setIsLoading(true);
|
|
1619
2892
|
setProcessingStatus("Assembling PDF...");
|
|
1620
2893
|
try {
|
|
1621
|
-
const { PDFDocument:
|
|
1622
|
-
const combinedDoc = await PDFDocument2.create();
|
|
2894
|
+
const { PDFDocument: PDFDocument5, PDFName: PDFName4, PDFString: PDFString4 } = await import('pdf-lib');
|
|
1623
2895
|
const loadedPdfs = /* @__PURE__ */ new Map();
|
|
1624
2896
|
const fileMap = /* @__PURE__ */ new Map();
|
|
2897
|
+
const pdfFiles = state.files.filter((f) => f.type === "pdf" && f.originalBytes?.length > 0);
|
|
2898
|
+
const imageFiles = state.files.filter((f) => f.type === "image");
|
|
2899
|
+
const isSinglePdfScenario = pdfFiles.length === 1 && imageFiles.length === 0;
|
|
1625
2900
|
for (const file of state.files) {
|
|
1626
2901
|
fileMap.set(file.id, file);
|
|
1627
|
-
if (file.type === "pdf" && file.originalBytes) {
|
|
2902
|
+
if (file.type === "pdf" && file.originalBytes && file.originalBytes.length > 0) {
|
|
1628
2903
|
try {
|
|
1629
|
-
const sourcePdf = await
|
|
2904
|
+
const sourcePdf = await PDFDocument5.load(file.originalBytes);
|
|
1630
2905
|
loadedPdfs.set(file.id, sourcePdf);
|
|
1631
2906
|
} catch (error) {
|
|
1632
2907
|
console.error(`Error loading PDF for file ${file.id}:`, error);
|
|
1633
2908
|
}
|
|
1634
2909
|
}
|
|
1635
2910
|
}
|
|
2911
|
+
const referencedFileIds = /* @__PURE__ */ new Set();
|
|
2912
|
+
for (const file of state.files) {
|
|
2913
|
+
for (const page of file.pages) {
|
|
2914
|
+
const originalFileId = page.originalFileId || file.id;
|
|
2915
|
+
if (!loadedPdfs.has(originalFileId)) {
|
|
2916
|
+
referencedFileIds.add(originalFileId);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
for (const fileId of referencedFileIds) {
|
|
2921
|
+
const cachedBytes = originalBytesCache.current.get(fileId);
|
|
2922
|
+
if (cachedBytes && cachedBytes.length > 0) {
|
|
2923
|
+
try {
|
|
2924
|
+
console.log(`[PageAssembler] Loading PDF from cache for deleted file ${fileId}`);
|
|
2925
|
+
const sourcePdf = await PDFDocument5.load(cachedBytes);
|
|
2926
|
+
loadedPdfs.set(fileId, sourcePdf);
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
console.error(`Error loading cached PDF for file ${fileId}:`, error);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
console.log("[PageAssembler] Checking single PDF scenario:", {
|
|
2933
|
+
isSinglePdfScenario,
|
|
2934
|
+
pdfFilesCount: pdfFiles.length,
|
|
2935
|
+
imageFilesCount: imageFiles.length
|
|
2936
|
+
});
|
|
2937
|
+
if (isSinglePdfScenario && pdfFiles[0]) {
|
|
2938
|
+
const sourceFile = pdfFiles[0];
|
|
2939
|
+
const sourcePdf = loadedPdfs.get(sourceFile.id);
|
|
2940
|
+
console.log("[PageAssembler] Single PDF check:", {
|
|
2941
|
+
sourcePdfLoaded: !!sourcePdf,
|
|
2942
|
+
pagesCount: sourceFile.pages.length,
|
|
2943
|
+
originalBytesLength: sourceFile.originalBytes?.length
|
|
2944
|
+
});
|
|
2945
|
+
if (sourcePdf) {
|
|
2946
|
+
const desiredPageIndices = sourceFile.pages.map((p) => p.srcIndex);
|
|
2947
|
+
const originalPageCount = sourcePdf.getPageCount();
|
|
2948
|
+
const allPagesFromSameFile = sourceFile.pages.every(
|
|
2949
|
+
(p) => (p.originalFileId || sourceFile.id) === sourceFile.id
|
|
2950
|
+
);
|
|
2951
|
+
const isOriginalOrder = desiredPageIndices.length === originalPageCount && desiredPageIndices.every((idx, i) => idx === i);
|
|
2952
|
+
console.log("[PageAssembler] Original bytes check:", {
|
|
2953
|
+
allPagesFromSameFile,
|
|
2954
|
+
isOriginalOrder,
|
|
2955
|
+
desiredPageIndices,
|
|
2956
|
+
originalPageCount
|
|
2957
|
+
});
|
|
2958
|
+
if (allPagesFromSameFile && isOriginalOrder) {
|
|
2959
|
+
console.log("[PageAssembler] \u2705 Returning ORIGINAL bytes (form fields preserved)");
|
|
2960
|
+
const arrayBuffer2 = sourceFile.originalBytes.buffer.slice(
|
|
2961
|
+
sourceFile.originalBytes.byteOffset,
|
|
2962
|
+
sourceFile.originalBytes.byteOffset + sourceFile.originalBytes.byteLength
|
|
2963
|
+
);
|
|
2964
|
+
const blob2 = new Blob([arrayBuffer2], { type: "application/pdf" });
|
|
2965
|
+
return blob2;
|
|
2966
|
+
} else {
|
|
2967
|
+
console.log("[PageAssembler] \u26A0\uFE0F Pages modified, will use copyPages (form fields may be lost)");
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
console.log("[PageAssembler] Starting multi-PDF assembly with field preservation");
|
|
2972
|
+
const allExtractedFields = [];
|
|
2973
|
+
const mergedMetadata = {
|
|
2974
|
+
version: "1.0",
|
|
2975
|
+
fields: {},
|
|
2976
|
+
fieldIdIndex: {}
|
|
2977
|
+
};
|
|
2978
|
+
const filePageUsage = /* @__PURE__ */ new Map();
|
|
2979
|
+
for (const file of state.files) {
|
|
2980
|
+
for (const page of file.pages) {
|
|
2981
|
+
const originalFileId = page.originalFileId || file.id;
|
|
2982
|
+
if (!filePageUsage.has(originalFileId)) {
|
|
2983
|
+
filePageUsage.set(originalFileId, /* @__PURE__ */ new Map());
|
|
2984
|
+
}
|
|
2985
|
+
const pageUsage = filePageUsage.get(originalFileId);
|
|
2986
|
+
if (!pageUsage.has(page.srcIndex)) {
|
|
2987
|
+
pageUsage.set(page.srcIndex, []);
|
|
2988
|
+
}
|
|
2989
|
+
pageUsage.get(page.srcIndex).push(page);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
let combinedPageIndex = 0;
|
|
2993
|
+
const pageIdToNewPageNumber = /* @__PURE__ */ new Map();
|
|
2994
|
+
for (const file of state.files) {
|
|
2995
|
+
for (const page of file.pages) {
|
|
2996
|
+
combinedPageIndex++;
|
|
2997
|
+
pageIdToNewPageNumber.set(page.id, combinedPageIndex);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
console.log("[PageAssembler] Page mapping:", Object.fromEntries(pageIdToNewPageNumber));
|
|
3001
|
+
const extractAndMapFields = async (fileId, fileName, pdfBytes2, sourcePdf) => {
|
|
3002
|
+
let sourceMetadata = null;
|
|
3003
|
+
if (sourcePdf) {
|
|
3004
|
+
try {
|
|
3005
|
+
const sourceInfoRef = sourcePdf.context.trailerInfo.Info;
|
|
3006
|
+
if (sourceInfoRef) {
|
|
3007
|
+
const sourceInfoDict = sourcePdf.context.lookup(sourceInfoRef);
|
|
3008
|
+
if (sourceInfoDict && typeof sourceInfoDict.get === "function") {
|
|
3009
|
+
const metadataObj = sourceInfoDict.get(PDFName4.of("SigniphiMetadata"));
|
|
3010
|
+
if (metadataObj) {
|
|
3011
|
+
const metadataStr = metadataObj.toString();
|
|
3012
|
+
let jsonStr = metadataStr;
|
|
3013
|
+
if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
|
|
3014
|
+
jsonStr = metadataStr.slice(1, -1);
|
|
3015
|
+
}
|
|
3016
|
+
sourceMetadata = JSON.parse(jsonStr);
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
} catch (metadataError) {
|
|
3021
|
+
console.warn(`Could not read metadata from PDF ${fileName}:`, metadataError);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
console.log(`[PageAssembler] Extracting fields from "${fileName}"`);
|
|
3025
|
+
const extractedFields = await extractFieldsFromPdf(pdfBytes2);
|
|
3026
|
+
console.log(`[PageAssembler] Found ${extractedFields.length} fields in "${fileName}"`);
|
|
3027
|
+
const pageUsageForFile = filePageUsage.get(fileId) || /* @__PURE__ */ new Map();
|
|
3028
|
+
for (const field of extractedFields) {
|
|
3029
|
+
const originalPage = field.page;
|
|
3030
|
+
const srcIndex = originalPage - 1;
|
|
3031
|
+
const pageInstances = pageUsageForFile.get(srcIndex) || [];
|
|
3032
|
+
for (const pageInstance of pageInstances) {
|
|
3033
|
+
const newPageNumber = pageIdToNewPageNumber.get(pageInstance.id);
|
|
3034
|
+
if (newPageNumber !== void 0) {
|
|
3035
|
+
const trackedField = {
|
|
3036
|
+
name: field.name,
|
|
3037
|
+
type: field.type,
|
|
3038
|
+
position: {
|
|
3039
|
+
x: field.x,
|
|
3040
|
+
y: field.y,
|
|
3041
|
+
width: field.width,
|
|
3042
|
+
height: field.height,
|
|
3043
|
+
page: newPageNumber
|
|
3044
|
+
// Updated to new page number
|
|
3045
|
+
},
|
|
3046
|
+
pageId: pageInstance.id,
|
|
3047
|
+
originalFileId: fileId,
|
|
3048
|
+
label: field.label,
|
|
3049
|
+
required: field.required,
|
|
3050
|
+
options: field.options,
|
|
3051
|
+
placeholder: field.placeholder,
|
|
3052
|
+
fieldId: field.fieldId,
|
|
3053
|
+
assignedSignerEmail: field.signer
|
|
3054
|
+
};
|
|
3055
|
+
allExtractedFields.push(trackedField);
|
|
3056
|
+
console.log(`[PageAssembler] Mapped field "${field.name}" from page ${originalPage} to page ${newPageNumber}`);
|
|
3057
|
+
const metadataKey = `${field.name}_p${newPageNumber}`;
|
|
3058
|
+
if (sourceMetadata?.fields?.[field.name]) {
|
|
3059
|
+
mergedMetadata.fields[metadataKey] = sourceMetadata.fields[field.name];
|
|
3060
|
+
}
|
|
3061
|
+
if (sourceMetadata?.fieldIdIndex?.[field.name]) {
|
|
3062
|
+
mergedMetadata.fieldIdIndex[metadataKey] = sourceMetadata.fieldIdIndex[field.name];
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
const processedFileIds = /* @__PURE__ */ new Set();
|
|
3069
|
+
for (const file of state.files) {
|
|
3070
|
+
if (file.type !== "pdf" || !file.originalBytes || file.originalBytes.length === 0) continue;
|
|
3071
|
+
try {
|
|
3072
|
+
await extractAndMapFields(file.id, file.name, file.originalBytes, loadedPdfs.get(file.id));
|
|
3073
|
+
processedFileIds.add(file.id);
|
|
3074
|
+
} catch (extractError) {
|
|
3075
|
+
console.error(`Error extracting fields from ${file.name}:`, extractError);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
for (const fileId of referencedFileIds) {
|
|
3079
|
+
if (processedFileIds.has(fileId)) continue;
|
|
3080
|
+
const cachedBytes = originalBytesCache.current.get(fileId);
|
|
3081
|
+
if (cachedBytes && cachedBytes.length > 0) {
|
|
3082
|
+
try {
|
|
3083
|
+
console.log(`[PageAssembler] Extracting fields from cached file ${fileId}`);
|
|
3084
|
+
await extractAndMapFields(fileId, `Cached file ${fileId}`, cachedBytes, loadedPdfs.get(fileId));
|
|
3085
|
+
} catch (extractError) {
|
|
3086
|
+
console.error(`Error extracting fields from cached file ${fileId}:`, extractError);
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
console.log(`[PageAssembler] Total extracted fields: ${allExtractedFields.length}`);
|
|
3091
|
+
console.log(`[PageAssembler] Field names:`, allExtractedFields.map((f) => `${f.name} (page ${f.position.page})`));
|
|
3092
|
+
const combinedDoc = await PDFDocument5.create();
|
|
1636
3093
|
for (const file of state.files) {
|
|
1637
3094
|
if (file.pages.length === 0) continue;
|
|
1638
3095
|
for (const page of file.pages) {
|
|
@@ -1652,7 +3109,17 @@ var PageAssembler = React7.forwardRef(
|
|
|
1652
3109
|
if (page.srcIndex >= 0 && page.srcIndex < sourcePageCount) {
|
|
1653
3110
|
try {
|
|
1654
3111
|
const copiedPages = await combinedDoc.copyPages(sourcePdf, [page.srcIndex]);
|
|
1655
|
-
copiedPages.forEach((copiedPage) =>
|
|
3112
|
+
copiedPages.forEach((copiedPage) => {
|
|
3113
|
+
try {
|
|
3114
|
+
const annotsRef = copiedPage.node.get(PDFName4.of("Annots"));
|
|
3115
|
+
if (annotsRef) {
|
|
3116
|
+
copiedPage.node.delete(PDFName4.of("Annots"));
|
|
3117
|
+
}
|
|
3118
|
+
} catch (annotError) {
|
|
3119
|
+
console.warn("Could not remove annotations from copied page:", annotError);
|
|
3120
|
+
}
|
|
3121
|
+
combinedDoc.addPage(copiedPage);
|
|
3122
|
+
});
|
|
1656
3123
|
} catch (copyError) {
|
|
1657
3124
|
console.error(`Error copying page ${page.srcIndex}:`, copyError);
|
|
1658
3125
|
throw new Error(`Failed to copy page: ${copyError instanceof Error ? copyError.message : String(copyError)}`);
|
|
@@ -1661,7 +3128,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
1661
3128
|
} else if (sourceFile.type === "image" && sourceFile.originalFile) {
|
|
1662
3129
|
try {
|
|
1663
3130
|
const imagePdfBytes = await imageToPdf(sourceFile.originalFile);
|
|
1664
|
-
const imagePdf = await
|
|
3131
|
+
const imagePdf = await PDFDocument5.load(imagePdfBytes);
|
|
1665
3132
|
const copiedPages = await combinedDoc.copyPages(imagePdf, [0]);
|
|
1666
3133
|
copiedPages.forEach((copiedPage) => combinedDoc.addPage(copiedPage));
|
|
1667
3134
|
} catch (imageError) {
|
|
@@ -1671,8 +3138,48 @@ var PageAssembler = React7.forwardRef(
|
|
|
1671
3138
|
}
|
|
1672
3139
|
}
|
|
1673
3140
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
3141
|
+
if (Object.keys(mergedMetadata.fields).length > 0) {
|
|
3142
|
+
try {
|
|
3143
|
+
const newInfoRef = combinedDoc.context.trailerInfo.Info;
|
|
3144
|
+
if (newInfoRef) {
|
|
3145
|
+
const newInfoDict = combinedDoc.context.lookup(newInfoRef);
|
|
3146
|
+
if (newInfoDict && typeof newInfoDict.set === "function") {
|
|
3147
|
+
newInfoDict.set(
|
|
3148
|
+
PDFName4.of("SigniphiMetadata"),
|
|
3149
|
+
PDFString4.of(JSON.stringify(mergedMetadata))
|
|
3150
|
+
);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
} catch (metadataError) {
|
|
3154
|
+
console.warn("Could not write merged metadata:", metadataError);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
let pdfBytes = await combinedDoc.save();
|
|
3158
|
+
if (allExtractedFields.length > 0) {
|
|
3159
|
+
console.log(`[PageAssembler] Re-adding ${allExtractedFields.length} fields to combined document`);
|
|
3160
|
+
const fieldsForPdf = allExtractedFields.map((field) => ({
|
|
3161
|
+
name: field.name,
|
|
3162
|
+
type: field.type,
|
|
3163
|
+
position: field.position,
|
|
3164
|
+
label: field.label,
|
|
3165
|
+
required: field.required,
|
|
3166
|
+
options: field.options,
|
|
3167
|
+
placeholder: field.placeholder,
|
|
3168
|
+
assignedSignerEmail: field.assignedSignerEmail
|
|
3169
|
+
}));
|
|
3170
|
+
try {
|
|
3171
|
+
pdfBytes = await addFormFieldsToPdf(pdfBytes, fieldsForPdf, {
|
|
3172
|
+
removeExistingFields: true,
|
|
3173
|
+
// Remove any orphaned field references
|
|
3174
|
+
drawLabels: false
|
|
3175
|
+
});
|
|
3176
|
+
console.log("[PageAssembler] \u2705 Successfully re-added form fields to combined PDF");
|
|
3177
|
+
} catch (addFieldsError) {
|
|
3178
|
+
console.error("[PageAssembler] Error re-adding fields:", addFieldsError);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
const arrayBuffer = pdfBytes.buffer.slice(pdfBytes.byteOffset, pdfBytes.byteOffset + pdfBytes.byteLength);
|
|
3182
|
+
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
1676
3183
|
return blob;
|
|
1677
3184
|
} catch (error) {
|
|
1678
3185
|
console.error("Error assembling PDF:", error);
|
|
@@ -1686,7 +3193,7 @@ var PageAssembler = React7.forwardRef(
|
|
|
1686
3193
|
isBusy: () => isLoading,
|
|
1687
3194
|
getState: () => state
|
|
1688
3195
|
}), [state, summary, isLoading]);
|
|
1689
|
-
return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(reactDnd.DndProvider, { backend: reactDndHtml5Backend.HTML5Backend, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-
|
|
3196
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(reactDnd.DndProvider, { backend: reactDndHtml5Backend.HTML5Backend, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-3", className), children: [
|
|
1690
3197
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1691
3198
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1692
3199
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium", children: "Page Assembly" }),
|
|
@@ -1725,6 +3232,10 @@ var PageAssembler = React7.forwardRef(
|
|
|
1725
3232
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-primary" }),
|
|
1726
3233
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: processingStatus })
|
|
1727
3234
|
] }) }),
|
|
3235
|
+
uploadError && !isLoading && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-destructive/50 bg-destructive/5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3236
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4 text-destructive flex-shrink-0" }),
|
|
3237
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-destructive", children: uploadError })
|
|
3238
|
+
] }) }),
|
|
1728
3239
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1729
3240
|
BulkActionBar,
|
|
1730
3241
|
{
|
|
@@ -1777,9 +3288,9 @@ var PageAssembler = React7.forwardRef(
|
|
|
1777
3288
|
]
|
|
1778
3289
|
}
|
|
1779
3290
|
) : !disableFileUpload && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1780
|
-
|
|
3291
|
+
"div",
|
|
1781
3292
|
{
|
|
1782
|
-
className: "p-12
|
|
3293
|
+
className: "p-12 rounded-lg bg-card text-card-foreground cursor-pointer transition-all border-2 border-dashed border-border hover:border-primary/60 hover:bg-primary/10",
|
|
1783
3294
|
onClick: handleFileUpload,
|
|
1784
3295
|
onDrop: handleDrop,
|
|
1785
3296
|
onDragOver: handleDragOver,
|
|
@@ -1859,14 +3370,24 @@ function debounce(func, wait) {
|
|
|
1859
3370
|
timeout = setTimeout(() => func(...args), wait);
|
|
1860
3371
|
});
|
|
1861
3372
|
}
|
|
1862
|
-
var PageAssembler_default = PageAssembler;
|
|
1863
3373
|
|
|
1864
3374
|
exports.Button = Button;
|
|
3375
|
+
exports.FormFieldType = FormFieldType;
|
|
1865
3376
|
exports.PageAssembler = PageAssembler;
|
|
3377
|
+
exports.PdfPasswordRequiredError = PdfPasswordRequiredError2;
|
|
3378
|
+
exports.UnsupportedPdfEncryptionError = UnsupportedPdfEncryptionError2;
|
|
3379
|
+
exports.addFormFieldsToPdf = addFormFieldsToPdf;
|
|
1866
3380
|
exports.createPdfBlobUrl = createPdfBlobUrl;
|
|
1867
|
-
exports.
|
|
3381
|
+
exports.decryptPdf = decryptPdf2;
|
|
1868
3382
|
exports.downloadPdf = downloadPdf;
|
|
3383
|
+
exports.extractFieldsFromPdf = extractFieldsFromPdf;
|
|
3384
|
+
exports.getSigniphiMetadata = getSigniphiMetadata;
|
|
1869
3385
|
exports.imageToPdf = imageToPdf;
|
|
3386
|
+
exports.mapFieldPositionsAfterAssembly = mapFieldPositionsAfterAssembly;
|
|
3387
|
+
exports.normalizePdfEncryption = normalizePdfEncryption2;
|
|
3388
|
+
exports.normalizeUploadedPdfBytes = normalizeUploadedPdfBytes;
|
|
1870
3389
|
exports.pdfToImages = pdfToImages;
|
|
3390
|
+
exports.rasterizePdfToPdf = rasterizePdfToPdf;
|
|
3391
|
+
exports.setSigniphiMetadata = setSigniphiMetadata;
|
|
1871
3392
|
//# sourceMappingURL=index.js.map
|
|
1872
3393
|
//# sourceMappingURL=index.js.map
|