@signiphi/page-assembly 0.2.0-beta.3 → 0.2.0-beta.5

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.mjs CHANGED
@@ -14,7 +14,7 @@ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
14
14
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
15
15
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
16
16
  import * as pdfjsLib from 'pdfjs-dist';
17
- import { PDFDocument } from 'pdf-lib';
17
+ import { PDFDocument, PDFTextField, PDFCheckBox, PDFDropdown, PDFRadioGroup, StandardFonts, rgb, PDFName, PDFString } from 'pdf-lib';
18
18
 
19
19
  // src/components/PageAssembler.tsx
20
20
  function cn(...inputs) {
@@ -693,6 +693,573 @@ function downloadPdf(pdfBytes, filename) {
693
693
  a.click();
694
694
  URL.revokeObjectURL(url);
695
695
  }
696
+ function getSigniphiMetadata(pdfDoc) {
697
+ try {
698
+ const infoRef = pdfDoc.context.trailerInfo.Info;
699
+ if (!infoRef) {
700
+ return null;
701
+ }
702
+ const infoObj = pdfDoc.context.lookup(infoRef);
703
+ if (!infoObj || typeof infoObj.get !== "function") {
704
+ return null;
705
+ }
706
+ const infoDict = infoObj;
707
+ const metadataObj = infoDict.get(PDFName.of("SigniphiMetadata"));
708
+ if (!metadataObj) {
709
+ return null;
710
+ }
711
+ const metadataStr = metadataObj.toString();
712
+ let jsonStr = metadataStr;
713
+ if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
714
+ jsonStr = metadataStr.slice(1, -1);
715
+ } else if (metadataStr.startsWith("<") && metadataStr.endsWith(">")) {
716
+ jsonStr = metadataStr.slice(1, -1);
717
+ }
718
+ const metadata = JSON.parse(jsonStr);
719
+ return metadata;
720
+ } catch (error) {
721
+ console.error("Failed to load Signiphi metadata:", error);
722
+ return null;
723
+ }
724
+ }
725
+ function getBaseFieldName(encodedName) {
726
+ let baseName = encodedName;
727
+ if (baseName.includes("__SIGNER__")) {
728
+ baseName = baseName.split("__SIGNER__")[0];
729
+ }
730
+ if (baseName.includes("__LABEL__")) {
731
+ baseName = baseName.split("__LABEL__")[0];
732
+ }
733
+ if (baseName.includes("__PLACEHOLDER__")) {
734
+ baseName = baseName.split("__PLACEHOLDER__")[0];
735
+ }
736
+ return baseName;
737
+ }
738
+ function getSignerFromFieldName(encodedName) {
739
+ if (encodedName.includes("__SIGNER__")) {
740
+ const parts = encodedName.split("__SIGNER__");
741
+ if (parts[1]) {
742
+ return parts[1].split("__")[0] || parts[1];
743
+ }
744
+ }
745
+ return void 0;
746
+ }
747
+ function determineFieldType(fieldName, widgetType) {
748
+ const baseName = getBaseFieldName(fieldName);
749
+ const nameLower = baseName.toLowerCase();
750
+ if (nameLower.includes("_signature") || nameLower.includes("signature")) {
751
+ return "signature";
752
+ }
753
+ if (nameLower.includes("_initials") || nameLower.includes("initials")) {
754
+ return "initials";
755
+ }
756
+ if (nameLower.includes("_date") || nameLower.includes("date")) {
757
+ return "date";
758
+ }
759
+ if (widgetType === "checkbox" || nameLower.includes("checkbox")) {
760
+ return "checkbox";
761
+ }
762
+ if (widgetType === "radio" || nameLower.includes("radio")) {
763
+ return "radio";
764
+ }
765
+ if (widgetType === "dropdown" || widgetType === "select" || nameLower.includes("dropdown")) {
766
+ return "dropdown";
767
+ }
768
+ return "text";
769
+ }
770
+ async function extractFieldsFromPdf(pdfBytes) {
771
+ const pdfDoc = await PDFDocument.load(pdfBytes);
772
+ const fields = [];
773
+ const metadata = getSigniphiMetadata(pdfDoc);
774
+ const form = pdfDoc.getForm();
775
+ const pdfFields = form.getFields();
776
+ for (const field of pdfFields) {
777
+ const fieldName = field.getName();
778
+ const widgets = field.acroField.getWidgets();
779
+ const baseName = getBaseFieldName(fieldName);
780
+ const signerFromName = getSignerFromFieldName(fieldName);
781
+ let required = false;
782
+ let options;
783
+ let widgetType;
784
+ if (field instanceof PDFTextField) {
785
+ required = field.isRequired();
786
+ console.log(`[field-extraction] PDFTextField "${fieldName}" isRequired=${required}`);
787
+ } else if (field instanceof PDFCheckBox) {
788
+ widgetType = "checkbox";
789
+ required = field.isRequired();
790
+ console.log(`[field-extraction] PDFCheckBox "${fieldName}" isRequired=${required}`);
791
+ } else if (field instanceof PDFDropdown) {
792
+ widgetType = "dropdown";
793
+ required = field.isRequired();
794
+ options = field.getOptions();
795
+ console.log(`[field-extraction] PDFDropdown "${fieldName}" isRequired=${required}`);
796
+ } else if (field instanceof PDFRadioGroup) {
797
+ widgetType = "radio";
798
+ required = field.isRequired();
799
+ options = field.getOptions();
800
+ console.log(`[field-extraction] PDFRadioGroup "${fieldName}" isRequired=${required}`);
801
+ } else {
802
+ console.log(`[field-extraction] Unknown field type "${fieldName}" - defaulting required=false`);
803
+ }
804
+ for (const widget of widgets) {
805
+ const rect = widget.getRectangle();
806
+ const pageRef = widget.P();
807
+ let pageNumber = 1;
808
+ if (pageRef) {
809
+ const pages = pdfDoc.getPages();
810
+ for (let i = 0; i < pages.length; i++) {
811
+ if (pages[i].ref === pageRef) {
812
+ pageNumber = i + 1;
813
+ break;
814
+ }
815
+ }
816
+ }
817
+ const pageQualifiedKey = `${baseName}_p${pageNumber}`;
818
+ let fieldMetadata = metadata?.fields?.[pageQualifiedKey];
819
+ if (!fieldMetadata) {
820
+ fieldMetadata = metadata?.fields?.[fieldName];
821
+ }
822
+ if (!fieldMetadata && baseName !== fieldName) {
823
+ fieldMetadata = metadata?.fields?.[baseName];
824
+ }
825
+ let fieldRequired = required;
826
+ let fieldOptions = options;
827
+ if (fieldMetadata?.required !== void 0) {
828
+ fieldRequired = fieldMetadata.required;
829
+ }
830
+ if (fieldMetadata?.options?.length) {
831
+ fieldOptions = fieldMetadata.options;
832
+ }
833
+ const page = pdfDoc.getPage(pageNumber - 1);
834
+ const pageHeight = page.getHeight();
835
+ const extractedField = {
836
+ name: baseName,
837
+ // Use clean base name without __SIGNER__ encoding (signer stored in signer property)
838
+ type: determineFieldType(fieldName, widgetType),
839
+ // Uses base name internally
840
+ page: pageNumber,
841
+ x: rect.x,
842
+ y: pageHeight - rect.y - rect.height,
843
+ // Convert from PDF coords (bottom-left) to top-left
844
+ width: rect.width,
845
+ height: rect.height,
846
+ required: fieldRequired,
847
+ options: fieldOptions,
848
+ signer: signerFromName
849
+ // Extract signer from encoded name
850
+ };
851
+ if (fieldMetadata) {
852
+ if (fieldMetadata.fieldId) {
853
+ extractedField.fieldId = fieldMetadata.fieldId;
854
+ }
855
+ if (fieldMetadata.label) {
856
+ extractedField.label = fieldMetadata.label;
857
+ }
858
+ if (!extractedField.signer && fieldMetadata.signer) {
859
+ extractedField.signer = fieldMetadata.signer;
860
+ }
861
+ if (fieldMetadata.placeholder) {
862
+ extractedField.placeholder = fieldMetadata.placeholder;
863
+ }
864
+ }
865
+ fields.push(extractedField);
866
+ }
867
+ }
868
+ const deduplicatedFields = fields.reduce((acc, field) => {
869
+ const newBaseName = getBaseFieldName(field.name);
870
+ const existingIndex = acc.findIndex((f) => {
871
+ const existingBaseName = getBaseFieldName(f.name);
872
+ return existingBaseName === newBaseName && f.page === field.page;
873
+ });
874
+ if (existingIndex === -1) {
875
+ acc.push(field);
876
+ } else {
877
+ const existing = acc[existingIndex];
878
+ acc[existingIndex] = {
879
+ ...existing,
880
+ // Prefer non-empty values from either field
881
+ label: existing.label || field.label,
882
+ placeholder: existing.placeholder || field.placeholder,
883
+ required: existing.required || field.required,
884
+ options: existing.options?.length ? existing.options : field.options,
885
+ fieldId: existing.fieldId || field.fieldId,
886
+ // Prefer signer from the field that has it
887
+ signer: existing.signer || field.signer
888
+ };
889
+ }
890
+ return acc;
891
+ }, []);
892
+ return deduplicatedFields;
893
+ }
894
+ function setSigniphiMetadata(pdfDoc, metadata) {
895
+ try {
896
+ const infoRef = pdfDoc.context.trailerInfo.Info;
897
+ const infoObj = pdfDoc.context.lookup(infoRef);
898
+ if (!infoObj || typeof infoObj.set !== "function") {
899
+ throw new Error("Info object is not a PDFDict or does not have set method");
900
+ }
901
+ const infoDict = infoObj;
902
+ const metadataString = JSON.stringify(metadata);
903
+ infoDict.set(PDFName.of("SigniphiMetadata"), PDFString.of(metadataString));
904
+ } catch (error) {
905
+ console.error("Failed to set Signiphi metadata:", error);
906
+ throw error;
907
+ }
908
+ }
909
+ var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
910
+ FormFieldType2["TEXT"] = "text";
911
+ FormFieldType2["SIGNATURE"] = "signature";
912
+ FormFieldType2["INITIALS"] = "initials";
913
+ FormFieldType2["DATE"] = "date";
914
+ FormFieldType2["CHECKBOX"] = "checkbox";
915
+ FormFieldType2["RADIO"] = "radio";
916
+ FormFieldType2["DROPDOWN"] = "dropdown";
917
+ FormFieldType2["TEXT_LABEL"] = "text_label";
918
+ return FormFieldType2;
919
+ })(FormFieldType || {});
920
+ function parseEncodedFieldName(encodedName) {
921
+ let baseName = encodedName;
922
+ let label;
923
+ let signer;
924
+ if (baseName.includes("__SIGNER__")) {
925
+ const parts = baseName.split("__SIGNER__");
926
+ baseName = parts[0];
927
+ signer = parts[1];
928
+ }
929
+ if (baseName.includes("__LABEL__")) {
930
+ const parts = baseName.split("__LABEL__");
931
+ baseName = parts[0];
932
+ label = parts[1];
933
+ }
934
+ if (baseName.includes("__PLACEHOLDER__")) {
935
+ const parts = baseName.split("__PLACEHOLDER__");
936
+ baseName = parts[0];
937
+ }
938
+ return { baseName, label, signer };
939
+ }
940
+ function encodeFieldNameWithSigner(fieldName, signerEmail) {
941
+ const { baseName, signer: existingSigner } = parseEncodedFieldName(fieldName);
942
+ const finalSigner = existingSigner || signerEmail;
943
+ if (!finalSigner) return baseName;
944
+ return `${baseName}__SIGNER__${finalSigner}`;
945
+ }
946
+ function encodeFieldNameWithLabelAndSigner(fieldName, label, signerEmail) {
947
+ const { baseName, label: existingLabel, signer: existingSigner } = parseEncodedFieldName(fieldName);
948
+ const finalLabel = existingLabel || label;
949
+ const finalSigner = existingSigner || signerEmail;
950
+ let name = baseName;
951
+ if (finalLabel && finalLabel.trim()) {
952
+ name = `${name}__LABEL__${finalLabel.trim()}`;
953
+ }
954
+ if (finalSigner) {
955
+ name = `${name}__SIGNER__${finalSigner}`;
956
+ }
957
+ return name;
958
+ }
959
+ async function addFormFieldsToPdf(pdfBytes, formFields, options = {}) {
960
+ const { removeExistingFields = false, drawLabels = false } = options;
961
+ const pdfDoc = await PDFDocument.load(pdfBytes);
962
+ const form = pdfDoc.getForm();
963
+ const pages = pdfDoc.getPages();
964
+ if (removeExistingFields) {
965
+ const existingFields = form.getFields();
966
+ for (const field of existingFields) {
967
+ try {
968
+ const acroField = field.acroField;
969
+ if (acroField && typeof acroField.getWidgets === "function") {
970
+ let widgets = acroField.getWidgets();
971
+ let safetyCounter = 0;
972
+ const maxIterations = widgets.length + 5;
973
+ while (widgets.length > 0 && safetyCounter < maxIterations) {
974
+ try {
975
+ acroField.removeWidget(widgets.length - 1);
976
+ widgets = acroField.getWidgets();
977
+ } catch {
978
+ break;
979
+ }
980
+ safetyCounter++;
981
+ }
982
+ }
983
+ form.removeField(field);
984
+ } catch (error) {
985
+ console.warn(`Failed to remove field ${field.getName()}:`, error);
986
+ try {
987
+ form.removeField(field);
988
+ } catch {
989
+ }
990
+ }
991
+ }
992
+ }
993
+ const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
994
+ const fieldNamePages = /* @__PURE__ */ new Map();
995
+ for (const field of formFields) {
996
+ const baseName = field.name;
997
+ if (!fieldNamePages.has(baseName)) {
998
+ fieldNamePages.set(baseName, /* @__PURE__ */ new Set());
999
+ }
1000
+ fieldNamePages.get(baseName).add(field.position.page);
1001
+ }
1002
+ const conflictingNames = /* @__PURE__ */ new Set();
1003
+ for (const [name, pages2] of fieldNamePages) {
1004
+ if (pages2.size > 1) {
1005
+ conflictingNames.add(name);
1006
+ }
1007
+ }
1008
+ for (const field of formFields) {
1009
+ const pageIndex = field.position.page - 1;
1010
+ if (pageIndex < 0 || pageIndex >= pages.length) {
1011
+ console.warn(`Field ${field.name} references page ${field.position.page} but PDF only has ${pages.length} pages`);
1012
+ continue;
1013
+ }
1014
+ const page = pages[pageIndex];
1015
+ const { height: pageHeight } = page.getSize();
1016
+ const pdfX = field.position.x;
1017
+ const pdfY = pageHeight - field.position.y - field.position.height;
1018
+ const uniqueFieldName = conflictingNames.has(field.name) ? `${field.name}_p${field.position.page}` : field.name;
1019
+ try {
1020
+ const fieldType = typeof field.type === "string" ? field.type : field.type;
1021
+ switch (fieldType) {
1022
+ case "text" /* TEXT */: {
1023
+ const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
1024
+ const textField = form.createTextField(encodedName);
1025
+ textField.addToPage(page, {
1026
+ x: pdfX,
1027
+ y: pdfY,
1028
+ width: field.position.width,
1029
+ height: field.position.height,
1030
+ borderColor: rgb(0.5, 0.5, 0.5),
1031
+ backgroundColor: rgb(1, 1, 1)
1032
+ });
1033
+ if (field.label && field.label.trim()) {
1034
+ textField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
1035
+ if (drawLabels) {
1036
+ const fontSize = Math.min(10, field.position.height * 0.4);
1037
+ const labelY = pdfY + field.position.height + 5;
1038
+ page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
1039
+ }
1040
+ }
1041
+ if (field.defaultValue && field.defaultValue.trim()) {
1042
+ textField.setText(field.defaultValue);
1043
+ }
1044
+ if (field.fontSize && field.fontSize >= 8 && field.fontSize <= 72) {
1045
+ try {
1046
+ textField.setFontSize(field.fontSize);
1047
+ } catch {
1048
+ }
1049
+ }
1050
+ if (field.multiline) {
1051
+ try {
1052
+ textField.enableMultiline();
1053
+ } catch {
1054
+ }
1055
+ }
1056
+ if (field.maxLength && field.maxLength > 0) {
1057
+ try {
1058
+ textField.setMaxLength(field.maxLength);
1059
+ } catch {
1060
+ }
1061
+ }
1062
+ if (field.required) {
1063
+ textField.enableRequired();
1064
+ }
1065
+ break;
1066
+ }
1067
+ case "checkbox" /* CHECKBOX */: {
1068
+ const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
1069
+ const checkBox = form.createCheckBox(encodedName);
1070
+ checkBox.addToPage(page, {
1071
+ x: pdfX,
1072
+ y: pdfY,
1073
+ width: field.position.width,
1074
+ height: field.position.height,
1075
+ borderColor: rgb(0.5, 0.5, 0.5),
1076
+ backgroundColor: rgb(1, 1, 1)
1077
+ });
1078
+ if (field.defaultValue === "true" || field.defaultValue === "checked") {
1079
+ checkBox.check();
1080
+ }
1081
+ if (field.required) {
1082
+ checkBox.enableRequired();
1083
+ }
1084
+ if (field.label && field.label.trim()) {
1085
+ try {
1086
+ checkBox.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
1087
+ } catch {
1088
+ }
1089
+ if (drawLabels) {
1090
+ const fontSize = Math.min(12, field.position.height * 0.6);
1091
+ const labelX = pdfX + field.position.width + 5;
1092
+ const labelY = pdfY + (field.position.height - fontSize) / 2;
1093
+ page.drawText(field.label, { x: labelX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
1094
+ }
1095
+ }
1096
+ break;
1097
+ }
1098
+ case "signature" /* SIGNATURE */: {
1099
+ const baseWithSuffix = field.name.endsWith("_signature") ? field.name : `${field.name}_signature`;
1100
+ const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
1101
+ const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
1102
+ const sigField = form.createTextField(encodedName);
1103
+ sigField.addToPage(page, {
1104
+ x: pdfX,
1105
+ y: pdfY,
1106
+ width: field.position.width,
1107
+ height: field.position.height,
1108
+ borderColor: rgb(0, 0, 1),
1109
+ backgroundColor: rgb(0.9, 0.9, 1)
1110
+ });
1111
+ if (field.label && field.label.trim()) {
1112
+ sigField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
1113
+ }
1114
+ sigField.enableReadOnly();
1115
+ if (field.required) {
1116
+ sigField.enableRequired();
1117
+ }
1118
+ break;
1119
+ }
1120
+ case "initials" /* INITIALS */: {
1121
+ const baseWithSuffix = field.name.endsWith("_initials") ? field.name : `${field.name}_initials`;
1122
+ const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
1123
+ const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
1124
+ const initField = form.createTextField(encodedName);
1125
+ initField.addToPage(page, {
1126
+ x: pdfX,
1127
+ y: pdfY,
1128
+ width: field.position.width,
1129
+ height: field.position.height,
1130
+ borderColor: rgb(0.5, 0, 0.5),
1131
+ backgroundColor: rgb(1, 0.95, 1)
1132
+ });
1133
+ if (field.label && field.label.trim()) {
1134
+ initField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
1135
+ }
1136
+ initField.enableReadOnly();
1137
+ if (field.required) {
1138
+ initField.enableRequired();
1139
+ }
1140
+ break;
1141
+ }
1142
+ case "date" /* DATE */: {
1143
+ const baseWithSuffix = field.name.endsWith("_date") ? field.name : `${field.name}_date`;
1144
+ const finalName = conflictingNames.has(field.name) ? `${baseWithSuffix}_p${field.position.page}` : baseWithSuffix;
1145
+ const encodedName = encodeFieldNameWithLabelAndSigner(finalName, field.label, field.assignedSignerEmail);
1146
+ const dateField = form.createTextField(encodedName);
1147
+ dateField.addToPage(page, {
1148
+ x: pdfX,
1149
+ y: pdfY,
1150
+ width: field.position.width,
1151
+ height: field.position.height,
1152
+ borderColor: rgb(0, 0.5, 0),
1153
+ backgroundColor: rgb(0.95, 1, 0.95)
1154
+ });
1155
+ if (field.label && field.label.trim()) {
1156
+ dateField.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
1157
+ }
1158
+ if (field.required) {
1159
+ dateField.enableRequired();
1160
+ }
1161
+ break;
1162
+ }
1163
+ case "dropdown" /* DROPDOWN */: {
1164
+ const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
1165
+ const dropdown = form.createDropdown(encodedName);
1166
+ dropdown.addToPage(page, {
1167
+ x: pdfX,
1168
+ y: pdfY,
1169
+ width: field.position.width,
1170
+ height: field.position.height,
1171
+ borderColor: rgb(0.5, 0.5, 0.5),
1172
+ backgroundColor: rgb(1, 1, 1)
1173
+ });
1174
+ if (field.options && field.options.length > 0) {
1175
+ dropdown.addOptions(field.options);
1176
+ if (field.defaultValue && field.options.includes(field.defaultValue)) {
1177
+ dropdown.select(field.defaultValue);
1178
+ }
1179
+ }
1180
+ if (field.label && field.label.trim()) {
1181
+ try {
1182
+ dropdown.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
1183
+ } catch {
1184
+ }
1185
+ if (drawLabels) {
1186
+ const fontSize = Math.min(10, field.position.height * 0.4);
1187
+ const labelY = pdfY + field.position.height + 5;
1188
+ page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
1189
+ }
1190
+ }
1191
+ if (field.required) {
1192
+ dropdown.enableRequired();
1193
+ }
1194
+ break;
1195
+ }
1196
+ case "radio" /* RADIO */: {
1197
+ const encodedName = encodeFieldNameWithSigner(uniqueFieldName, field.assignedSignerEmail);
1198
+ const radioGroup = form.createRadioGroup(encodedName);
1199
+ if (field.options && field.options.length > 0) {
1200
+ const optionHeight = Math.min(20, field.position.height / field.options.length);
1201
+ const spacing = field.position.height / field.options.length;
1202
+ field.options.forEach((option, index) => {
1203
+ const optionY = pdfY + field.position.height - (index + 1) * spacing + (spacing - optionHeight) / 2;
1204
+ radioGroup.addOptionToPage(option, page, {
1205
+ x: pdfX,
1206
+ y: optionY,
1207
+ width: optionHeight,
1208
+ height: optionHeight,
1209
+ borderColor: rgb(0.5, 0.5, 0.5),
1210
+ backgroundColor: rgb(1, 1, 1)
1211
+ });
1212
+ if (drawLabels) {
1213
+ const labelX = pdfX + optionHeight + 5;
1214
+ const labelY = optionY + optionHeight / 4;
1215
+ page.drawText(option, { x: labelX, y: labelY, size: 10, font, color: rgb(0, 0, 0) });
1216
+ }
1217
+ });
1218
+ if (field.defaultValue && field.options.includes(field.defaultValue)) {
1219
+ radioGroup.select(field.defaultValue);
1220
+ }
1221
+ }
1222
+ if (field.label && field.label.trim()) {
1223
+ try {
1224
+ radioGroup.acroField.dict.set(PDFName.of("TU"), PDFString.of(field.label));
1225
+ } catch {
1226
+ }
1227
+ if (drawLabels) {
1228
+ const fontSize = 10;
1229
+ const labelY = pdfY + field.position.height + 5;
1230
+ page.drawText(field.label, { x: pdfX, y: labelY, size: fontSize, font, color: rgb(0, 0, 0) });
1231
+ }
1232
+ }
1233
+ if (field.required) {
1234
+ radioGroup.enableRequired();
1235
+ }
1236
+ break;
1237
+ }
1238
+ default:
1239
+ console.warn(`Unknown field type: ${fieldType} for field ${field.name}`);
1240
+ }
1241
+ } catch (error) {
1242
+ console.error(`Error adding field ${field.name}:`, error);
1243
+ }
1244
+ }
1245
+ return await pdfDoc.save();
1246
+ }
1247
+ function mapFieldPositionsAfterAssembly(fields, pageMapping) {
1248
+ return fields.map((field) => {
1249
+ const mappingKey = String(field.position.page - 1);
1250
+ const newPageNumber = pageMapping.get(mappingKey);
1251
+ if (newPageNumber !== void 0) {
1252
+ return {
1253
+ ...field,
1254
+ position: { ...field.position, page: newPageNumber }
1255
+ };
1256
+ }
1257
+ return field;
1258
+ }).filter((field) => {
1259
+ const mappingKey = String(field.position.page - 1);
1260
+ return pageMapping.has(mappingKey) || pageMapping.size === 0;
1261
+ });
1262
+ }
696
1263
  var isMultiSelectModifier = (event) => {
697
1264
  return event.ctrlKey || event.metaKey;
698
1265
  };
@@ -1325,6 +1892,7 @@ var PageAssembler = forwardRef(
1325
1892
  const processedInitialPdfRef = useRef(null);
1326
1893
  const isDraggingPage = useRef(false);
1327
1894
  const onChangeRef = useRef(onChange);
1895
+ const originalBytesCache = useRef(/* @__PURE__ */ new Map());
1328
1896
  const summary = useMemo(() => ({
1329
1897
  totalFiles: state.files.length,
1330
1898
  totalPages: state.files.reduce((sum, file) => sum + file.pages.length, 0)
@@ -1394,6 +1962,7 @@ var PageAssembler = forwardRef(
1394
1962
  originalFileId: fileId
1395
1963
  }))
1396
1964
  };
1965
+ originalBytesCache.current.set(fileId, originalBytes);
1397
1966
  dispatch({ type: "ADD_FILE", payload: file });
1398
1967
  } catch (error) {
1399
1968
  console.error("Error processing initial PDF:", error);
@@ -1446,6 +2015,7 @@ var PageAssembler = forwardRef(
1446
2015
  const arrayBuffer = await file.arrayBuffer();
1447
2016
  const pdfBytes = new Uint8Array(arrayBuffer);
1448
2017
  fileInfo.originalBytes = new Uint8Array(pdfBytes);
2018
+ originalBytesCache.current.set(fileInfo.id, fileInfo.originalBytes);
1449
2019
  const defensiveCopy = new Uint8Array(pdfBytes);
1450
2020
  const pages = await pdfToImages(defensiveCopy, { hideFormFields: true });
1451
2021
  fileInfo.pages = pages.map((page, index) => ({
@@ -1462,6 +2032,7 @@ var PageAssembler = forwardRef(
1462
2032
  const arrayBuffer = await file.arrayBuffer();
1463
2033
  const imageBytes = new Uint8Array(arrayBuffer);
1464
2034
  fileInfo.originalBytes = new Uint8Array(imageBytes);
2035
+ originalBytesCache.current.set(fileInfo.id, fileInfo.originalBytes);
1465
2036
  const thumbUrl = URL.createObjectURL(file);
1466
2037
  const img = new Image();
1467
2038
  const dimensions = await new Promise((resolve) => {
@@ -1589,21 +2160,205 @@ var PageAssembler = forwardRef(
1589
2160
  setIsLoading(true);
1590
2161
  setProcessingStatus("Assembling PDF...");
1591
2162
  try {
1592
- const { PDFDocument: PDFDocument2 } = await import('pdf-lib');
1593
- const combinedDoc = await PDFDocument2.create();
2163
+ const { PDFDocument: PDFDocument4, PDFName: PDFName3, PDFString: PDFString3 } = await import('pdf-lib');
1594
2164
  const loadedPdfs = /* @__PURE__ */ new Map();
1595
2165
  const fileMap = /* @__PURE__ */ new Map();
2166
+ const pdfFiles = state.files.filter((f) => f.type === "pdf" && f.originalBytes?.length > 0);
2167
+ const imageFiles = state.files.filter((f) => f.type === "image");
2168
+ const isSinglePdfScenario = pdfFiles.length === 1 && imageFiles.length === 0;
1596
2169
  for (const file of state.files) {
1597
2170
  fileMap.set(file.id, file);
1598
2171
  if (file.type === "pdf" && file.originalBytes && file.originalBytes.length > 0) {
1599
2172
  try {
1600
- const sourcePdf = await PDFDocument2.load(file.originalBytes);
2173
+ const sourcePdf = await PDFDocument4.load(file.originalBytes);
1601
2174
  loadedPdfs.set(file.id, sourcePdf);
1602
2175
  } catch (error) {
1603
2176
  console.error(`Error loading PDF for file ${file.id}:`, error);
1604
2177
  }
1605
2178
  }
1606
2179
  }
2180
+ const referencedFileIds = /* @__PURE__ */ new Set();
2181
+ for (const file of state.files) {
2182
+ for (const page of file.pages) {
2183
+ const originalFileId = page.originalFileId || file.id;
2184
+ if (!loadedPdfs.has(originalFileId)) {
2185
+ referencedFileIds.add(originalFileId);
2186
+ }
2187
+ }
2188
+ }
2189
+ for (const fileId of referencedFileIds) {
2190
+ const cachedBytes = originalBytesCache.current.get(fileId);
2191
+ if (cachedBytes && cachedBytes.length > 0) {
2192
+ try {
2193
+ console.log(`[PageAssembler] Loading PDF from cache for deleted file ${fileId}`);
2194
+ const sourcePdf = await PDFDocument4.load(cachedBytes);
2195
+ loadedPdfs.set(fileId, sourcePdf);
2196
+ } catch (error) {
2197
+ console.error(`Error loading cached PDF for file ${fileId}:`, error);
2198
+ }
2199
+ }
2200
+ }
2201
+ console.log("[PageAssembler] Checking single PDF scenario:", {
2202
+ isSinglePdfScenario,
2203
+ pdfFilesCount: pdfFiles.length,
2204
+ imageFilesCount: imageFiles.length
2205
+ });
2206
+ if (isSinglePdfScenario && pdfFiles[0]) {
2207
+ const sourceFile = pdfFiles[0];
2208
+ const sourcePdf = loadedPdfs.get(sourceFile.id);
2209
+ console.log("[PageAssembler] Single PDF check:", {
2210
+ sourcePdfLoaded: !!sourcePdf,
2211
+ pagesCount: sourceFile.pages.length,
2212
+ originalBytesLength: sourceFile.originalBytes?.length
2213
+ });
2214
+ if (sourcePdf) {
2215
+ const desiredPageIndices = sourceFile.pages.map((p) => p.srcIndex);
2216
+ const originalPageCount = sourcePdf.getPageCount();
2217
+ const allPagesFromSameFile = sourceFile.pages.every(
2218
+ (p) => (p.originalFileId || sourceFile.id) === sourceFile.id
2219
+ );
2220
+ const isOriginalOrder = desiredPageIndices.length === originalPageCount && desiredPageIndices.every((idx, i) => idx === i);
2221
+ console.log("[PageAssembler] Original bytes check:", {
2222
+ allPagesFromSameFile,
2223
+ isOriginalOrder,
2224
+ desiredPageIndices,
2225
+ originalPageCount
2226
+ });
2227
+ if (allPagesFromSameFile && isOriginalOrder) {
2228
+ console.log("[PageAssembler] \u2705 Returning ORIGINAL bytes (form fields preserved)");
2229
+ const arrayBuffer2 = sourceFile.originalBytes.buffer.slice(
2230
+ sourceFile.originalBytes.byteOffset,
2231
+ sourceFile.originalBytes.byteOffset + sourceFile.originalBytes.byteLength
2232
+ );
2233
+ const blob2 = new Blob([arrayBuffer2], { type: "application/pdf" });
2234
+ return blob2;
2235
+ } else {
2236
+ console.log("[PageAssembler] \u26A0\uFE0F Pages modified, will use copyPages (form fields may be lost)");
2237
+ }
2238
+ }
2239
+ }
2240
+ console.log("[PageAssembler] Starting multi-PDF assembly with field preservation");
2241
+ const allExtractedFields = [];
2242
+ const mergedMetadata = {
2243
+ version: "1.0",
2244
+ fields: {},
2245
+ fieldIdIndex: {}
2246
+ };
2247
+ const filePageUsage = /* @__PURE__ */ new Map();
2248
+ for (const file of state.files) {
2249
+ for (const page of file.pages) {
2250
+ const originalFileId = page.originalFileId || file.id;
2251
+ if (!filePageUsage.has(originalFileId)) {
2252
+ filePageUsage.set(originalFileId, /* @__PURE__ */ new Map());
2253
+ }
2254
+ const pageUsage = filePageUsage.get(originalFileId);
2255
+ if (!pageUsage.has(page.srcIndex)) {
2256
+ pageUsage.set(page.srcIndex, []);
2257
+ }
2258
+ pageUsage.get(page.srcIndex).push(page);
2259
+ }
2260
+ }
2261
+ let combinedPageIndex = 0;
2262
+ const pageIdToNewPageNumber = /* @__PURE__ */ new Map();
2263
+ for (const file of state.files) {
2264
+ for (const page of file.pages) {
2265
+ combinedPageIndex++;
2266
+ pageIdToNewPageNumber.set(page.id, combinedPageIndex);
2267
+ }
2268
+ }
2269
+ console.log("[PageAssembler] Page mapping:", Object.fromEntries(pageIdToNewPageNumber));
2270
+ const extractAndMapFields = async (fileId, fileName, pdfBytes2, sourcePdf) => {
2271
+ let sourceMetadata = null;
2272
+ if (sourcePdf) {
2273
+ try {
2274
+ const sourceInfoRef = sourcePdf.context.trailerInfo.Info;
2275
+ if (sourceInfoRef) {
2276
+ const sourceInfoDict = sourcePdf.context.lookup(sourceInfoRef);
2277
+ if (sourceInfoDict && typeof sourceInfoDict.get === "function") {
2278
+ const metadataObj = sourceInfoDict.get(PDFName3.of("SigniphiMetadata"));
2279
+ if (metadataObj) {
2280
+ const metadataStr = metadataObj.toString();
2281
+ let jsonStr = metadataStr;
2282
+ if (metadataStr.startsWith("(") && metadataStr.endsWith(")")) {
2283
+ jsonStr = metadataStr.slice(1, -1);
2284
+ }
2285
+ sourceMetadata = JSON.parse(jsonStr);
2286
+ }
2287
+ }
2288
+ }
2289
+ } catch (metadataError) {
2290
+ console.warn(`Could not read metadata from PDF ${fileName}:`, metadataError);
2291
+ }
2292
+ }
2293
+ console.log(`[PageAssembler] Extracting fields from "${fileName}"`);
2294
+ const extractedFields = await extractFieldsFromPdf(pdfBytes2);
2295
+ console.log(`[PageAssembler] Found ${extractedFields.length} fields in "${fileName}"`);
2296
+ const pageUsageForFile = filePageUsage.get(fileId) || /* @__PURE__ */ new Map();
2297
+ for (const field of extractedFields) {
2298
+ const originalPage = field.page;
2299
+ const srcIndex = originalPage - 1;
2300
+ const pageInstances = pageUsageForFile.get(srcIndex) || [];
2301
+ for (const pageInstance of pageInstances) {
2302
+ const newPageNumber = pageIdToNewPageNumber.get(pageInstance.id);
2303
+ if (newPageNumber !== void 0) {
2304
+ const trackedField = {
2305
+ name: field.name,
2306
+ type: field.type,
2307
+ position: {
2308
+ x: field.x,
2309
+ y: field.y,
2310
+ width: field.width,
2311
+ height: field.height,
2312
+ page: newPageNumber
2313
+ // Updated to new page number
2314
+ },
2315
+ pageId: pageInstance.id,
2316
+ originalFileId: fileId,
2317
+ label: field.label,
2318
+ required: field.required,
2319
+ options: field.options,
2320
+ placeholder: field.placeholder,
2321
+ fieldId: field.fieldId,
2322
+ assignedSignerEmail: field.signer
2323
+ };
2324
+ allExtractedFields.push(trackedField);
2325
+ console.log(`[PageAssembler] Mapped field "${field.name}" from page ${originalPage} to page ${newPageNumber}`);
2326
+ const metadataKey = `${field.name}_p${newPageNumber}`;
2327
+ if (sourceMetadata?.fields?.[field.name]) {
2328
+ mergedMetadata.fields[metadataKey] = sourceMetadata.fields[field.name];
2329
+ }
2330
+ if (sourceMetadata?.fieldIdIndex?.[field.name]) {
2331
+ mergedMetadata.fieldIdIndex[metadataKey] = sourceMetadata.fieldIdIndex[field.name];
2332
+ }
2333
+ }
2334
+ }
2335
+ }
2336
+ };
2337
+ const processedFileIds = /* @__PURE__ */ new Set();
2338
+ for (const file of state.files) {
2339
+ if (file.type !== "pdf" || !file.originalBytes || file.originalBytes.length === 0) continue;
2340
+ try {
2341
+ await extractAndMapFields(file.id, file.name, file.originalBytes, loadedPdfs.get(file.id));
2342
+ processedFileIds.add(file.id);
2343
+ } catch (extractError) {
2344
+ console.error(`Error extracting fields from ${file.name}:`, extractError);
2345
+ }
2346
+ }
2347
+ for (const fileId of referencedFileIds) {
2348
+ if (processedFileIds.has(fileId)) continue;
2349
+ const cachedBytes = originalBytesCache.current.get(fileId);
2350
+ if (cachedBytes && cachedBytes.length > 0) {
2351
+ try {
2352
+ console.log(`[PageAssembler] Extracting fields from cached file ${fileId}`);
2353
+ await extractAndMapFields(fileId, `Cached file ${fileId}`, cachedBytes, loadedPdfs.get(fileId));
2354
+ } catch (extractError) {
2355
+ console.error(`Error extracting fields from cached file ${fileId}:`, extractError);
2356
+ }
2357
+ }
2358
+ }
2359
+ console.log(`[PageAssembler] Total extracted fields: ${allExtractedFields.length}`);
2360
+ console.log(`[PageAssembler] Field names:`, allExtractedFields.map((f) => `${f.name} (page ${f.position.page})`));
2361
+ const combinedDoc = await PDFDocument4.create();
1607
2362
  for (const file of state.files) {
1608
2363
  if (file.pages.length === 0) continue;
1609
2364
  for (const page of file.pages) {
@@ -1623,7 +2378,17 @@ var PageAssembler = forwardRef(
1623
2378
  if (page.srcIndex >= 0 && page.srcIndex < sourcePageCount) {
1624
2379
  try {
1625
2380
  const copiedPages = await combinedDoc.copyPages(sourcePdf, [page.srcIndex]);
1626
- copiedPages.forEach((copiedPage) => combinedDoc.addPage(copiedPage));
2381
+ copiedPages.forEach((copiedPage) => {
2382
+ try {
2383
+ const annotsRef = copiedPage.node.get(PDFName3.of("Annots"));
2384
+ if (annotsRef) {
2385
+ copiedPage.node.delete(PDFName3.of("Annots"));
2386
+ }
2387
+ } catch (annotError) {
2388
+ console.warn("Could not remove annotations from copied page:", annotError);
2389
+ }
2390
+ combinedDoc.addPage(copiedPage);
2391
+ });
1627
2392
  } catch (copyError) {
1628
2393
  console.error(`Error copying page ${page.srcIndex}:`, copyError);
1629
2394
  throw new Error(`Failed to copy page: ${copyError instanceof Error ? copyError.message : String(copyError)}`);
@@ -1632,7 +2397,7 @@ var PageAssembler = forwardRef(
1632
2397
  } else if (sourceFile.type === "image" && sourceFile.originalFile) {
1633
2398
  try {
1634
2399
  const imagePdfBytes = await imageToPdf(sourceFile.originalFile);
1635
- const imagePdf = await PDFDocument2.load(imagePdfBytes);
2400
+ const imagePdf = await PDFDocument4.load(imagePdfBytes);
1636
2401
  const copiedPages = await combinedDoc.copyPages(imagePdf, [0]);
1637
2402
  copiedPages.forEach((copiedPage) => combinedDoc.addPage(copiedPage));
1638
2403
  } catch (imageError) {
@@ -1642,7 +2407,46 @@ var PageAssembler = forwardRef(
1642
2407
  }
1643
2408
  }
1644
2409
  }
1645
- const pdfBytes = await combinedDoc.save();
2410
+ if (Object.keys(mergedMetadata.fields).length > 0) {
2411
+ try {
2412
+ const newInfoRef = combinedDoc.context.trailerInfo.Info;
2413
+ if (newInfoRef) {
2414
+ const newInfoDict = combinedDoc.context.lookup(newInfoRef);
2415
+ if (newInfoDict && typeof newInfoDict.set === "function") {
2416
+ newInfoDict.set(
2417
+ PDFName3.of("SigniphiMetadata"),
2418
+ PDFString3.of(JSON.stringify(mergedMetadata))
2419
+ );
2420
+ }
2421
+ }
2422
+ } catch (metadataError) {
2423
+ console.warn("Could not write merged metadata:", metadataError);
2424
+ }
2425
+ }
2426
+ let pdfBytes = await combinedDoc.save();
2427
+ if (allExtractedFields.length > 0) {
2428
+ console.log(`[PageAssembler] Re-adding ${allExtractedFields.length} fields to combined document`);
2429
+ const fieldsForPdf = allExtractedFields.map((field) => ({
2430
+ name: field.name,
2431
+ type: field.type,
2432
+ position: field.position,
2433
+ label: field.label,
2434
+ required: field.required,
2435
+ options: field.options,
2436
+ placeholder: field.placeholder,
2437
+ assignedSignerEmail: field.assignedSignerEmail
2438
+ }));
2439
+ try {
2440
+ pdfBytes = await addFormFieldsToPdf(pdfBytes, fieldsForPdf, {
2441
+ removeExistingFields: true,
2442
+ // Remove any orphaned field references
2443
+ drawLabels: false
2444
+ });
2445
+ console.log("[PageAssembler] \u2705 Successfully re-added form fields to combined PDF");
2446
+ } catch (addFieldsError) {
2447
+ console.error("[PageAssembler] Error re-adding fields:", addFieldsError);
2448
+ }
2449
+ }
1646
2450
  const arrayBuffer = pdfBytes.buffer.slice(pdfBytes.byteOffset, pdfBytes.byteOffset + pdfBytes.byteLength);
1647
2451
  const blob = new Blob([arrayBuffer], { type: "application/pdf" });
1648
2452
  return blob;
@@ -1831,8 +2635,7 @@ function debounce(func, wait) {
1831
2635
  timeout = setTimeout(() => func(...args), wait);
1832
2636
  });
1833
2637
  }
1834
- var PageAssembler_default = PageAssembler;
1835
2638
 
1836
- export { Button, PageAssembler, createPdfBlobUrl, PageAssembler_default as default, downloadPdf, imageToPdf, pdfToImages };
2639
+ export { Button, FormFieldType, PageAssembler, addFormFieldsToPdf, createPdfBlobUrl, downloadPdf, extractFieldsFromPdf, getSigniphiMetadata, imageToPdf, mapFieldPositionsAfterAssembly, pdfToImages, setSigniphiMetadata };
1837
2640
  //# sourceMappingURL=index.mjs.map
1838
2641
  //# sourceMappingURL=index.mjs.map