mirror of
https://github.com/documenso/documenso.git
synced 2026-07-11 13:35:20 +10:00
refactor(pdf): deslop AcroForm import cleanup
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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<ImportAcroFormFieldsResult> => {
|
||||
// 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<PreparedItem> => {
|
||||
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<Recipient, 'id' | 'email' | 'role' | 'signingOrder'>[]) => {
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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<string, unknown>) => ({
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user