origami 1.2.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -0,0 +1,95 @@
1
+ require 'minitest/autorun'
2
+ require 'stringio'
3
+
4
+ class TestEncryption < Minitest::Test
5
+ def setup
6
+ @target = PDF.read(File.join(__dir__, "dataset/calc.pdf"),
7
+ ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
8
+ @output = StringIO.new
9
+ end
10
+
11
+ def test_encrypt_rc4_40b
12
+ @output.string = ""
13
+ @target.encrypt(cipher: 'rc4', key_size: 40).save(@output)
14
+ end
15
+
16
+ def test_encrypt_rc4_128b
17
+ @output.string = ""
18
+ @target.encrypt(cipher: 'rc4').save(@output)
19
+ end
20
+
21
+ def test_encrypt_aes_128b
22
+ @output.string = ""
23
+ @target.encrypt(cipher: 'aes').save(@output)
24
+ end
25
+
26
+ def test_decrypt_rc4_40b
27
+ @output.string = ""
28
+
29
+ pdf = PDF.new.encrypt(cipher: 'rc4', key_size: 40)
30
+ pdf.Catalog[:Test] = "test"
31
+ pdf.save(@output)
32
+
33
+ refute_equal pdf.Catalog[:Test], "test"
34
+
35
+ @output = @output.reopen(@output.string, "r")
36
+ pdf = PDF.read(@output, ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
37
+
38
+ assert_equal pdf.Catalog[:Test], "test"
39
+ end
40
+
41
+ def test_decrypt_rc4_128b
42
+ @output.string = ""
43
+ pdf = PDF.new.encrypt(cipher: 'rc4')
44
+ pdf.Catalog[:Test] = "test"
45
+ pdf.save(@output)
46
+
47
+ refute_equal pdf.Catalog[:Test], "test"
48
+
49
+ @output.reopen(@output.string, "r")
50
+ pdf = PDF.read(@output, ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
51
+
52
+ assert_equal pdf.Catalog[:Test], "test"
53
+ end
54
+
55
+ def test_decrypt_aes_128b
56
+ @output.string = ""
57
+ pdf = PDF.new.encrypt(cipher: 'aes')
58
+ pdf.Catalog[:Test] = "test"
59
+ pdf.save(@output)
60
+
61
+ refute_equal pdf.Catalog[:Test], "test"
62
+
63
+ @output = @output.reopen(@output.string, "r")
64
+ pdf = PDF.read(@output, ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
65
+
66
+ assert_equal pdf.Catalog[:Test], "test"
67
+ end
68
+
69
+ def test_decrypt_aes_256b
70
+ @output.string = ""
71
+ pdf = PDF.new.encrypt(cipher: 'aes', key_size: 256)
72
+ pdf.Catalog[:Test] = "test"
73
+ pdf.save(@output)
74
+
75
+ refute_equal pdf.Catalog[:Test], "test"
76
+
77
+ @output = @output.reopen(@output.string, "r")
78
+ pdf = PDF.read(@output, ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
79
+
80
+ assert_equal pdf.Catalog[:Test], "test"
81
+ end
82
+
83
+ def test_crypt_filter
84
+ @output.string = ""
85
+ pdf = PDF.new.encrypt(cipher: 'aes', key_size: 128)
86
+
87
+ pdf.Catalog[:S1] = Stream.new("test", :Filter => :Crypt)
88
+ pdf.Catalog[:S2] = Stream.new("test")
89
+
90
+ pdf.save(@output)
91
+
92
+ assert_equal pdf.Catalog.S1.encoded_data, "test"
93
+ refute_equal pdf.Catalog.S2.encoded_data, "test"
94
+ end
95
+ end
@@ -0,0 +1,96 @@
1
+ require 'minitest/autorun'
2
+
3
+ class TestPDFParser < Minitest::Test
4
+ def setup
5
+ @data =
6
+ %w{
7
+ dataset/empty.pdf
8
+ dataset/calc.pdf
9
+ dataset/crypto.pdf
10
+ }
11
+
12
+ @dict = StringScanner.new "<</Ref 2 0 R/N null/Pi 3.14 /D <<>>>>"
13
+
14
+ @literalstring = StringScanner.new "(\\122\\125by\\n)"
15
+ @hexastring = StringScanner.new "<52 55 62 79 0A>"
16
+ @true = StringScanner.new "true"
17
+ @false = StringScanner.new "false"
18
+ @real = StringScanner.new "-3.141592653"
19
+ @int = StringScanner.new "00000000002000000000000"
20
+ @name = StringScanner.new "/#52#55#62#79#0A"
21
+ @ref = StringScanner.new "199 1 R"
22
+ end
23
+
24
+ def test_parse_pdf
25
+ @data.each do |file|
26
+ pdf = PDF.read(File.join(__dir__, file), ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
27
+
28
+ assert_instance_of PDF, pdf
29
+
30
+ pdf.each_object do |object|
31
+ assert_kind_of Origami::Object, object
32
+ end
33
+ end
34
+ end
35
+
36
+ def test_parse_dictionary
37
+ dict = Dictionary.parse(@dict)
38
+
39
+ assert_instance_of Dictionary, dict
40
+ assert_instance_of Dictionary, dict[:D]
41
+ assert_instance_of Null, dict[:N]
42
+ assert_instance_of Reference, dict[:Ref]
43
+ assert_raises(InvalidReferenceError) { dict[:Ref].solve }
44
+ assert dict[:Pi] == 3.14
45
+ end
46
+
47
+ def test_parse_string
48
+ str = LiteralString.parse(@literalstring)
49
+ assert_instance_of LiteralString, str
50
+ assert_equal str.value, "RUby\n"
51
+
52
+ str = HexaString.parse(@hexastring)
53
+ assert_instance_of HexaString, str
54
+ assert_equal str.value, "RUby\n"
55
+ end
56
+
57
+ def test_parse_bool
58
+ b_true = Boolean.parse(@true)
59
+ b_false = Boolean.parse(@false)
60
+
61
+ assert_instance_of Boolean, b_true
62
+ assert_instance_of Boolean, b_false
63
+
64
+ assert b_false.false?
65
+ assert (not b_true.false?)
66
+ end
67
+
68
+ def test_parse_real
69
+ real = Real.parse(@real)
70
+ assert_instance_of Real, real
71
+
72
+ assert_equal real, -3.141592653
73
+ end
74
+
75
+ def test_parse_int
76
+ int = Origami::Integer.parse(@int)
77
+ assert_instance_of Origami::Integer, int
78
+
79
+ assert_equal int, 2000000000000
80
+ end
81
+
82
+ def test_parse_name
83
+ name = Name.parse(@name)
84
+ assert_instance_of Name, name
85
+
86
+ assert_equal name.value, :"RUby\n"
87
+ end
88
+
89
+ def test_parse_reference
90
+ ref = Reference.parse(@ref)
91
+ assert_instance_of Reference, ref
92
+
93
+ assert_equal [199, 1], ref.to_a
94
+ assert_raises(InvalidReferenceError) { ref.solve }
95
+ end
96
+ end
@@ -0,0 +1,58 @@
1
+ require 'minitest/autorun'
2
+ require 'stringio'
3
+ require 'openssl'
4
+
5
+ class TestSign < Minitest::Test
6
+
7
+ def setup
8
+ @target = PDF.read(File.join(__dir__, "dataset/calc.pdf"),
9
+ ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
10
+ @output = StringIO.new
11
+
12
+ @key = OpenSSL::PKey::RSA.new 2048
13
+
14
+ name = OpenSSL::X509::Name.parse 'CN=origami/DC=example'
15
+
16
+ @cert = OpenSSL::X509::Certificate.new
17
+ @cert.version = 2
18
+ @cert.serial = 0
19
+ @cert.not_before = Time.now
20
+ @cert.not_after = Time.now + 3600
21
+
22
+ @cert.public_key = @key.public_key
23
+ @cert.subject = name
24
+
25
+ extension_factory = OpenSSL::X509::ExtensionFactory.new nil, @cert
26
+
27
+ @cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
28
+ @cert.add_extension extension_factory.create_extension('keyUsage', 'digitalSignature')
29
+ @cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
30
+
31
+ @cert.issuer = name
32
+ @cert.sign @key, OpenSSL::Digest::SHA256.new
33
+ end
34
+
35
+ def test_sign
36
+ sig_annot = Annotation::Widget::Signature.new.set_indirect(true)
37
+ sig_annot.Rect = Rectangle[llx: 89.0, lly: 386.0, urx: 190.0, ury: 353.0]
38
+
39
+ @target.append_page do |page|
40
+ page.add_annotation(sig_annot)
41
+ end
42
+
43
+ @target.sign(@cert, @key,
44
+ annotation: sig_annot,
45
+ issuer: "Guillaume Delugré",
46
+ location: "France",
47
+ contact: "origami@localhost",
48
+ reason: "Example"
49
+ )
50
+
51
+ assert @target.frozen?
52
+ assert @target.signed?
53
+
54
+ @target.save(@output)
55
+
56
+ assert PDF.read(@output.reopen(@output.string,'r'), verbosity: Parser::VERBOSE_QUIET).verify
57
+ end
58
+ end
@@ -0,0 +1,182 @@
1
+ require 'minitest/autorun'
2
+ require 'stringio'
3
+
4
+ class TestStreams < Minitest::Test
5
+ def setup
6
+ @target = PDF.new
7
+ @output = StringIO.new
8
+ @data = "0123456789" * 1024
9
+ end
10
+
11
+ def test_predictors
12
+ stm = Stream.new(@data, :Filter => :FlateDecode)
13
+ stm.set_predictor(Filter::Predictor::TIFF)
14
+ raw = stm.encoded_data
15
+ stm.data = nil
16
+ stm.encoded_data = raw
17
+
18
+ assert_equal @data, stm.data
19
+
20
+ stm = Stream.new(@data, :Filter => :FlateDecode)
21
+ stm.set_predictor(Filter::Predictor::PNG_SUB)
22
+ raw = stm.encoded_data
23
+ stm.data = nil
24
+ stm.encoded_data = raw
25
+
26
+ assert_equal @data, stm.data
27
+
28
+ stm = Stream.new(@data, :Filter => :FlateDecode)
29
+ stm.set_predictor(Filter::Predictor::PNG_UP)
30
+ raw = stm.encoded_data
31
+ stm.data = nil
32
+ stm.encoded_data = raw
33
+
34
+ assert_equal stm.data, @data
35
+
36
+ stm = Stream.new(@data, :Filter => :FlateDecode)
37
+ stm.set_predictor(Filter::Predictor::PNG_AVERAGE)
38
+ raw = stm.encoded_data
39
+ stm.data = nil
40
+ stm.encoded_data = raw
41
+
42
+ assert_equal stm.data, @data
43
+
44
+ stm = Stream.new(@data, :Filter => :FlateDecode)
45
+ stm.set_predictor(Filter::Predictor::PNG_PAETH)
46
+ raw = stm.encoded_data
47
+ stm.data = nil
48
+ stm.encoded_data = raw
49
+
50
+ assert_equal stm.data, @data
51
+ end
52
+
53
+ def test_filter_flate
54
+ stm = Stream.new(@data, :Filter => :FlateDecode)
55
+ raw = stm.encoded_data
56
+ stm.data = nil
57
+ stm.encoded_data = raw
58
+
59
+ assert_equal stm.data, @data
60
+ end
61
+
62
+ def test_filter_asciihex
63
+ stm = Stream.new(@data, :Filter => :ASCIIHexDecode)
64
+ raw = stm.encoded_data
65
+ stm.data = nil
66
+ stm.encoded_data = raw
67
+
68
+ assert_equal stm.data, @data
69
+
70
+ assert_raises(Filter::InvalidASCIIHexStringError) do
71
+ Filter::ASCIIHex.decode("123456789ABCDEFGHIJKL")
72
+ end
73
+
74
+ assert_equal Filter::ASCIIHex.decode(""), ""
75
+ end
76
+
77
+ def test_filter_ascii85
78
+ stm = Stream.new(@data, :Filter => :ASCII85Decode)
79
+ raw = stm.encoded_data
80
+ stm.data = nil
81
+ stm.encoded_data = raw
82
+
83
+ assert_equal stm.data, @data
84
+
85
+ assert_raises(Filter::InvalidASCII85StringError) do
86
+ Filter::ASCII85.decode("ABCD\x01")
87
+ end
88
+
89
+ assert_equal Filter::ASCII85.decode(""), ""
90
+ end
91
+
92
+ def test_filter_rle
93
+ stm = Stream.new(@data, :Filter => :RunLengthDecode)
94
+ raw = stm.encoded_data
95
+ stm.data = nil
96
+ stm.encoded_data = raw
97
+
98
+ assert_equal stm.data, @data
99
+
100
+ assert_raises(Filter::InvalidRunLengthDataError) do
101
+ Filter::RunLength.decode("\x7f")
102
+ end
103
+
104
+ assert_equal Filter::RunLength.decode(""), ""
105
+ end
106
+
107
+ def test_filter_lzw
108
+ stm = Stream.new(@data, :Filter => :LZWDecode)
109
+ raw = stm.encoded_data
110
+ stm.data = nil
111
+ stm.encoded_data = raw
112
+
113
+ assert_equal stm.data, @data
114
+
115
+ assert_raises(Filter::InvalidLZWDataError) do
116
+ Filter::LZW.decode("abcd")
117
+ end
118
+
119
+ assert_equal Filter::LZW.decode(""), ""
120
+ end
121
+
122
+ def test_filter_ccittfax
123
+ stm = Stream.new(@data[0, 216], :Filter => :CCITTFaxDecode)
124
+
125
+ raw = stm.encoded_data
126
+ stm.data = nil
127
+ stm.encoded_data = raw
128
+
129
+ assert_equal stm.data, @data[0, 216]
130
+
131
+ assert_raises(Filter::InvalidCCITTFaxDataError) do
132
+ Filter::CCITTFax.decode("abcd")
133
+ end
134
+
135
+ assert_equal Filter::CCITTFax.decode(""), ""
136
+ end
137
+
138
+ def test_stream
139
+ chain = %i[FlateDecode LZWDecode ASCIIHexDecode]
140
+
141
+ stm = Stream.new(@data, Filter: chain)
142
+ @target << stm
143
+ @target.save(@output)
144
+
145
+ assert stm.Length == stm.encoded_data.length
146
+ assert_equal stm.filters, chain
147
+ assert_equal stm.data, @data
148
+ end
149
+
150
+ def test_object_stream
151
+ objstm = ObjectStream.new
152
+ objstm.Filter = %i[FlateDecode ASCIIHexDecode RunLengthDecode]
153
+
154
+ @target << objstm
155
+
156
+ assert_raises(InvalidObjectError) do
157
+ objstm.insert Stream.new
158
+ end
159
+
160
+ 3.times do
161
+ objstm.insert HexaString.new(@data)
162
+ end
163
+
164
+ assert_equal objstm.objects.size, 3
165
+
166
+ objstm.each_object do |object|
167
+ assert_instance_of HexaString, object
168
+ assert_equal object.parent, objstm
169
+ assert objstm.include?(object.no)
170
+ assert_equal objstm.extract(object.no), object
171
+ assert_equal objstm.extract_by_index(objstm.index(object.no)), object
172
+ end
173
+
174
+ objstm.delete(objstm.objects.first.no)
175
+ assert_equal objstm.objects.size, 2
176
+
177
+ @target.save(@output)
178
+
179
+ assert_instance_of Origami::Integer, objstm.N
180
+ assert_equal objstm.N, objstm.objects.size
181
+ end
182
+ end
@@ -0,0 +1,67 @@
1
+ require 'minitest/autorun'
2
+ require 'stringio'
3
+ require 'strscan'
4
+
5
+ class TestXrefs < MiniTest::Test
6
+
7
+ def setup
8
+ @target = PDF.new
9
+ end
10
+
11
+ def test_xreftable
12
+ output = StringIO.new
13
+
14
+ @target.save(output)
15
+ output.reopen(output.string, 'r')
16
+
17
+ pdf = PDF.read(output, verbosity: Parser::VERBOSE_QUIET, ignore_errors: false)
18
+
19
+ xreftable = pdf.revisions.last.xreftable
20
+ assert_instance_of XRef::Section, xreftable
21
+
22
+ pdf.root_objects.each do |object|
23
+ xref = xreftable.find(object.no)
24
+
25
+ assert_instance_of XRef, xref
26
+ assert xref.used?
27
+
28
+ assert_equal xref.offset, object.file_offset
29
+ end
30
+ end
31
+
32
+ def test_xrefstream
33
+ output = StringIO.new
34
+ objstm = ObjectStream.new
35
+ objstm.Filter = :FlateDecode
36
+
37
+ @target.insert objstm
38
+
39
+ 3.times do
40
+ objstm.insert Null.new
41
+ end
42
+
43
+ @target.save(output)
44
+ output = output.reopen(output.string, 'r')
45
+
46
+ pdf = PDF.read(output, verbosity: Parser::VERBOSE_QUIET, ignore_errors: false)
47
+ xrefstm = pdf.revisions.last.xrefstm
48
+
49
+ assert_instance_of XRefStream, xrefstm
50
+ assert xrefstm.entries.all?{ |xref| xref.is_a?(XRef) or xref.is_a?(XRefToCompressedObj) }
51
+
52
+ pdf.each_object(compressed: true) do |object|
53
+ xref = xrefstm.find(object.no)
54
+
55
+ if object.parent.is_a?(ObjectStream)
56
+ assert_instance_of XRefToCompressedObj, xref
57
+ assert_equal xref.objstmno, object.parent.no
58
+ assert_equal xref.index, object.parent.index(object.no)
59
+ else
60
+ assert_instance_of XRef, xref
61
+ assert_equal xref.offset, object.file_offset
62
+ end
63
+ end
64
+
65
+ assert_instance_of Catalog, xrefstm.Root
66
+ end
67
+ end