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
@@ -1,456 +1,544 @@
1
1
  =begin
2
2
 
3
- = File
4
- xreftable.rb
5
-
6
- = Info
7
- This file is part of Origami, PDF manipulation framework for Ruby
8
- Copyright (C) 2010 Guillaume Delugré <guillaume AT security-labs DOT org>
9
- All right reserved.
10
-
11
- Origami is free software: you can redistribute it and/or modify
12
- it under the terms of the GNU Lesser General Public License as published by
13
- the Free Software Foundation, either version 3 of the License, or
14
- (at your option) any later version.
15
-
16
- Origami is distributed in the hope that it will be useful,
17
- but WITHOUT ANY WARRANTY; without even the implied warranty of
18
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
- GNU Lesser General Public License for more details.
20
-
21
- You should have received a copy of the GNU Lesser General Public License
22
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
23
5
 
24
- =end
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
25
10
 
26
- module Origami
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
27
15
 
28
- class PDF
29
-
30
- #
31
- # Tries to strip any xrefs information off the document.
32
- #
33
- def remove_xrefs
34
- def delete_xrefstm(xrefstm)
35
- prev = xrefstm.Prev
36
- delete_object(xrefstm.reference)
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
37
18
 
38
- if prev.is_a?(Integer) and (prev_stm = get_object_by_offset(prev)).is_a?(XRefStream)
39
- delete_xrefstm(prev_stm)
40
- end
41
- end
19
+ =end
42
20
 
43
- @revisions.reverse_each do |rev|
44
- if rev.has_xrefstm?
45
- delete_xrefstm(rev.xrefstm)
46
- end
47
-
48
- if rev.trailer.has_dictionary? and rev.trailer.XRefStm.is_a?(Integer)
49
- xrefstm = get_object_by_offset(rev.trailer.XRefStm)
21
+ module Origami
50
22
 
51
- delete_xrefstm(xrefstm) if xrefstm.is_a?(XRefStream)
52
- end
23
+ class PDF
24
+ #
25
+ # Tries to strip any xrefs information off the document.
26
+ #
27
+ def remove_xrefs
53
28
 
54
- rev.xrefstm = rev.xreftable = nil
55
- end
56
- end
29
+ # Delete a XRefStream and its ancestors.
30
+ delete_xrefstm = -> (xrefstm) do
31
+ prev = xrefstm.Prev
32
+ delete_object(xrefstm.reference)
57
33
 
58
- end
34
+ if prev.is_a?(Integer) and (prev_stm = get_object_by_offset(prev)).is_a?(XRefStream)
35
+ delete_xrefstm.call(prev_stm)
36
+ end
37
+ end
59
38
 
60
- class InvalidXRefError < Exception #:nodoc:
61
- end
39
+ @revisions.reverse_each do |rev|
40
+ if rev.has_xrefstm?
41
+ delete_xrefstm.call(rev.xrefstm)
42
+ end
62
43
 
63
- #
64
- # Class representing a Cross-reference information.
65
- #
66
- class XRef
67
-
68
- FREE = "f"
69
- USED = "n"
70
- FIRSTFREE = 65535
44
+ if rev.trailer.has_dictionary? and rev.trailer.XRefStm.is_a?(Integer)
45
+ xrefstm = get_object_by_offset(rev.trailer.XRefStm)
71
46
 
72
- @@regexp = /(\d{10}) (\d{5}) (n|f)(\r\n| \r| \n)/
73
-
74
- attr_accessor :offset, :generation, :state
75
-
76
- #
77
- # Creates a new XRef.
78
- # _offset_:: The file _offset_ of the referenced Object.
79
- # _generation_:: The generation number of the referenced Object.
80
- # _state_:: The state of the referenced Object (FREE or USED).
81
- #
82
- def initialize(offset, generation, state)
83
- @offset, @generation, @state = offset, generation, state
47
+ delete_xrefstm.call(xrefstm) if xrefstm.is_a?(XRefStream)
48
+ end
49
+
50
+ rev.xrefstm = rev.xreftable = nil
51
+ end
52
+ end
84
53
  end
