diff --git a/assets/acroform-import-rotated-180.pdf b/assets/acroform-import-rotated-180.pdf deleted file mode 100644 index 8e6427c01..000000000 --- a/assets/acroform-import-rotated-180.pdf +++ /dev/null @@ -1,717 +0,0 @@ -%PDF-1.7 -%βγΟΣ -1 0 obj -<< -/Type /Pages -/Kids [4 0 R 5 0 R] -/Count 2 ->> -endobj -2 0 obj -<< -/Type /Catalog -/Pages 1 0 R -/AcroForm 6 0 R ->> -endobj -3 0 obj -<< -/Title (Untitled) -/Author (Unknown) -/Creator (@libpdf/core) -/Producer (@libpdf/core) -/CreationDate (D:20260521033345Z) -/ModDate (D:20260521033345Z) ->> -endobj -4 0 obj -<< -/Type /Page -/MediaBox [0 0 612 792] -/Resources << ->> -/Rotate 180 -/Parent 1 0 R -/Annots [8 0 R 11 0 R 14 0 R] ->> -endobj -5 0 obj -<< -/Type /Page -/MediaBox [0 0 612 792] -/Resources << ->> -/Rotate 180 -/Parent 1 0 R -/Annots [18 0 R 21 0 R 24 0 R 27 0 R 31 0 R 34 0 R 37 0 R] ->> -endobj -6 0 obj -<< -/Fields [7 0 R 10 0 R 13 0 R 17 0 R 20 0 R 30 0 R 33 0 R 36 0 R] -/DR << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> -/DA (/Helv 0 Tf 0 g) -/NeedAppearances false -/SigFlags 3 ->> -endobj -7 0 obj -<< -/FT /Tx -/T (CustomerName) -/Kids [8 0 R] -/DA (0 g) ->> -endobj -8 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 620 280 644] -/P 4 0 R -/Parent 7 0 R -/F 4 -/AP << -/N 9 0 R ->> ->> -endobj -9 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 198 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -10 0 obj -<< -/FT /Tx -/T (signed_date) -/Kids [11 0 R] -/DA (0 g) ->> -endobj -11 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 560 280 584] -/P 4 0 R -/Parent 10 0 R -/F 4 -/AP << -/N 12 0 R ->> ->> -endobj -12 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 198 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -13 0 obj -<< -/FT /Btn -/T (accept_terms) -/Kids [14 0 R] -/V /Off ->> -endobj -14 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 500 98 518] -/P 4 0 R -/Parent 13 0 R -/F 4 -/AS /Off -/AP << -/N << -/Yes 15 0 R -/Off 16 0 R ->> ->> ->> -endobj -15 0 obj -<< -/Length 47/Type /XObject -/Subtype /Form -/BBox [0 0 18 18] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 12.6 Tf -0 g -2.7 4.59 Td -(4) Tj -ET -Q - -endstream -endobj -16 0 obj -<< -/Length 0/Type /XObject -/Subtype /Form -/BBox [0 0 18 18] -/Resources << ->> ->> -stream - -endstream -endobj -17 0 obj -<< -/FT /Ch -/T (country) -/Ff 131072 -/Kids [18 0 R] -/Opt [(USA) (Canada) (Germany)] -/DA (0 g) -/V (USA) -/DV (USA) ->> -endobj -18 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 700 280 724] -/P 5 0 R -/Parent 17 0 R -/F 4 -/AP << -/N 19 0 R ->> ->> -endobj -19 0 obj -<< -/Length 76/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 178 22 re -W -n -BT -/Helv 14 Tf -0 g -2 9.872 Td -(USA) Tj -ET -Q -EMC - -endstream -endobj -20 0 obj -<< -/FT /Btn -/T (payment_method) -/Ff 32768 -/Kids [21 0 R 24 0 R 27 0 R] -/V /PayPal -/DV /PayPal -/Opt [(Credit Card) (PayPal) (Bank Transfer)] ->> -endobj -21 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 640 96 656] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /Off -/AP << -/N << -/Credit#20Card 22 0 R -/Off 23 0 R ->> ->> ->> -endobj -22 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -23 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -24 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 615 96 631] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /PayPal -/AP << -/N << -/PayPal 25 0 R -/Off 26 0 R ->> ->> ->> -endobj -25 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -26 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -27 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 590 96 606] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /Off -/AP << -/N << -/Bank#20Transfer 28 0 R -/Off 29 0 R ->> ->> ->> -endobj -28 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -29 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -30 0 obj -<< -/FT /Tx -/T (initials) -/Kids [31 0 R] -/DA (0 g) ->> -endobj -31 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 540 140 564] -/P 5 0 R -/Parent 30 0 R -/F 4 -/AP << -/N 32 0 R ->> ->> -endobj -32 0 obj -<< -/Length 72/Type /XObject -/Subtype /Form -/BBox [0 0 60 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 58 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -33 0 obj -<< -/FT /Tx -/T (contact_email) -/Kids [34 0 R] -/DA (0 g) ->> -endobj -34 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [160 540 380 564] -/P 5 0 R -/Parent 33 0 R -/F 4 -/AP << -/N 35 0 R ->> ->> -endobj -35 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 220 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 218 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -36 0 obj -<< -/FT /Tx -/T (item_qty) -/Kids [37 0 R] -/DA (0 g) -/MaxLen 4 ->> -endobj -37 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [400 540 460 564] -/P 5 0 R -/Parent 36 0 R -/F 4 -/AP << -/N 38 0 R ->> ->> -endobj -38 0 obj -<< -/Length 72/Type /XObject -/Subtype /Form -/BBox [0 0 60 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 58 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -xref -0 39 -0000000000 65535 f -0000000015 00000 n -0000000078 00000 n -0000000143 00000 n -0000000312 00000 n -0000000442 00000 n -0000000601 00000 n -0000000890 00000 n -0000000961 00000 n -0000001082 00000 n -0000001343 00000 n -0000001415 00000 n -0000001539 00000 n -0000001801 00000 n -0000001873 00000 n -0000002028 00000 n -0000002266 00000 n -0000002380 00000 n -0000002510 00000 n -0000002634 00000 n -0000002899 00000 n -0000003058 00000 n -0000003223 00000 n -0000003460 00000 n -0000003735 00000 n -0000003896 00000 n -0000004133 00000 n -0000004408 00000 n -0000004575 00000 n -0000004812 00000 n -0000005087 00000 n -0000005156 00000 n -0000005280 00000 n -0000005540 00000 n -0000005614 00000 n -0000005739 00000 n -0000006001 00000 n -0000006080 00000 n -0000006205 00000 n -trailer -<< -/Size 39 -/Root 2 0 R -/Info 3 0 R -/ID [<6012AA895DABB8C2373E5EF0CB137849> <6012AA895DABB8C2373E5EF0CB137849>] ->> -startxref -6465 -%%EOF diff --git a/assets/acroform-import-rotated-270.pdf b/assets/acroform-import-rotated-270.pdf deleted file mode 100644 index e488c6acc..000000000 --- a/assets/acroform-import-rotated-270.pdf +++ /dev/null @@ -1,717 +0,0 @@ -%PDF-1.7 -%βγΟΣ -1 0 obj -<< -/Type /Pages -/Kids [4 0 R 5 0 R] -/Count 2 ->> -endobj -2 0 obj -<< -/Type /Catalog -/Pages 1 0 R -/AcroForm 6 0 R ->> -endobj -3 0 obj -<< -/Title (Untitled) -/Author (Unknown) -/Creator (@libpdf/core) -/Producer (@libpdf/core) -/CreationDate (D:20260521033345Z) -/ModDate (D:20260521033345Z) ->> -endobj -4 0 obj -<< -/Type /Page -/MediaBox [0 0 612 792] -/Resources << ->> -/Rotate 270 -/Parent 1 0 R -/Annots [8 0 R 11 0 R 14 0 R] ->> -endobj -5 0 obj -<< -/Type /Page -/MediaBox [0 0 612 792] -/Resources << ->> -/Rotate 270 -/Parent 1 0 R -/Annots [18 0 R 21 0 R 24 0 R 27 0 R 31 0 R 34 0 R 37 0 R] ->> -endobj -6 0 obj -<< -/Fields [7 0 R 10 0 R 13 0 R 17 0 R 20 0 R 30 0 R 33 0 R 36 0 R] -/DR << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> -/DA (/Helv 0 Tf 0 g) -/NeedAppearances false -/SigFlags 3 ->> -endobj -7 0 obj -<< -/FT /Tx -/T (CustomerName) -/Kids [8 0 R] -/DA (0 g) ->> -endobj -8 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 620 280 644] -/P 4 0 R -/Parent 7 0 R -/F 4 -/AP << -/N 9 0 R ->> ->> -endobj -9 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 198 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -10 0 obj -<< -/FT /Tx -/T (signed_date) -/Kids [11 0 R] -/DA (0 g) ->> -endobj -11 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 560 280 584] -/P 4 0 R -/Parent 10 0 R -/F 4 -/AP << -/N 12 0 R ->> ->> -endobj -12 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 198 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -13 0 obj -<< -/FT /Btn -/T (accept_terms) -/Kids [14 0 R] -/V /Off ->> -endobj -14 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 500 98 518] -/P 4 0 R -/Parent 13 0 R -/F 4 -/AS /Off -/AP << -/N << -/Yes 15 0 R -/Off 16 0 R ->> ->> ->> -endobj -15 0 obj -<< -/Length 47/Type /XObject -/Subtype /Form -/BBox [0 0 18 18] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 12.6 Tf -0 g -2.7 4.59 Td -(4) Tj -ET -Q - -endstream -endobj -16 0 obj -<< -/Length 0/Type /XObject -/Subtype /Form -/BBox [0 0 18 18] -/Resources << ->> ->> -stream - -endstream -endobj -17 0 obj -<< -/FT /Ch -/T (country) -/Ff 131072 -/Kids [18 0 R] -/Opt [(USA) (Canada) (Germany)] -/DA (0 g) -/V (USA) -/DV (USA) ->> -endobj -18 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 700 280 724] -/P 5 0 R -/Parent 17 0 R -/F 4 -/AP << -/N 19 0 R ->> ->> -endobj -19 0 obj -<< -/Length 76/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 178 22 re -W -n -BT -/Helv 14 Tf -0 g -2 9.872 Td -(USA) Tj -ET -Q -EMC - -endstream -endobj -20 0 obj -<< -/FT /Btn -/T (payment_method) -/Ff 32768 -/Kids [21 0 R 24 0 R 27 0 R] -/V /PayPal -/DV /PayPal -/Opt [(Credit Card) (PayPal) (Bank Transfer)] ->> -endobj -21 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 640 96 656] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /Off -/AP << -/N << -/Credit#20Card 22 0 R -/Off 23 0 R ->> ->> ->> -endobj -22 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -23 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -24 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 615 96 631] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /PayPal -/AP << -/N << -/PayPal 25 0 R -/Off 26 0 R ->> ->> ->> -endobj -25 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -26 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -27 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 590 96 606] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /Off -/AP << -/N << -/Bank#20Transfer 28 0 R -/Off 29 0 R ->> ->> ->> -endobj -28 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -29 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -30 0 obj -<< -/FT /Tx -/T (initials) -/Kids [31 0 R] -/DA (0 g) ->> -endobj -31 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 540 140 564] -/P 5 0 R -/Parent 30 0 R -/F 4 -/AP << -/N 32 0 R ->> ->> -endobj -32 0 obj -<< -/Length 72/Type /XObject -/Subtype /Form -/BBox [0 0 60 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 58 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -33 0 obj -<< -/FT /Tx -/T (contact_email) -/Kids [34 0 R] -/DA (0 g) ->> -endobj -34 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [160 540 380 564] -/P 5 0 R -/Parent 33 0 R -/F 4 -/AP << -/N 35 0 R ->> ->> -endobj -35 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 220 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 218 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -36 0 obj -<< -/FT /Tx -/T (item_qty) -/Kids [37 0 R] -/DA (0 g) -/MaxLen 4 ->> -endobj -37 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [400 540 460 564] -/P 5 0 R -/Parent 36 0 R -/F 4 -/AP << -/N 38 0 R ->> ->> -endobj -38 0 obj -<< -/Length 72/Type /XObject -/Subtype /Form -/BBox [0 0 60 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 58 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -xref -0 39 -0000000000 65535 f -0000000015 00000 n -0000000078 00000 n -0000000143 00000 n -0000000312 00000 n -0000000442 00000 n -0000000601 00000 n -0000000890 00000 n -0000000961 00000 n -0000001082 00000 n -0000001343 00000 n -0000001415 00000 n -0000001539 00000 n -0000001801 00000 n -0000001873 00000 n -0000002028 00000 n -0000002266 00000 n -0000002380 00000 n -0000002510 00000 n -0000002634 00000 n -0000002899 00000 n -0000003058 00000 n -0000003223 00000 n -0000003460 00000 n -0000003735 00000 n -0000003896 00000 n -0000004133 00000 n -0000004408 00000 n -0000004575 00000 n -0000004812 00000 n -0000005087 00000 n -0000005156 00000 n -0000005280 00000 n -0000005540 00000 n -0000005614 00000 n -0000005739 00000 n -0000006001 00000 n -0000006080 00000 n -0000006205 00000 n -trailer -<< -/Size 39 -/Root 2 0 R -/Info 3 0 R -/ID [<024D70B7251C83CF3A243EA11D6F5FC6> <024D70B7251C83CF3A243EA11D6F5FC6>] ->> -startxref -6465 -%%EOF diff --git a/assets/acroform-import-rotated-90.pdf b/assets/acroform-import-rotated-90.pdf deleted file mode 100644 index cdf0ea464..000000000 --- a/assets/acroform-import-rotated-90.pdf +++ /dev/null @@ -1,717 +0,0 @@ -%PDF-1.7 -%βγΟΣ -1 0 obj -<< -/Type /Pages -/Kids [4 0 R 5 0 R] -/Count 2 ->> -endobj -2 0 obj -<< -/Type /Catalog -/Pages 1 0 R -/AcroForm 6 0 R ->> -endobj -3 0 obj -<< -/Title (Untitled) -/Author (Unknown) -/Creator (@libpdf/core) -/Producer (@libpdf/core) -/CreationDate (D:20260521033345Z) -/ModDate (D:20260521033345Z) ->> -endobj -4 0 obj -<< -/Type /Page -/MediaBox [0 0 612 792] -/Resources << ->> -/Rotate 90 -/Parent 1 0 R -/Annots [8 0 R 11 0 R 14 0 R] ->> -endobj -5 0 obj -<< -/Type /Page -/MediaBox [0 0 612 792] -/Resources << ->> -/Rotate 90 -/Parent 1 0 R -/Annots [18 0 R 21 0 R 24 0 R 27 0 R 31 0 R 34 0 R 37 0 R] ->> -endobj -6 0 obj -<< -/Fields [7 0 R 10 0 R 13 0 R 17 0 R 20 0 R 30 0 R 33 0 R 36 0 R] -/DR << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> -/DA (/Helv 0 Tf 0 g) -/NeedAppearances false -/SigFlags 3 ->> -endobj -7 0 obj -<< -/FT /Tx -/T (CustomerName) -/Kids [8 0 R] -/DA (0 g) ->> -endobj -8 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 620 280 644] -/P 4 0 R -/Parent 7 0 R -/F 4 -/AP << -/N 9 0 R ->> ->> -endobj -9 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 198 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -10 0 obj -<< -/FT /Tx -/T (signed_date) -/Kids [11 0 R] -/DA (0 g) ->> -endobj -11 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 560 280 584] -/P 4 0 R -/Parent 10 0 R -/F 4 -/AP << -/N 12 0 R ->> ->> -endobj -12 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 198 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -13 0 obj -<< -/FT /Btn -/T (accept_terms) -/Kids [14 0 R] -/V /Off ->> -endobj -14 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 500 98 518] -/P 4 0 R -/Parent 13 0 R -/F 4 -/AS /Off -/AP << -/N << -/Yes 15 0 R -/Off 16 0 R ->> ->> ->> -endobj -15 0 obj -<< -/Length 47/Type /XObject -/Subtype /Form -/BBox [0 0 18 18] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 12.6 Tf -0 g -2.7 4.59 Td -(4) Tj -ET -Q - -endstream -endobj -16 0 obj -<< -/Length 0/Type /XObject -/Subtype /Form -/BBox [0 0 18 18] -/Resources << ->> ->> -stream - -endstream -endobj -17 0 obj -<< -/FT /Ch -/T (country) -/Ff 131072 -/Kids [18 0 R] -/Opt [(USA) (Canada) (Germany)] -/DA (0 g) -/V (USA) -/DV (USA) ->> -endobj -18 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 700 280 724] -/P 5 0 R -/Parent 17 0 R -/F 4 -/AP << -/N 19 0 R ->> ->> -endobj -19 0 obj -<< -/Length 76/Type /XObject -/Subtype /Form -/BBox [0 0 200 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 178 22 re -W -n -BT -/Helv 14 Tf -0 g -2 9.872 Td -(USA) Tj -ET -Q -EMC - -endstream -endobj -20 0 obj -<< -/FT /Btn -/T (payment_method) -/Ff 32768 -/Kids [21 0 R 24 0 R 27 0 R] -/V /PayPal -/DV /PayPal -/Opt [(Credit Card) (PayPal) (Bank Transfer)] ->> -endobj -21 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 640 96 656] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /Off -/AP << -/N << -/Credit#20Card 22 0 R -/Off 23 0 R ->> ->> ->> -endobj -22 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -23 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -24 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 615 96 631] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /PayPal -/AP << -/N << -/PayPal 25 0 R -/Off 26 0 R ->> ->> ->> -endobj -25 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -26 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -27 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 590 96 606] -/P 5 0 R -/Parent 20 0 R -/F 4 -/AS /Off -/AP << -/N << -/Bank#20Transfer 28 0 R -/Off 29 0 R ->> ->> ->> -endobj -28 0 obj -<< -/Length 46/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << -/Font << -/ZaDb << -/Type /Font -/Subtype /Type1 -/BaseFont /ZapfDingbats ->> ->> ->> ->> -stream -q -BT -/ZaDb 9.6 Tf -0 g -3.2 4.64 Td -(l) Tj -ET -Q - -endstream -endobj -29 0 obj -<< -/Length 159/Type /XObject -/Subtype /Form -/BBox [0 0 16 16] -/Resources << ->> ->> -stream -q -0 G -12.8 8 m -12.8 10.65104 10.65104 12.8 8 12.8 c -5.34896 12.8 3.2 10.65104 3.2 8 c -3.2 5.34896 5.34896 3.2 8 3.2 c -10.65104 3.2 12.8 5.34896 12.8 8 c -h -S -Q - -endstream -endobj -30 0 obj -<< -/FT /Tx -/T (initials) -/Kids [31 0 R] -/DA (0 g) ->> -endobj -31 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [80 540 140 564] -/P 5 0 R -/Parent 30 0 R -/F 4 -/AP << -/N 32 0 R ->> ->> -endobj -32 0 obj -<< -/Length 72/Type /XObject -/Subtype /Form -/BBox [0 0 60 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 58 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -33 0 obj -<< -/FT /Tx -/T (contact_email) -/Kids [34 0 R] -/DA (0 g) ->> -endobj -34 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [160 540 380 564] -/P 5 0 R -/Parent 33 0 R -/F 4 -/AP << -/N 35 0 R ->> ->> -endobj -35 0 obj -<< -/Length 73/Type /XObject -/Subtype /Form -/BBox [0 0 220 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 218 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -36 0 obj -<< -/FT /Tx -/T (item_qty) -/Kids [37 0 R] -/DA (0 g) -/MaxLen 4 ->> -endobj -37 0 obj -<< -/Type /Annot -/Subtype /Widget -/Rect [400 540 460 564] -/P 5 0 R -/Parent 36 0 R -/F 4 -/AP << -/N 38 0 R ->> ->> -endobj -38 0 obj -<< -/Length 72/Type /XObject -/Subtype /Form -/BBox [0 0 60 24] -/Resources << -/Font << -/Helv << -/Type /Font -/Subtype /Type1 -/BaseFont /Helvetica ->> ->> ->> ->> -stream -/Tx BMC -q -1 1 58 22 re -W -n -BT -/Helv 14 Tf -0 g -2 6.974 Td -() Tj -ET -Q -EMC - -endstream -endobj -xref -0 39 -0000000000 65535 f -0000000015 00000 n -0000000078 00000 n -0000000143 00000 n -0000000312 00000 n -0000000441 00000 n -0000000599 00000 n -0000000888 00000 n -0000000959 00000 n -0000001080 00000 n -0000001341 00000 n -0000001413 00000 n -0000001537 00000 n -0000001799 00000 n -0000001871 00000 n -0000002026 00000 n -0000002264 00000 n -0000002378 00000 n -0000002508 00000 n -0000002632 00000 n -0000002897 00000 n -0000003056 00000 n -0000003221 00000 n -0000003458 00000 n -0000003733 00000 n -0000003894 00000 n -0000004131 00000 n -0000004406 00000 n -0000004573 00000 n -0000004810 00000 n -0000005085 00000 n -0000005154 00000 n -0000005278 00000 n -0000005538 00000 n -0000005612 00000 n -0000005737 00000 n -0000005999 00000 n -0000006078 00000 n -0000006203 00000 n -trailer -<< -/Size 39 -/Root 2 0 R -/Info 3 0 R -/ID [<28A7D9B2A870BB05CFF56CD76F06FC33> <28A7D9B2A870BB05CFF56CD76F06FC33>] ->> -startxref -6463 -%%EOF diff --git a/packages/app-tests/e2e/scenarios/acroform-import.spec.ts b/packages/app-tests/e2e/scenarios/acroform-import.spec.ts index d6858831c..a26e54fdb 100644 --- a/packages/app-tests/e2e/scenarios/acroform-import.spec.ts +++ b/packages/app-tests/e2e/scenarios/acroform-import.spec.ts @@ -98,11 +98,8 @@ test.describe('AcroForm Import', () => { }, }); - // No fields are created at upload time. expect(envelope.fields).toHaveLength(0); - // The stored PDF still carries the original AcroForm widgets β€” they - // survive the upload pipeline and are available to the import button. const pdfBuffer = await getFileServerSide(envelope.envelopeItems[0].documentData); expect(await pdfHasFormFields(pdfBuffer)).toBe(true); @@ -226,21 +223,18 @@ test.describe('AcroForm Import', () => { expect(meta?.source).toBe('acroform'); } - // FIELD_CREATED audit log entries match the number of imported fields. const auditEntries = await prisma.documentAuditLog.findMany({ where: { envelopeId: after.id, type: 'FIELD_CREATED' }, }); expect(auditEntries.length).toBe(after.fields.length); - // The envelope item now points at a new (flat) DocumentData record. expect(after.envelopeItems[0].documentDataId).not.toBe(oldDocumentDataId); const flattenedPdf = await getFileServerSide(after.envelopeItems[0].documentData); expect(await pdfHasFormFields(flattenedPdf)).toBe(false); - // The old DocumentData record has been cleaned up. const oldRecord = await prisma.documentData.findUnique({ where: { id: oldDocumentDataId } }); expect(oldRecord).toBeNull(); diff --git a/packages/app-tests/e2e/scenarios/form-flattening.spec.ts b/packages/app-tests/e2e/scenarios/form-flattening.spec.ts index e0cfa91c9..e05641718 100644 --- a/packages/app-tests/e2e/scenarios/form-flattening.spec.ts +++ b/packages/app-tests/e2e/scenarios/form-flattening.spec.ts @@ -136,9 +136,6 @@ test.describe('Form Flattening', () => { expect(envelope.formValues).toEqual(TEST_FORM_VALUES); expect(envelope.type).toBe(EnvelopeType.DOCUMENT); - // DOCUMENT uploads no longer auto-flatten β€” the editor's "Import from - // PDF form" button is the new flatten trigger. Inserted values remain - // visible in the (still-interactive) widgets. const documentData = envelope.envelopeItems[0].documentData; const pdfBuffer = await getFileServerSide(documentData); @@ -160,8 +157,6 @@ test.describe('Form Flattening', () => { const payload: TCreateEnvelopePayload = { type: EnvelopeType.DOCUMENT, title: 'Document without Form Values', - // No formValues - form fields stay interactive until the user clicks - // "Import from PDF form" in the editor. }; const formData = new FormData(); @@ -188,7 +183,6 @@ test.describe('Form Flattening', () => { }, }); - // DOCUMENT uploads no longer auto-flatten. const documentData = envelope.envelopeItems[0].documentData; const pdfBuffer = await getFileServerSide(documentData); @@ -749,7 +743,6 @@ test.describe('Form Flattening', () => { }, }); - // DOCUMENT uploads no longer auto-flatten; widgets remain interactive. const documentData = envelope.envelopeItems[0].documentData; const pdfBuffer = await getFileServerSide(documentData); @@ -800,8 +793,6 @@ test.describe('Form Flattening', () => { [FORM_FIELDS.TEXT_FIELD]: 'Only this field', }); - // DOCUMENT uploads no longer auto-flatten; widgets remain interactive - // and the inserted value is visible inside the still-editable field. const documentData = envelope.envelopeItems[0].documentData; const pdfBuffer = await getFileServerSide(documentData); diff --git a/packages/lib/server-only/envelope-item/create-envelope-items.ts b/packages/lib/server-only/envelope-item/create-envelope-items.ts index b014f8f84..8e728f4eb 100644 --- a/packages/lib/server-only/envelope-item/create-envelope-items.ts +++ b/packages/lib/server-only/envelope-item/create-envelope-items.ts @@ -53,8 +53,6 @@ export const UNSAFE_createEnvelopeItems = async ({ buffer = await insertFormValuesInPdf({ pdf: buffer, formValues: envelope.formValues }); } - // Preserve interactive AcroForm widgets so they can be imported as - // Documenso fields on demand via the editor's "Import from PDF" button. const normalized = await normalizePdf(buffer, { flattenForm: false, }); diff --git a/packages/lib/server-only/envelope-item/import-acroform-fields.ts b/packages/lib/server-only/envelope-item/import-acroform-fields.ts index 4bb45d987..5e42abea7 100644 --- a/packages/lib/server-only/envelope-item/import-acroform-fields.ts +++ b/packages/lib/server-only/envelope-item/import-acroform-fields.ts @@ -46,11 +46,6 @@ type PreparedItem = { envelopeItemTitle: string; oldDocumentDataId: string; extraction: AcroFormExtractionResult; - /** - * The new flattened DocumentData record. Only set when the item had widgets - * AND no signed signature (signed-sig items keep their original PDF so the - * signature stays valid; their other widgets are still imported). - */ newDocumentData?: DocumentData; }; @@ -58,8 +53,6 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({ envelope, apiRequestMetadata, }: UnsafeImportAcroFormFieldsOptions): Promise => { - // 1. Per-item: load PDF, extract widgets, flatten + upload new PDF when needed. - // Done outside the transaction β€” IO and PDF work is slow. const prepared: PreparedItem[] = await Promise.all( envelope.envelopeItems.map(async (item): Promise => { const buffer = await getFileServerSide(item.documentData); @@ -117,19 +110,14 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({ extraction, }; - // No fields to import β†’ leave the PDF as-is. if (extraction.fields.length === 0) { return base; } - // Signed signature β†’ keep widgets in the PDF so the signature stays valid. - // Other widgets still flow into Documenso fields below. if (extraction.hasSignedSignature) { return base; } - // Flatten the form, upload as a fresh DocumentData. The old record is - // deleted after the transaction commits. const flattened = await normalizePdf(Buffer.from(buffer), { flattenForm: true, }); @@ -178,8 +166,6 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({ }; } - // 2. Transaction: resolve recipient, swap documentData, create fields, - // write audit logs. const { createdFields, importedItemsCount } = await prisma.$transaction(async (tx) => { const pickFirstSignableRecipient = (recipients: Pick[]) => { const signable = recipients.filter((r) => r.role === RecipientRole.SIGNER || r.role === RecipientRole.APPROVER); @@ -200,20 +186,17 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({ })[0]; }; - let availableRecipients = await tx.recipient.findMany({ - where: { envelopeId: envelope.id }, - select: { id: true, email: true, role: true, signingOrder: true }, - }); + let recipient = pickFirstSignableRecipient( + await tx.recipient.findMany({ + where: { envelopeId: envelope.id }, + select: { id: true, email: true, role: true, signingOrder: true }, + }), + ); - let firstSignableRecipient = pickFirstSignableRecipient(availableRecipients); - - if (!firstSignableRecipient) { - // Mirror the placeholder branch in createEnvelope: create Recipient 1 as - // a SIGNER so the imported fields have somewhere to land. The user can - // reassign in the editor afterwards. + if (!recipient) { const placeholderEmail = 'recipient.1@documenso.com'; - await tx.recipient.create({ + recipient = await tx.recipient.create({ data: { envelopeId: envelope.id, email: placeholderEmail, @@ -224,24 +207,10 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({ sendStatus: SendStatus.NOT_SENT, signingStatus: SigningStatus.NOT_SIGNED, }, - }); - - // eslint-disable-next-line require-atomic-updates - availableRecipients = await tx.recipient.findMany({ - where: { envelopeId: envelope.id }, select: { id: true, email: true, role: true, signingOrder: true }, }); - - firstSignableRecipient = pickFirstSignableRecipient(availableRecipients); } - if (!firstSignableRecipient) { - // Should be unreachable β€” we just created one. - throw new Error('Failed to resolve a signable recipient for AcroForm import'); - } - - const recipient = firstSignableRecipient; - const createdFields: Field[] = []; let importedItemsCount = 0; @@ -250,7 +219,6 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({ continue; } - // Swap to the flattened PDF when we produced one. if (item.newDocumentData) { await tx.envelopeItem.update({ where: { id: item.envelopeItemId }, @@ -306,8 +274,6 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({ return { createdFields, importedItemsCount }; }); - // 3. Delete orphaned old DocumentData records. Outside the transaction so a - // delete failure does not undo the import. await Promise.all( prepared .filter((p) => p.newDocumentData !== undefined) diff --git a/packages/lib/server-only/envelope-item/replace-envelope-item-pdf.ts b/packages/lib/server-only/envelope-item/replace-envelope-item-pdf.ts index 22b48a2c8..ffb226528 100644 --- a/packages/lib/server-only/envelope-item/replace-envelope-item-pdf.ts +++ b/packages/lib/server-only/envelope-item/replace-envelope-item-pdf.ts @@ -80,8 +80,6 @@ export const UNSAFE_replaceEnvelopeItemPdf = async ({ buffer = await insertFormValuesInPdf({ pdf: buffer, formValues: envelope.formValues }); } - // Preserve interactive AcroForm widgets so they can be imported as - // Documenso fields on demand via the editor's "Import from PDF" button. const normalized = await normalizePdf(buffer, { flattenForm: false, }); diff --git a/packages/lib/server-only/pdf/acroform-fields.test.ts b/packages/lib/server-only/pdf/acroform-fields.test.ts deleted file mode 100644 index d186f8be0..000000000 --- a/packages/lib/server-only/pdf/acroform-fields.test.ts +++ /dev/null @@ -1,621 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; - -import { AnnotationFlags, PDF, PdfDict, PdfName, PdfNumber, PdfString } from '@libpdf/core'; -import { FieldType } from '@prisma/client'; -import { beforeAll, describe, expect, it, vi } from 'vitest'; - -import { convertAcroFormFieldsToFieldInputs, extractAcroFormFieldsFromPDF } from './acroform-fields'; - -const ASSETS_DIR = path.resolve(__dirname, '../../../../assets'); - -const loadAsset = (name: string) => fs.readFileSync(path.join(ASSETS_DIR, name)); - -const buildPdfBuffer = async (rotation: 0 | 90 | 180 | 270 = 0) => { - const pdf = PDF.create(); - pdf.addPage({ width: 612, height: 792, rotate: rotation }); - pdf.addPage({ width: 612, height: 792, rotate: rotation }); - - const form = pdf.getOrCreateForm(); - const [page1, page2] = pdf.getPages(); - - const customerName = form.createTextField('CustomerName'); - page1.drawField(customerName, { x: 80, y: 620, width: 200, height: 24 }); - - const signedDate = form.createTextField('signed_date'); - page1.drawField(signedDate, { x: 80, y: 560, width: 200, height: 24 }); - - const acceptTerms = form.createCheckbox('accept_terms', { onValue: 'Yes' }); - page1.drawField(acceptTerms, { x: 80, y: 500, width: 18, height: 18 }); - - const country = form.createDropdown('country', { - options: ['USA', 'Canada', 'Germany'], - defaultValue: 'USA', - }); - page2.drawField(country, { x: 80, y: 700, width: 200, height: 24 }); - - const payment = form.createRadioGroup('payment_method', { - options: ['Credit Card', 'PayPal', 'Bank Transfer'], - defaultValue: 'PayPal', - }); - page2.drawField(payment, { x: 80, y: 640, width: 16, height: 16, option: 'Credit Card' }); - page2.drawField(payment, { x: 80, y: 615, width: 16, height: 16, option: 'PayPal' }); - page2.drawField(payment, { x: 80, y: 590, width: 16, height: 16, option: 'Bank Transfer' }); - - const initials = form.createTextField('initials'); - page2.drawField(initials, { x: 80, y: 540, width: 60, height: 24 }); - - const email = form.createTextField('contact_email'); - page2.drawField(email, { x: 160, y: 540, width: 220, height: 24 }); - - const qty = form.createTextField('item_qty', { maxLength: 4 }); - page2.drawField(qty, { x: 400, y: 540, width: 60, height: 24 }); - - // Format-action precedence: name matches /name/i (would resolve to NAME) but - // AcroForm carries an `AFNumber_Format` action β€” the action MUST win. - const customerNameNumberField = form.createTextField('customer_name_amount'); - page2.drawField(customerNameNumberField, { x: 80, y: 480, width: 120, height: 24 }); - customerNameNumberField.acroField().set( - 'AA', - PdfDict.of({ - F: PdfDict.of({ - S: PdfName.of('JavaScript'), - JS: PdfString.fromString('AFNumber_Format(2, 0, 0, 0, "$", true)'), - }), - }), - ); - - // Required CHECKBOX: /Ff bit 2 (REQUIRED) set via raw dict. - const requiredTerms = form.createCheckbox('required_terms', { onValue: 'Accepted' }); - page2.drawField(requiredTerms, { x: 220, y: 480, width: 18, height: 18 }); - requiredTerms.acroField().set('Ff', PdfNumber.of(2)); - - // /TU (alternateName) label fallback: partialName is `shipping_addr` but - // /TU carries a human label that MUST be preferred over partialName. - const shippingAddress = form.createTextField('shipping_addr'); - page2.drawField(shippingAddress, { x: 280, y: 480, width: 220, height: 24 }); - shippingAddress.acroField().set('TU', PdfString.fromString('Shipping address')); - - // Hidden widget: should land in `unsupported` with reason 'hidden' rather - // than producing a field. WidgetAnnotation has no public setFlag, so we - // write the annotation /F entry directly (bit 2 = Hidden). - const hiddenField = form.createTextField('hidden_field'); - page2.drawField(hiddenField, { x: 80, y: 440, width: 100, height: 20 }); - const hiddenWidgets = hiddenField.getWidgets() as Array<{ dict: PdfDict }>; - if (hiddenWidgets[0]) { - hiddenWidgets[0].dict.set('F', PdfNumber.of(AnnotationFlags.Hidden)); - } - - // Off-page widget: positioned beyond the right edge of a 612pt-wide page so - // the extractor reports `off-page`. Drawn AFTER all others so the file is - // intentionally malformed. - const offPageField = form.createTextField('off_page_field'); - page2.drawField(offPageField, { x: 700, y: 440, width: 60, height: 20 }); - - return Buffer.from(await pdf.save()); -}; - -describe('extractAcroFormFieldsFromPDF', () => { - let baseBuffer: Buffer; - - beforeAll(async () => { - baseBuffer = await buildPdfBuffer(); - }); - - describe('non-AcroForm input', () => { - it('returns no-form skip reason for PDFs without an AcroForm', async () => { - const pdf = PDF.create(); - pdf.addPage({ width: 612, height: 792 }); - const emptyBuffer = Buffer.from(await pdf.save()); - - const result = await extractAcroFormFieldsFromPDF(emptyBuffer); - - expect(result.fields).toEqual([]); - expect(result.unsupported).toEqual([]); - expect(result.hasSignedSignature).toBe(false); - expect(result.skipReason).toBe('no-form'); - }); - - it('returns error skip reason without throwing for malformed PDFs', async () => { - const garbage = Buffer.from('not a pdf'); - - const result = await extractAcroFormFieldsFromPDF(garbage); - - expect(result.fields).toEqual([]); - expect(result.skipReason).toBe('error'); - }); - }); - - describe('field type resolution', () => { - it('maps each supported AcroForm field type to its Documenso counterpart', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - - expect(result.skipReason).toBeUndefined(); - expect(result.hasSignedSignature).toBe(false); - // Two intentionally-bad widgets in the fixture: hidden and off-page. - expect(result.unsupported.map((u) => u.reason).sort()).toEqual(['hidden', 'off-page']); - - const byName = new Map(result.fields.map((f) => [f.fieldName, f.fieldAndMeta.type])); - - expect(byName.get('CustomerName')).toBe(FieldType.NAME); - expect(byName.get('signed_date')).toBe(FieldType.DATE); - expect(byName.get('accept_terms')).toBe(FieldType.CHECKBOX); - expect(byName.get('country')).toBe(FieldType.DROPDOWN); - expect(byName.get('payment_method')).toBe(FieldType.RADIO); - expect(byName.get('initials')).toBe(FieldType.INITIALS); - expect(byName.get('contact_email')).toBe(FieldType.EMAIL); - expect(byName.get('item_qty')).toBe(FieldType.NUMBER); - // Hidden and off-page widgets must not appear as fields. - expect(byName.has('hidden_field')).toBe(false); - expect(byName.has('off_page_field')).toBe(false); - }); - - it('emits one Documenso RADIO field per widget (one field per option)', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const radioFields = result.fields.filter((f) => f.fieldName === 'payment_method'); - - expect(radioFields).toHaveLength(3); - expect(radioFields.every((f) => f.fieldAndMeta.type === FieldType.RADIO)).toBe(true); - }); - - it('stamps source: "acroform" on every imported field meta', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - - expect(result.fields.length).toBeGreaterThan(0); - - for (const f of result.fields) { - expect(f.fieldAndMeta.fieldMeta?.source).toBe('acroform'); - } - }); - - it('does not use /T partialName as a label when no /TU is set', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const nameField = result.fields.find((f) => f.fieldName === 'CustomerName'); - - expect(nameField).toBeDefined(); - expect(nameField?.fieldAndMeta.fieldMeta?.label).toBeUndefined(); - }); - - it('prefers AcroForm /TU (alternateName) over partialName for the label', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const shippingField = result.fields.find((f) => f.fieldName === 'shipping_addr'); - - expect(shippingField).toBeDefined(); - expect(shippingField?.fieldAndMeta.fieldMeta?.label).toBe('Shipping address'); - }); - - it('lets AcroForm format actions override the name heuristic (P1.1)', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const numberField = result.fields.find((f) => f.fieldName === 'customer_name_amount'); - - // Without the format action, /name/i would steer this to NAME. The - // `AFNumber_Format` action MUST win. - expect(numberField?.fieldAndMeta.type).toBe(FieldType.NUMBER); - }); - - it('maps required CHECKBOX (/Ff bit 2) to validationRule "at-least" + length 1', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const required = result.fields.find((f) => f.fieldName === 'required_terms'); - - expect(required?.fieldAndMeta.type).toBe(FieldType.CHECKBOX); - if (required?.fieldAndMeta.type === FieldType.CHECKBOX) { - expect(required.fieldAndMeta.fieldMeta?.required).toBe(true); - expect(required.fieldAndMeta.fieldMeta?.validationRule).toBe('at-least'); - expect(required.fieldAndMeta.fieldMeta?.validationLength).toBe(1); - } - }); - }); - - describe('default values', () => { - it('copies AcroForm dropdown default into fieldMeta.defaultValue when formValues was not provided', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const country = result.fields.find((f) => f.fieldName === 'country'); - - expect(country?.fieldAndMeta.type).toBe(FieldType.DROPDOWN); - if (country?.fieldAndMeta.type === FieldType.DROPDOWN) { - expect(country.fieldAndMeta.fieldMeta?.defaultValue).toBe('USA'); - expect(country.fieldAndMeta.fieldMeta?.values?.map((v) => v.value)).toEqual(['USA', 'Canada', 'Germany']); - } - }); - - it('skips AcroForm defaults when formValuesProvided is true', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer, { formValuesProvided: true }); - const country = result.fields.find((f) => f.fieldName === 'country'); - - expect(country?.fieldAndMeta.type).toBe(FieldType.DROPDOWN); - if (country?.fieldAndMeta.type === FieldType.DROPDOWN) { - expect(country.fieldAndMeta.fieldMeta?.defaultValue).toBe(''); - } - }); - - it('marks the default radio option as checked when formValues was not provided', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const radios = result.fields.filter((f) => f.fieldName === 'payment_method'); - const checkedValues = radios.flatMap((r) => { - if (r.fieldAndMeta.type !== FieldType.RADIO) { - return []; - } - - return (r.fieldAndMeta.fieldMeta?.values ?? []).filter((v) => v.checked).map((v) => v.value); - }); - - expect(checkedValues).toContain('PayPal'); - }); - }); - - describe('geometry', () => { - it('produces percentages relative to rendered page dimensions for non-rotated pages', async () => { - const result = await extractAcroFormFieldsFromPDF(baseBuffer); - const customerName = result.fields.find((f) => f.fieldName === 'CustomerName'); - - expect(customerName).toBeDefined(); - - if (customerName) { - // user (80, 620) with width 200, height 24 on a 612x792 page. - // Rendered top-left: (80, 792 - 620 - 24) = (80, 148). - const xPct = (customerName.x / customerName.pageWidth) * 100; - const yPct = (customerName.y / customerName.pageHeight) * 100; - const wPct = (customerName.width / customerName.pageWidth) * 100; - const hPct = (customerName.height / customerName.pageHeight) * 100; - - expect(xPct).toBeCloseTo((80 / 612) * 100, 2); - expect(yPct).toBeCloseTo((148 / 792) * 100, 2); - expect(wPct).toBeCloseTo((200 / 612) * 100, 2); - expect(hPct).toBeCloseTo((24 / 792) * 100, 2); - } - }); - - it('handles 90-degree rotated pages with the inverse rotation transform', async () => { - const rotated = await buildPdfBuffer(90); - const result = await extractAcroFormFieldsFromPDF(rotated); - const customerName = result.fields.find((f) => f.fieldName === 'CustomerName'); - - expect(customerName).toBeDefined(); - - if (customerName) { - // Rendered page dims for /Rotate 90: width = mediaH (792), height = mediaW (612). - expect(customerName.pageWidth).toBeCloseTo(792, 0); - expect(customerName.pageHeight).toBeCloseTo(612, 0); - - // R=90 transform: renderedX = yB = 620, renderedY = xL = 80. - const xPct = (customerName.x / customerName.pageWidth) * 100; - const yPct = (customerName.y / customerName.pageHeight) * 100; - - expect(xPct).toBeCloseTo((620 / 792) * 100, 2); - expect(yPct).toBeCloseTo((80 / 612) * 100, 2); - } - }); - - it('handles 180-degree rotated pages', async () => { - const rotated = await buildPdfBuffer(180); - const result = await extractAcroFormFieldsFromPDF(rotated); - const customerName = result.fields.find((f) => f.fieldName === 'CustomerName'); - - expect(customerName).toBeDefined(); - - if (customerName) { - // R=180: renderedX = mediaW - xR = 612 - 280 = 332. renderedY = yB = 620. - const xPct = (customerName.x / customerName.pageWidth) * 100; - const yPct = (customerName.y / customerName.pageHeight) * 100; - - expect(xPct).toBeCloseTo((332 / 612) * 100, 2); - expect(yPct).toBeCloseTo((620 / 792) * 100, 2); - } - }); - - it('handles 270-degree rotated pages', async () => { - const rotated = await buildPdfBuffer(270); - const result = await extractAcroFormFieldsFromPDF(rotated); - const customerName = result.fields.find((f) => f.fieldName === 'CustomerName'); - - expect(customerName).toBeDefined(); - - if (customerName) { - // R=270 page dims swap: pageW = mediaH = 792, pageH = mediaW = 612. - // renderedX = mediaH - yT = 792 - 644 = 148. renderedY = mediaW - xR = 612 - 280 = 332. - const xPct = (customerName.x / customerName.pageWidth) * 100; - const yPct = (customerName.y / customerName.pageHeight) * 100; - - expect(xPct).toBeCloseTo((148 / 792) * 100, 2); - expect(yPct).toBeCloseTo((332 / 612) * 100, 2); - } - }); - }); -}); - -describe('convertAcroFormFieldsToFieldInputs', () => { - it('sorts by (page, y, x) before mapping recipients', async () => { - const buffer = await buildPdfBuffer(); - const { fields } = await extractAcroFormFieldsFromPDF(buffer); - - const recipient = { id: 42 }; - const inputs = convertAcroFormFieldsToFieldInputs(fields, () => recipient, 'env_item_1'); - - expect(inputs).toHaveLength(fields.length); - - // Sorted by page first. - const pages = inputs.map((i) => i.page); - const sortedPages = [...pages].sort((a, b) => a - b); - expect(pages).toEqual(sortedPages); - - // Within a single page, positionY (top-left coords) should be ascending - // when rows are >2% apart. - for (let i = 1; i < inputs.length; i++) { - const prev = inputs[i - 1]; - const curr = inputs[i]; - - if (prev.page !== curr.page) { - continue; - } - - const yDelta = curr.positionY - prev.positionY; - // Either next row (yDelta > -2%) or same row (then x must be ascending). - if (Math.abs(yDelta) <= 2) { - expect(curr.positionX).toBeGreaterThanOrEqual(prev.positionX); - } else { - expect(yDelta).toBeGreaterThan(0); - } - } - }); - - it('passes recipientId through the resolver for every imported field', async () => { - const buffer = await buildPdfBuffer(); - const { fields } = await extractAcroFormFieldsFromPDF(buffer); - - const recipient = { id: 7 }; - const inputs = convertAcroFormFieldsToFieldInputs(fields, () => recipient, 'env_item_xyz'); - - for (const input of inputs) { - expect(input.recipientId).toBe(recipient.id); - expect(input.envelopeItemId).toBe('env_item_xyz'); - } - }); -}); - -describe('extractAcroFormFieldsFromPDF β€” committed fixture', () => { - it('extracts the expected fields from assets/acroform-import-test.pdf', async () => { - const buffer = loadAsset('acroform-import-test.pdf'); - const result = await extractAcroFormFieldsFromPDF(buffer); - - expect(result.skipReason).toBeUndefined(); - expect(result.unsupported).toEqual([]); - expect(result.fields.length).toBeGreaterThanOrEqual(8); - }); - - it('extracts the expected fields from rotated fixtures (90/180/270)', async () => { - for (const angle of [90, 180, 270]) { - const buffer = loadAsset(`acroform-import-rotated-${angle}.pdf`); - const result = await extractAcroFormFieldsFromPDF(buffer); - - expect(result.skipReason).toBeUndefined(); - expect(result.unsupported).toEqual([]); - expect(result.fields.length).toBeGreaterThanOrEqual(8); - } - }); -}); - -/** - * Stub-based scenarios exercise paths that {@link buildPdfBuffer} cannot easily - * produce (signed signatures, encrypted docs, XFA hybrids, listboxes, widgets - * with mismatched page refs). Each stub mirrors the methods the extractor - * actually calls β€” keeping the contract narrow so a future refactor of the - * extractor surfaces here as a loud test failure rather than a silent miss. - */ -describe('extractAcroFormFieldsFromPDF β€” mocked scenarios', () => { - const emptyAcroFormDict = { has: () => false }; - const emptyCatalogDict = { - getDict: () => emptyAcroFormDict, - }; - const emptyContext = { - catalog: { getDict: () => emptyCatalogDict }, - resolve: () => null, - }; - - const buildFieldStub = (overrides: Record) => ({ - name: 'stub_field', - partialName: 'stub_field', - alternateName: null, - isReadOnly: () => false, - isRequired: () => false, - acroField: () => ({ - get: () => undefined, - getDict: () => undefined, - getNumber: () => undefined, - has: () => false, - set: () => undefined, - }), - getWidgets: () => [], - ...overrides, - }); - - it('returns skipReason "encrypted" when PDF.isEncrypted is true (P2.6)', async () => { - vi.spyOn(PDF, 'load').mockResolvedValueOnce({ isEncrypted: true } as unknown as PDF); - - const result = await extractAcroFormFieldsFromPDF(Buffer.from('stub')); - - expect(result.skipReason).toBe('encrypted'); - expect(result.fields).toEqual([]); - expect(result.hasSignedSignature).toBe(false); - }); - - it('returns skipReason "xfa-hybrid" when /AcroForm has /XFA and no AcroForm fields (P2.1)', async () => { - const xfaCatalogDict = { - getDict: (key: string) => (key === 'AcroForm' ? { has: (k: string) => k === 'XFA' } : undefined), - }; - - vi.spyOn(PDF, 'load').mockResolvedValueOnce({ - isEncrypted: false, - context: { catalog: { getDict: () => xfaCatalogDict }, resolve: () => null }, - getForm: () => ({ getFields: () => [] }), - getPages: () => [], - } as unknown as PDF); - - const result = await extractAcroFormFieldsFromPDF(Buffer.from('stub')); - - expect(result.skipReason).toBe('xfa-hybrid'); - expect(result.fields).toEqual([]); - }); - - it('imports AcroForm widgets even when /AcroForm also has /XFA', async () => { - const xfaCatalogDict = { - getDict: (key: string) => (key === 'AcroForm' ? { has: (k: string) => k === 'XFA' } : undefined), - }; - const pageRef = { objectNumber: 1, generation: 0 } as unknown as PdfDict; - const widget = { - rect: [100, 600, 300, 624] as [number, number, number, number], - pageRef, - isHidden: () => false, - getOnValue: () => null, - }; - const field = buildFieldStub({ - type: 'text', - name: 'topmostSubform[0].Page1[0].f1_1[0]', - partialName: 'f1_1', - getValue: () => '', - getDefaultValue: () => '', - getWidgets: () => [widget], - }); - const page = { - ref: pageRef, - width: 612, - height: 792, - rotation: 0, - getMediaBox: () => ({ x: 0, y: 0, width: 612, height: 792 }), - }; - - vi.spyOn(PDF, 'load').mockResolvedValueOnce({ - isEncrypted: false, - context: { catalog: { getDict: () => xfaCatalogDict }, resolve: () => null }, - getForm: () => ({ getFields: () => [field] }), - getPages: () => [page], - } as unknown as PDF); - - const result = await extractAcroFormFieldsFromPDF(Buffer.from('stub')); - - expect(result.skipReason).toBeUndefined(); - expect(result.fields).toHaveLength(1); - expect(result.fields[0]?.fieldAndMeta.type).toBe(FieldType.TEXT); - expect(result.fields[0]?.fieldAndMeta.fieldMeta?.label).toBe(''); - }); - - it('skips signed signature widgets and reports hasSignedSignature: true (P1.2)', async () => { - const signedField = buildFieldStub({ - type: 'signature', - name: 'ExistingSignature', - partialName: 'ExistingSignature', - isSigned: () => true, - }); - - vi.spyOn(PDF, 'load').mockResolvedValueOnce({ - isEncrypted: false, - context: emptyContext, - getForm: () => ({ getFields: () => [signedField] }), - getPages: () => [], - } as unknown as PDF); - - const result = await extractAcroFormFieldsFromPDF(Buffer.from('stub')); - - expect(result.hasSignedSignature).toBe(true); - expect(result.fields).toEqual([]); - expect(result.unsupported.find((u) => u.reason === 'signed-signature')).toMatchObject({ - fieldName: 'ExistingSignature', - acroFormType: 'signature', - }); - }); - - it('imports unsigned signature widgets normally (P1.2 negative control)', async () => { - const pageRef = { objectNumber: 1, generation: 0 } as unknown as PdfDict; - const widget = { - rect: [100, 600, 300, 640] as [number, number, number, number], - pageRef, - isHidden: () => false, - getOnValue: () => null, - }; - const unsignedField = buildFieldStub({ - type: 'signature', - name: 'NewSignature', - partialName: 'NewSignature', - isSigned: () => false, - getWidgets: () => [widget], - }); - const page = { - ref: pageRef, - width: 612, - height: 792, - rotation: 0, - getMediaBox: () => ({ x: 0, y: 0, width: 612, height: 792 }), - }; - - vi.spyOn(PDF, 'load').mockResolvedValueOnce({ - isEncrypted: false, - context: emptyContext, - getForm: () => ({ getFields: () => [unsignedField] }), - getPages: () => [page], - } as unknown as PDF); - - const result = await extractAcroFormFieldsFromPDF(Buffer.from('stub')); - - expect(result.hasSignedSignature).toBe(false); - expect(result.fields).toHaveLength(1); - expect(result.fields[0]?.fieldAndMeta.type).toBe(FieldType.SIGNATURE); - }); - - it('reports listbox fields as unsupported-type (P2.6)', async () => { - const listboxField = buildFieldStub({ - type: 'listbox', - name: 'colors', - partialName: 'colors', - }); - - vi.spyOn(PDF, 'load').mockResolvedValueOnce({ - isEncrypted: false, - context: emptyContext, - getForm: () => ({ getFields: () => [listboxField] }), - getPages: () => [], - } as unknown as PDF); - - const result = await extractAcroFormFieldsFromPDF(Buffer.from('stub')); - - expect(result.fields).toEqual([]); - expect(result.unsupported).toHaveLength(1); - expect(result.unsupported[0]?.reason).toBe('unsupported-type'); - expect(result.unsupported[0]?.acroFormType).toBe('listbox'); - }); - - it('reports widgets with unknown pageRef as no-page-match (P2.6)', async () => { - const realPageRef = { objectNumber: 1, generation: 0 } as unknown as PdfDict; - const orphanPageRef = { objectNumber: 99, generation: 0 } as unknown as PdfDict; - const orphanField = buildFieldStub({ - type: 'text', - name: 'orphan_text', - partialName: 'orphan_text', - getWidgets: () => [ - { - rect: [100, 600, 200, 620] as [number, number, number, number], - pageRef: orphanPageRef, - isHidden: () => false, - getOnValue: () => null, - }, - ], - }); - const page = { - ref: realPageRef, - width: 612, - height: 792, - rotation: 0, - getMediaBox: () => ({ x: 0, y: 0, width: 612, height: 792 }), - }; - - vi.spyOn(PDF, 'load').mockResolvedValueOnce({ - isEncrypted: false, - context: emptyContext, - getForm: () => ({ getFields: () => [orphanField] }), - getPages: () => [page], - } as unknown as PDF); - - const result = await extractAcroFormFieldsFromPDF(Buffer.from('stub')); - - expect(result.fields).toEqual([]); - expect(result.unsupported.map((u) => u.reason)).toContain('no-page-match'); - }); -}); diff --git a/packages/lib/server-only/pdf/acroform-fields.ts b/packages/lib/server-only/pdf/acroform-fields.ts index 8f88a0754..74999881d 100644 --- a/packages/lib/server-only/pdf/acroform-fields.ts +++ b/packages/lib/server-only/pdf/acroform-fields.ts @@ -253,13 +253,7 @@ const resolveTextSubtype = ( } => { const candidateNames = [field.partialName, field.alternateName]; - let formatHint: 'date' | 'number' | null = null; - - try { - formatHint = getTextFieldFormatHint(field.acroField(), resolver); - } catch { - formatHint = null; - } + const formatHint = getTextFieldFormatHint(field.acroField(), resolver); // AcroForm format actions take precedence over name tokens β€” Adobe set them // explicitly, so they're a stronger signal than a heuristic regex hit. @@ -271,17 +265,7 @@ const resolveTextSubtype = ( return { documensoType: FieldType.NUMBER }; } - let maxLen = Number.POSITIVE_INFINITY; - - try { - const lenEntry = field.acroField().getNumber('MaxLen', resolver); - - if (lenEntry) { - maxLen = lenEntry.value; - } - } catch { - // Ignore β€” MaxLen is optional. - } + const maxLen = field.acroField().getNumber('MaxLen', resolver)?.value ?? Number.POSITIVE_INFINITY; if (candidateNames.some(isDateFieldByName)) { return { documensoType: FieldType.DATE }; @@ -623,9 +607,6 @@ const resolveWidgetPages = ( return { matched, unmatched }; }; -/** - * Options for {@link extractAcroFormFieldsFromPDF}. - */ export type ExtractAcroFormOptions = { /** * When true, `insertFormValuesInPdf` already ran for this buffer. The @@ -719,14 +700,12 @@ export const extractAcroFormFieldsFromPDF = async ( const widgets = formField.getWidgets(); const { matched, unmatched } = resolveWidgetPages(widgets, pageByRef); - for (const widget of unmatched) { + for (let i = 0; i < unmatched.length; i += 1) { unsupported.push({ fieldName: field.name, acroFormType, reason: 'no-page-match', }); - // Reference widget so it isn't tree-shaken in dev tooling; harmless at runtime. - void widget; } let widgetCounter = 0; diff --git a/packages/trpc/server/envelope-router/create-envelope.ts b/packages/trpc/server/envelope-router/create-envelope.ts index b8bee8e47..2b6d0a92e 100644 --- a/packages/trpc/server/envelope-router/create-envelope.ts +++ b/packages/trpc/server/envelope-router/create-envelope.ts @@ -118,8 +118,6 @@ export const createEnvelopeRouteCaller = async ({ }); } - // Preserve interactive AcroForm widgets so they can be imported as - // Documenso fields on demand via the editor's "Import from PDF" button. const normalized = await normalizePdf(pdf, { flattenForm: false, }); diff --git a/packages/trpc/server/envelope-router/import-acroform-fields.ts b/packages/trpc/server/envelope-router/import-acroform-fields.ts index 6dc1da8ae..5f0f6d16e 100644 --- a/packages/trpc/server/envelope-router/import-acroform-fields.ts +++ b/packages/trpc/server/envelope-router/import-acroform-fields.ts @@ -10,14 +10,6 @@ import { ZImportAcroFormFieldsResponseSchema, } from './import-acroform-fields.types'; -/** - * Internal-only β€” driven by the "Import from PDF" button in the envelope editor. - * - * Extracts AcroForm widgets from each envelope item's stored PDF, creates - * Documenso `Field` rows for the supported widgets, flattens the PDF in place - * (so widgets do not visually duplicate the imported fields), and emits a - * `FIELD_CREATED` audit entry per field on DOCUMENT envelopes. - */ export const importAcroFormFieldsRoute = authenticatedProcedure .input(ZImportAcroFormFieldsRequestSchema) .output(ZImportAcroFormFieldsResponseSchema) @@ -69,7 +61,7 @@ export const importAcroFormFieldsRoute = authenticatedProcedure }); } - return await UNSAFE_importAcroFormFieldsFromEnvelope({ + return UNSAFE_importAcroFormFieldsFromEnvelope({ envelope, apiRequestMetadata: metadata, }); diff --git a/scripts/generate-acroform-test-pdf.mjs b/scripts/generate-acroform-test-pdf.mjs deleted file mode 100644 index 84d7080a4..000000000 --- a/scripts/generate-acroform-test-pdf.mjs +++ /dev/null @@ -1,99 +0,0 @@ -// scripts/generate-acroform-test-pdf.mjs -// -// Generates the AcroForm import fixture PDFs used by the test suite. -// Run via: node scripts/generate-acroform-test-pdf.mjs - -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { PDF } from '@libpdf/core'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ASSETS_DIR = path.resolve(__dirname, '..', 'assets'); - -const LETTER_WIDTH = 612; -const LETTER_HEIGHT = 792; - -/** - * Build the base fixture: one of each supported AcroForm field type with a - * mix of name heuristics so the type-resolution heuristic gets exercised. - */ -async function buildBaseFixture({ rotation = 0 } = {}) { - const pdf = PDF.create(); - - pdf.addPage({ width: LETTER_WIDTH, height: LETTER_HEIGHT, rotate: rotation }); - pdf.addPage({ width: LETTER_WIDTH, height: LETTER_HEIGHT, rotate: rotation }); - - const page1 = pdf.getPage(0); - const page2 = pdf.getPage(1); - - const form = pdf.getOrCreateForm(); - - // Page 1: text (NAME heuristic), text (DATE heuristic), checkbox. - // Note: SignatureField widgets can't be drawn without going through libpdf's - // PDFSignature path. Production PDFs (Adobe Acrobat etc.) supply the widget - // themselves; covered via unit-test mocks rather than this generator. - - const customerName = form.createTextField('CustomerName'); - page1.drawField(customerName, { x: 80, y: 620, width: 200, height: 24 }); - - const signedDate = form.createTextField('signed_date'); - page1.drawField(signedDate, { x: 80, y: 560, width: 200, height: 24 }); - - const acceptTerms = form.createCheckbox('accept_terms', { onValue: 'Yes' }); - page1.drawField(acceptTerms, { x: 80, y: 500, width: 18, height: 18 }); - - // Page 2: dropdown, radio (3 options), text (INITIALS), text (EMAIL), - // text (NUMBER via name + small MaxLen), and a required-readonly text. - const country = form.createDropdown('country', { - options: ['USA', 'Canada', 'Germany'], - defaultValue: 'USA', - }); - page2.drawField(country, { x: 80, y: 700, width: 200, height: 24 }); - - const payment = form.createRadioGroup('payment_method', { - options: ['Credit Card', 'PayPal', 'Bank Transfer'], - defaultValue: 'PayPal', - }); - page2.drawField(payment, { x: 80, y: 640, width: 16, height: 16, option: 'Credit Card' }); - page2.drawField(payment, { x: 80, y: 615, width: 16, height: 16, option: 'PayPal' }); - page2.drawField(payment, { x: 80, y: 590, width: 16, height: 16, option: 'Bank Transfer' }); - - const initials = form.createTextField('initials'); - page2.drawField(initials, { x: 80, y: 540, width: 60, height: 24 }); - - const email = form.createTextField('contact_email'); - page2.drawField(email, { x: 160, y: 540, width: 220, height: 24 }); - - const qty = form.createTextField('item_qty', { maxLength: 4 }); - page2.drawField(qty, { x: 400, y: 540, width: 60, height: 24 }); - - return Buffer.from(await pdf.save()); -} - -function ensureAssetsDir() { - if (!fs.existsSync(ASSETS_DIR)) { - fs.mkdirSync(ASSETS_DIR, { recursive: true }); - } -} - -async function main() { - ensureAssetsDir(); - - const base = await buildBaseFixture(); - fs.writeFileSync(path.join(ASSETS_DIR, 'acroform-import-test.pdf'), base); - - const rot90 = await buildBaseFixture({ rotation: 90 }); - fs.writeFileSync(path.join(ASSETS_DIR, 'acroform-import-rotated-90.pdf'), rot90); - - const rot180 = await buildBaseFixture({ rotation: 180 }); - fs.writeFileSync(path.join(ASSETS_DIR, 'acroform-import-rotated-180.pdf'), rot180); - - const rot270 = await buildBaseFixture({ rotation: 270 }); - fs.writeFileSync(path.join(ASSETS_DIR, 'acroform-import-rotated-270.pdf'), rot270); - - console.log('Wrote fixtures to', ASSETS_DIR); -} - -await main();