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
@@ -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