85
-
86
- def self.parse(stream) #:nodoc:
87
-
88
- if stream.scan(@@regexp).nil?
89
- raise InvalidXRefError, "Invalid XRef format"
90
- end
91
-
92
- offset = stream[1].to_i
93
- generation = stream[2].to_i
94
- state = stream[3]
95
-
96
- XRef.new(offset, generation, state)
54
+
55
+ class InvalidXRefError < Error #:nodoc:
97
56
  end
98
-
57
+
99
58
  #
100
- # Outputs self into PDF code.
59
+ # Class representing a Cross-reference information.
101
60
  #
102
- def to_s
103
- off = ("0" * (10 - @offset.to_s.length)) + @offset.to_s
104
- gen = ("0" * (5 - @generation.to_s.length)) + @generation.to_s
105
-
106
- "#{off} #{gen} #{@state}" + EOL
107
- end
61
+ class XRef
108
62
 
109
- def to_xrefstm_data(type_w, field1_w, field2_w)
110
- type_w <<= 3
111
- field1_w <<= 3
112
- field2_w <<= 3
63
+ FREE = "f"
64
+ USED = "n"
65
+ FIRSTFREE = 65535
113
66
 
114
- type = ((@state == FREE) ? "\000" : "\001").unpack("B#{type_w}")[0]
67
+ @@regexp = /(?<offset>\d{10}) (?<gen>\d{5}) (?<state>n|f)(\r\n| \r| \n)/
115
68
 
116
- offset = @offset.to_s(2)
117
- offset = '0' * (field1_w - offset.size) + offset
118
- generation = @generation.to_s(2)
69
+ attr_accessor :offset, :generation, :state
119
70
 
