origami 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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