120
- generation = '0' * (field2_w - generation.size) + generation
121
-
122
- [ type , offset, generation ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
123
- end
124
-
125
- class InvalidXRefSubsectionError < Exception #:nodoc:
126
- end
127
-
128
- #
129
- # Class representing a cross-reference subsection.
130
- # A subsection contains a continute set of XRef.
131
- #
132
- class Subsection
133
-
134
- @@regexp = Regexp.new("(\\d+) (\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
135
-
136
- attr_reader :range
137
-
138
- #
139
- # Creates a new XRef subsection.
140
- # _start_:: The number of the first object referenced in the subsection.
141
- # _entries_:: An array of XRef.
142
- #
143
- def initialize(start, entries = [])
144
-
145
- @entries = entries.dup
146
- @range = Range.new(start, start + entries.size - 1)
147
-
148
- end
149
-
150
- def self.parse(stream) #:nodoc:
151
-
152
- if stream.scan(@@regexp).nil?
153
- raise InvalidXRefSubsectionError, "Bad subsection format"
71
+ #
72
+ # Creates a new XRef.
73
+ # _offset_:: The file _offset_ of the referenced Object.
74
+ # _generation_:: The generation number of the referenced Object.
75
+ # _state_:: The state of the referenced Object (FREE or USED).
76
+ #
77
+ def initialize(offset, generation, state)
78
+ @offset, @generation, @state = offset, generation, state
154
79
  end
155
-
156
- start = stream[1].to_i
157
- size = stream[2].to_i
158
-
159
- xrefs = []
160
- size.times do
161
- xrefs << XRef.parse(stream)
162
- end
163
-
164
- XRef::Subsection.new(start, xrefs)
165
- end
166
-
167
- #
168
- # Returns whether this subsection contains information about a particular object.
169
- # _no_:: The Object number.
170
- #
171
- def has_object?(no)
172
- @range.include?(no)
173
- end
174
-
175
- #
176
- # Returns XRef associated with a given object.
177
- # _no_:: The Object number.
178
- #
179
- def [](no)
180
- @entries[no - @range.begin]
181
- end
182
-
183
- #
184
- # Processes each XRef in the subsection.
185
- #
186
- def each(&b)
187
- @entries.each(&b)
188
- end
189
-
190
- #
191
- # Outputs self into PDF code.
192
- #
193
- def to_s
194
- section = "#{@range.begin} #{@range.end - @range.begin + 1}" + EOL
195
- @entries.each { |xref|
196
- section << xref.to_s
197
- }
198
-
199
- section
200
- end
201
-
202
- end
203
80
 
204
- class InvalidXRefSectionError < Exception #:nodoc:
205
- end
81
+ def self.parse(stream) #:nodoc:
82
+ if stream.scan(@@regexp).nil?
83
+ raise InvalidXRefError, "Invalid XRef format"
84
+ end
206
85
 
207
- #
208
- # Class representing a Cross-reference table.
209
- # A section contains a set of XRefSubsection.
210
- #
211
- class Section
212
-
213
- @@regexp_open = Regexp.new(WHITESPACES + "xref" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
214
- @@regexp_sub = Regexp.new("(\\d+) (\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
215
-
216
- #
217
- # Creates a new XRef section.
218
- # _subsections_:: An array of XRefSubsection.
219
- #
220
- def initialize(subsections = [])
221
- @subsections = subsections
222
- end
223
-
224
- def self.parse(stream) #:nodoc:
225
-
226
- if stream.skip(@@regexp_open).nil?
227
- raise InvalidXRefSectionError, "No xref token found"
86
+ offset = stream['offset'].to_i
87
+ generation = stream['gen'].to_i
88
+ state = stream['state']
89
+
90
+ XRef.new(offset, generation, state)
228
91
  end
229
-
230
- subsections = []
231
- while stream.match?(@@regexp_sub) do
232
- subsections << XRef::Subsection.parse(stream)
92
+
93
+ #
94
+ # Returns true if the associated object is used.
95
+ #
96
+ def used?
97
+ @state == USED
233
98
  end
234
99
 
235
- XRef::Section.new(subsections)
236
- end
237
-
238
- #
239
- # Appends a new subsection.
240
- # _subsection_:: A XRefSubsection.
241
- #
242
- def <<(subsection)
243
- @subsections << subsection
244
- end
245
-
246
- #
247
- # Returns a XRef associated with a given object.
248
- # _no_:: The Object number.
249
- #
250
- def [](no)
251
- @subsections.each { |s|
252
- return s[no] if s.has_object?(no)
253
- }
254
- nil
255
- end
256
-
257
- alias :find :[]
258
-
259
- #
260
- # Processes each XRefSubsection.
261
- #
262
- def each(&b)
263
- @subsections.each(&b)
264
- end
265
-
266
- #
267
- # Outputs self into PDF code.
268
- #
269
- def to_s
270
- "xref" << EOL << @subsections.join
271
- end
272
-
273
- end
274
-
275
- end
100
+ #
101
+ # Returns true if the associated object is freed.
102
+ #
103
+ def free?
104
+ @state == FREE
105
+ end
276
106
 
277
- #
278
- # An xref poiting to an Object embedded in an ObjectStream.
279
- #
280
- class XRefToCompressedObj
107
+ #
108
+ # Outputs self into PDF code.
109
+ #
110
+ def to_s
111
+ off = @offset.to_s.rjust(10, '0')
112
+ gen = @generation.to_s.rjust(5, '0')
281
113
 
282
- attr_accessor :objstmno, :index
114
+ "#{off} #{gen} #{@state}" + EOL
115
+ end
283
116
 
284
- def initialize(objstmno, index)
285
- @objstmno = objstmno
286
- @index = index
287
- end
117
+ def to_xrefstm_data(type_w, field1_w, field2_w)
118
+ type_w <<= 3
119
+ field1_w <<= 3
120
+ field2_w <<= 3
288
121
 
289
- def to_xrefstm_data(type_w, field1_w, field2_w)
122
+ type = ((@state == FREE) ? "\000" : "\001").unpack("B#{type_w}")[0]
290
123
 
291
- type_w <<= 3
292
- field1_w <<= 3
293
- field2_w <<= 3
124
+ offset = @offset.to_s(2).rjust(field1_w, '0')
125
+ generation = @generation.to_s(2).rjust(field2_w, '0')
294
126
 
295
- type = "\002".unpack("B#{type_w}")[0]
296
- objstmno = @objstmno.to_s(2)
297
- objstmno = '0' * (field1_w - objstmno.size) + objstmno
298
- index = @index.to_s(2)
299
- index = '0' * (field2_w - index.size) + index
127
+ [ type , offset, generation ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
128
+ end
300
129
 
301
- [ type , objstmno, index ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
302
- end
130
+ class InvalidXRefSubsectionError < Error #:nodoc:
131
+ end
303
132
 
304
- end
305
-
306
- class InvalidXRefStreamObjectError < InvalidStreamObjectError ; end
133
+ #
134
+ # Class representing a cross-reference subsection.
135
+ # A subsection contains a continute set of XRef.
136
+ #
137
+ class Subsection
138
+ @@regexp = Regexp.new("(?<start>\\d+) (?<size>\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
139
+
140
+ attr_reader :range
141
+
142
+ #
143
+ # Creates a new XRef subsection.
144
+ # _start_:: The number of the first object referenced in the subsection.
145
+ # _entries_:: An array of XRef.
146
+ #
147
+ def initialize(start, entries = [])
148
+ @entries = entries.dup
149
+ @range = Range.new(start, start + entries.size - 1)
150
+ end
151
+
152
+ def self.parse(stream) #:nodoc:
153
+ if stream.scan(@@regexp).nil?
154
+ raise InvalidXRefSubsectionError, "Bad subsection format"
155
+ end
156
+
157
+ start = stream['start'].to_i
158
+ size = stream['size'].to_i
159
+
160
+ xrefs = []
161
+ size.times do
162
+ xrefs << XRef.parse(stream)
163
+ end
164
+
165
+ XRef::Subsection.new(start, xrefs)
166
+ end
167
+
168
+ #
169
+ # Returns whether this subsection contains information about a particular object.
170
+ # _no_:: The Object number.
171
+ #
172
+ def has_object?(no)
173
+ @range.include?(no)
174
+ end
175
+
176
+ #
177
+ # Returns XRef associated with a given object.
178
+ # _no_:: The Object number.
179
+ #
180
+ def [](no)
181
+ @entries[no - @range.begin]
182
+ end
183
+
184
+ #
185
+ # Processes each XRef in the subsection.
186
+ #
187
+ def each(&b)
188
+ @entries.each(&b)
189
+ end
190
+
191
+ #
192
+ # Processes each XRef in the subsection, passing the XRef and the object number to the block.
193
+ #
194
+ def each_with_number
195
+ return enum_for(__method__) { self.size } unless block_given?
196
+
197
+ counter = @range.to_enum
198
+ @entries.each do |entry|
199
+ yield(entry, counter.next)
200
+ end
201
+ end
202
+
203
+ #
204
+ # The number of entries in the subsection.
205
+ #
206
+ def size
207
+ @entries.size
208
+ end
209
+
210
+ #
211
+ # Outputs self into PDF code.
212
+ #
213
+ def to_s
214
+ section = "#{@range.begin} #{@range.end - @range.begin + 1}" + EOL
215
+ @entries.each do |xref|
216
+ section << xref.to_s
217
+ end
218
+
219
+ section
220
+ end
221
+ end
307
222
 
308
- #
309
- # Class representing a XRef Stream.
310
- #
311
- class XRefStream < Stream
223
+ class InvalidXRefSectionError < Error #:nodoc:
224
+ end
312
225
 
313
- XREF_FREE = 0
314
- XREF_USED = 1
315
- XREF_COMPRESSED = 2
316
-
317
- include Enumerable
318
- include StandardObject
226
+ #
227
+ # Class representing a Cross-reference table.
228
+ # A section contains a set of XRefSubsection.
229
+ #
230
+ class Section
231
+ TOKEN = "xref"
232
+
233
+ @@regexp_open = Regexp.new(WHITESPACES + TOKEN + WHITESPACES + "(\\r?\\n|\\r\\n?)")
234
+ @@regexp_sub = Regexp.new("(\\d+) (\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
235
+
236
+ #
237
+ # Creates a new XRef section.
238
+ # _subsections_:: An array of XRefSubsection.
239
+ #
240
+ def initialize(subsections = [])
241
+ @subsections = subsections
242
+ end
243
+
244
+ def self.parse(stream) #:nodoc:
245
+ if stream.skip(@@regexp_open).nil?
246
+ raise InvalidXRefSectionError, "No xref token found"
247
+ end
248
+
249
+ subsections = []
250
+ while stream.match?(@@regexp_sub) do
251
+ subsections << XRef::Subsection.parse(stream)
252
+ end
253
+
254
+ XRef::Section.new(subsections)
255
+ end
256
+
257
+ #
258
+ # Appends a new subsection.
259
+ # _subsection_:: A XRefSubsection.
260
+ #
261
+ def <<(subsection)
262
+ @subsections << subsection
263
+ end
264
+
265
+ #
266
+ # Returns a XRef associated with a given object.
267
+ # _no_:: The Object number.
268
+ #
269
+ def [](no)
270
+ @subsections.each do |s|
271
+ return s[no] if s.has_object?(no)
272
+ end
273
+
274
+ nil
275
+ end
276
+ alias find []
277
+
278
+ #
279
+ # Processes each XRef in each Subsection.
280
+ #
281
+ def each(&b)
282
+ return enum_for(__method__) { self.size } unless block_given?
283
+
284
+ @subsections.each do |subsection|
285
+ subsection.each(&b)
286
+ end
287
+ end
288
+
289
+ #
290
+ # Processes each XRef in each Subsection, passing the XRef and the object number.
291
+ #
292
+ def each_with_number(&b)
293
+ return enum_for(__method__) { self.size } unless block_given?
294
+
295
+ @subsections.each do |subsection|
296
+ subsection.each_with_number(&b)
297
+ end
298
+ end
299
+
300
+ #
301
+ # Processes each Subsection in this table.
302
+ #
303
+ def each_subsection(&b)
304
+ @subsections.each(&b)
305
+ end
306
+
307
+ #
308
+ # Returns an Array of Subsection.
309
+ #
310
+ def subsections
311
+ @subsections
312
+ end
313
+
314
+ #
315
+ # Clear all the entries.
316
+ #
317
+ def clear
318
+ @subsections.clear
319
+ end
320
+
321
+ #
322
+ # The number of XRef entries in the Section.
323
+ #
324
+ def size
325
+ @subsections.reduce(0) { |total, subsection| total + subsection.size }
326
+ end
327
+
328
+ #
329
+ # Outputs self into PDF code.
330
+ #
331
+ def to_s
332
+ "xref" << EOL << @subsections.join
333
+ end
334
+ end
335
+ end
319
336
 
320
337
  #
321
- # Xref fields
338
+ # An xref poiting to an Object embedded in an ObjectStream.
322
339
  #
323
- field :Type, :Type => Name, :Default => :XRef, :Required => true, :Version => "1.5"
324
- field :Size, :Type => Integer, :Required => true
325
- field :Index, :Type => Array
326
- field :Prev, :Type => Integer
327
- field :W, :Type => Array, :Required => true
340
+ class XRefToCompressedObj
341
+ attr_accessor :objstmno, :index
328
342
 
329
- #
330
- # Trailer fields
331
- #
332
- field :Root, :Type => Dictionary, :Required => true
333
- field :Encrypt, :Type => Dictionary
334
- field :Info, :Type => Dictionary
335
- field :ID, :Type => Array
336
-
337
- def initialize(data = "", dictionary = {})
338
- super(data, dictionary)
339
-
340
- @xrefs = nil
341
- end
343
+ def initialize(objstmno, index)
344
+ @objstmno = objstmno
345
+ @index = index
346
+ end
347
+
348
+ def to_xrefstm_data(type_w, field1_w, field2_w)
349
+ type_w <<= 3
350
+ field1_w <<= 3
351
+ field2_w <<= 3
342
352
 
343
- def entries
344
- load! if @xrefs.nil?
353
+ type = "\002".unpack("B#{type_w}")[0]
354
+ objstmno = @objstmno.to_s(2).rjust(field1_w, '0')
355
+ index = @index.to_s(2).rjust(field2_w, '0')
345
356
 
346
- @xrefs
357
+ [ type , objstmno, index ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
358
+ end
347
359
  end
348
360
 
361
+ class InvalidXRefStreamObjectError < InvalidStreamObjectError ; end
362
+
349
363
  #
350
- # Returns XRef entries present in this stream.
364
+ # Class representing a XRef Stream.
351
365
  #
352
- def pre_build #:nodoc:
353
- load! if @xrefs.nil?
366
+ class XRefStream < Stream
367
+ include Enumerable
368
+ include StandardObject
369
+
370
+ XREF_FREE = 0
371
+ XREF_USED = 1
372
+ XREF_COMPRESSED = 2
373
+
374
+ #
375
+ # Xref fields
376
+ #
377
+ field :Type, :Type => Name, :Default => :XRef, :Required => true, :Version => "1.5"
378
+ field :Size, :Type => Integer, :Required => true
379
+ field :Index, :Type => Array.of(Integer, Integer)
380
+ field :Prev, :Type => Integer
381
+ field :W, :Type => Array.of(Integer, length: 3), :Required => true
382
+
383
+ #
384
+ # Trailer fields
385
+ #
386
+ field :Root, :Type => Catalog, :Required => true
387
+ field :Encrypt, :Type => Encryption::Standard::Dictionary
388
+ field :Info, :Type => Metadata
389
+ field :ID, :Type => Array.of(String, length: 2)
390
+
391
+ def initialize(data = "", dictionary = {})
392
+ super(data, dictionary)
393
+
394
+ @xrefs = nil
395
+ end
354
396
 
355
- self.W = [ 1, 2, 2 ] unless has_field?(:W)
356
- self.Size = @xrefs.length + 1
397
+ def entries
398
+ load! if @xrefs.nil?
357
399
 
358
- save!
400
+ @xrefs
401
+ end
359
402
 
360
- super
361
- end
403
+ #
404
+ # Returns XRef entries present in this stream.
405
+ #
406
+ def pre_build #:nodoc:
407
+ load! if @xrefs.nil?
362
408
 
363
- #
364
- # Adds an XRef to this Stream.
365
- #
366
- def <<(xref)
367
- load! if @xrefs.nil?
409
+ self.W = [ 1, 2, 2 ] unless has_field?(:W)
410
+ self.Size = @xrefs.length + 1
368
411
 
369
- @xrefs << xref
370
- end
412
+ save!
371
413
 
372
- #
373
- # Iterates over each XRef present in the stream.
374
- #
375
- def each(&b)
376
- load! if @xrefs.nil?
414
+ super
415
+ end
377
416
 
378
- @xrefs.each(&b)
379
- end
417
+ #
418
+ # Adds an XRef to this Stream.
419
+ #
420
+ def <<(xref)
421
+ load! if @xrefs.nil?
380
422
 
381
- #
382
- # Returns an XRef matching this object number.
383
- #
384
- def find(no)
385
- load! if @xrefs.nil?
423
+ @xrefs << xref
424
+ end
386
425
 
387
- ranges = self.Index || [ 0, @xrefs.length ]
426
+ #
427
+ # Iterates over each XRef present in the stream.
428
+ #
429
+ def each(&b)
430
+ load! if @xrefs.nil?
388
431
 
389
- index = 0
390
- (ranges.size / 2).times do |i|
391
- brange = ranges[i*2].to_i
392
- size = ranges[i*2+1].to_i
393
- return @xrefs[index + no - brange] if Range.new(brange, brange + size - 1) === no
432
+ @xrefs.each(&b)
433
+ end
394
434
 
395
- index += size
396
- end
435
+ #
436
+ # Iterates over each XRef present in the stream, passing the XRef and its object number.
437
+ #
438
+ def each_with_number
439
+ return enum_for(__method__) unless block_given?
440
+
441
+ load! if @xrefs.nil?
442
+
443
+ ranges = object_ranges
444
+ xrefs = @xrefs.to_enum
445
+
446
+ ranges.each do |range|
447
+ range.each do |no|
448
+ begin
449
+ yield(xrefs.next, no)
450
+ rescue StopIteration
451
+ raise InvalidXRefStreamObjectError, "Range is bigger than number of entries"
452
+ end
453
+ end
454
+ end
455
+ end
397
456
 
398
- nil
399
- end
457
+ #
458
+ # Returns an XRef matching this object number.
459
+ #
460
+ def find(no)
461
+ load! if @xrefs.nil?
400
462
 
401
- def clear
402
- self.data = ''
403
- @xrefs = []
404
- self.Index = []
405
- end
463
+ ranges = object_ranges
406
464
 
407
- private
465
+ index = 0
466
+ ranges.each do |range|
467
+ return @xrefs[index + no - range.begin] if range.cover?(no)
408
468
 
409
- def load! #:nodoc:
410
- if @xrefs.nil? and has_field?(:W)
411
- widths = self.W
469
+ index += range.size
470
+ end
412
471
 
413
- if not widths.is_a?(Array) or widths.length != 3 or widths.any?{|width| not width.is_a?(Integer) }
414
- raise InvalidXRefStreamObjectError, "W field must be an array of 3 integers"
472
+ nil
415
473
  end
416
474
 
417
- decode!
418
-
419
- type_w = self.W[0]
420
- field1_w = self.W[1]
421
- field2_w = self.W[2]
422
-
423
- entrymask = "B#{type_w << 3}B#{field1_w << 3}B#{field2_w << 3}"
424
- size = @data.size / (type_w + field1_w + field2_w)
425
-
426
- xentries = @data.unpack(entrymask * size).map!{|field| field.to_i(2) }
427
-
428
- @xrefs = []
429
- size.times do |i|
430
- type,field1,field2 = xentries[i*3].ord,xentries[i*3+1].ord,xentries[i*3+2].ord
431
- case type
432
- when XREF_FREE
433
- @xrefs << XRef.new(field1, field2, XRef::FREE)
434
- when XREF_USED
435
- @xrefs << XRef.new(field1, field2, XRef::USED)
436
- when XREF_COMPRESSED
437
- @xrefs << XRefToCompressedObj.new(field1, field2)
438
- end
475
+ def clear
476
+ self.data = ''
477
+ @xrefs = []
478
+ self.Index = []
439
479
  end
440
- else
441
- @xrefs = []
442
- end
443
- end
444
480
 
445
- def save! #:nodoc:
446
- self.data = ""
481
+ private
447
482
 
448
- type_w, field1_w, field2_w = self.W
449
- @xrefs.each do |xref| @data << xref.to_xrefstm_data(type_w, field1_w, field2_w) end
483
+ def object_ranges
484
+ load! if @xrefs.nil?
450
485
 
451
- encode!
452
- end
486
+ if self.key?(:Index)
487
+ ranges = self.Index
488
+ unless ranges.is_a?(Array) and ranges.length.even? and ranges.all?{|i| i.is_a?(Integer)}
489
+ raise InvalidXRefStreamObjectError, "Index must be an even Array of integers"
490
+ end
491
+
492
+ ranges.each_slice(2).map { |start, length| Range.new(start.to_i, start.to_i + length.to_i - 1) }
493
+ else
494
+ [ 0...@xrefs.size ]
495
+ end
496
+ end
497
+
498
+ def load! #:nodoc:
499
+ if @xrefs.nil? and has_field?(:W)
500
+ widths = self.W
501
+
502
+ if not widths.is_a?(Array) or widths.length != 3 or widths.any?{|width| not width.is_a?(Integer) }
503
+ raise InvalidXRefStreamObjectError, "W field must be an array of 3 integers"
504
+ end
505
+
506
+ decode!
507
+
508
+ type_w = self.W[0]
509
+ field1_w = self.W[1]
510
+ field2_w = self.W[2]
511
+
512
+ entrymask = "B#{type_w << 3}B#{field1_w << 3}B#{field2_w << 3}"
513
+ size = @data.size / (type_w + field1_w + field2_w)
514
+
515
+ xentries = @data.unpack(entrymask * size).map!{|field| field.to_i(2) }
516
+
517
+ @xrefs = []
518
+ size.times do |i|
519
+ type,field1,field2 = xentries[i*3].ord,xentries[i*3+1].ord,xentries[i*3+2].ord
520
+ case type
521
+ when XREF_FREE
522
+ @xrefs << XRef.new(field1, field2, XRef::FREE)
523
+ when XREF_USED
524
+ @xrefs << XRef.new(field1, field2, XRef::USED)
525
+ when XREF_COMPRESSED
526
+ @xrefs << XRefToCompressedObj.new(field1, field2)
527
+ end
528
+ end
529
+ else
530
+ @xrefs = []
531
+ end
532
+ end
453
533
 
454
- end
534
+ def save! #:nodoc:
535
+ self.data = ""
536
+
537
+ type_w, field1_w, field2_w = self.W
538
+ @xrefs.each do |xref| @data << xref.to_xrefstm_data(type_w, field1_w, field2_w) end
539
+
540
+ encode!
541
+ end
542
+ end
455
543
 
456
544
  end