origami 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/COPYING.LESSER +165 -0
  2. data/README +77 -0
  3. data/VERSION +1 -0
  4. data/bin/config/pdfcop.conf.yml +237 -0
  5. data/bin/gui/about.rb +46 -0
  6. data/bin/gui/config.rb +132 -0
  7. data/bin/gui/file.rb +385 -0
  8. data/bin/gui/hexdump.rb +74 -0
  9. data/bin/gui/hexview.rb +91 -0
  10. data/bin/gui/imgview.rb +72 -0
  11. data/bin/gui/menu.rb +392 -0
  12. data/bin/gui/properties.rb +132 -0
  13. data/bin/gui/signing.rb +635 -0
  14. data/bin/gui/textview.rb +107 -0
  15. data/bin/gui/treeview.rb +409 -0
  16. data/bin/gui/walker.rb +282 -0
  17. data/bin/gui/xrefs.rb +79 -0
  18. data/bin/pdf2graph +121 -0
  19. data/bin/pdf2ruby +353 -0
  20. data/bin/pdfcocoon +104 -0
  21. data/bin/pdfcop +455 -0
  22. data/bin/pdfdecompress +104 -0
  23. data/bin/pdfdecrypt +95 -0
  24. data/bin/pdfencrypt +112 -0
  25. data/bin/pdfextract +221 -0
  26. data/bin/pdfmetadata +123 -0
  27. data/bin/pdfsh +13 -0
  28. data/bin/pdfwalker +7 -0
  29. data/bin/shell/.irbrc +104 -0
  30. data/bin/shell/console.rb +136 -0
  31. data/bin/shell/hexdump.rb +83 -0
  32. data/origami.rb +36 -0
  33. data/origami/3d.rb +239 -0
  34. data/origami/acroform.rb +321 -0
  35. data/origami/actions.rb +299 -0
  36. data/origami/adobe/fdf.rb +259 -0
  37. data/origami/adobe/ppklite.rb +489 -0
  38. data/origami/annotations.rb +775 -0
  39. data/origami/array.rb +187 -0
  40. data/origami/boolean.rb +101 -0
  41. data/origami/catalog.rb +486 -0
  42. data/origami/destinations.rb +213 -0
  43. data/origami/dictionary.rb +188 -0
  44. data/origami/docmdp.rb +96 -0
  45. data/origami/encryption.rb +1293 -0
  46. data/origami/export.rb +283 -0
  47. data/origami/file.rb +222 -0
  48. data/origami/filters.rb +250 -0
  49. data/origami/filters/ascii.rb +189 -0
  50. data/origami/filters/ccitt.rb +515 -0
  51. data/origami/filters/crypt.rb +47 -0
  52. data/origami/filters/dct.rb +61 -0
  53. data/origami/filters/flate.rb +112 -0
  54. data/origami/filters/jbig2.rb +63 -0
  55. data/origami/filters/jpx.rb +53 -0
  56. data/origami/filters/lzw.rb +195 -0
  57. data/origami/filters/predictors.rb +276 -0
  58. data/origami/filters/runlength.rb +117 -0
  59. data/origami/font.rb +209 -0
  60. data/origami/functions.rb +93 -0
  61. data/origami/graphics.rb +33 -0
  62. data/origami/graphics/colors.rb +191 -0
  63. data/origami/graphics/instruction.rb +126 -0
  64. data/origami/graphics/path.rb +154 -0
  65. data/origami/graphics/patterns.rb +180 -0
  66. data/origami/graphics/state.rb +164 -0
  67. data/origami/graphics/text.rb +224 -0
  68. data/origami/graphics/xobject.rb +493 -0
  69. data/origami/header.rb +90 -0
  70. data/origami/linearization.rb +318 -0
  71. data/origami/metadata.rb +114 -0
  72. data/origami/name.rb +170 -0
  73. data/origami/null.rb +75 -0
  74. data/origami/numeric.rb +188 -0
  75. data/origami/obfuscation.rb +233 -0
  76. data/origami/object.rb +527 -0
  77. data/origami/outline.rb +59 -0
  78. data/origami/page.rb +559 -0
  79. data/origami/parser.rb +268 -0
  80. data/origami/parsers/fdf.rb +45 -0
  81. data/origami/parsers/pdf.rb +27 -0
  82. data/origami/parsers/pdf/linear.rb +113 -0
  83. data/origami/parsers/ppklite.rb +86 -0
  84. data/origami/pdf.rb +1144 -0
  85. data/origami/reference.rb +113 -0
  86. data/origami/signature.rb +474 -0
  87. data/origami/stream.rb +575 -0
  88. data/origami/string.rb +416 -0
  89. data/origami/trailer.rb +173 -0
  90. data/origami/webcapture.rb +87 -0
  91. data/origami/xfa.rb +3027 -0
  92. data/origami/xreftable.rb +447 -0
  93. data/templates/patterns.rb +66 -0
  94. data/templates/widgets.rb +173 -0
  95. data/templates/xdp.rb +92 -0
  96. data/tests/dataset/test.dummycrt +28 -0
  97. data/tests/dataset/test.dummykey +27 -0
  98. data/tests/tc_actions.rb +32 -0
  99. data/tests/tc_annotations.rb +85 -0
  100. data/tests/tc_pages.rb +37 -0
  101. data/tests/tc_pdfattach.rb +24 -0
  102. data/tests/tc_pdfencrypt.rb +110 -0
  103. data/tests/tc_pdfnew.rb +32 -0
  104. data/tests/tc_pdfparse.rb +98 -0
  105. data/tests/tc_pdfsig.rb +37 -0
  106. data/tests/tc_streams.rb +129 -0
  107. data/tests/ts_pdf.rb +45 -0
  108. metadata +193 -0
@@ -0,0 +1,416 @@
1
+ =begin
2
+
3
+ = File
4
+ string.rb
5
+
6
+ = Info
7
+ Origami is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ Origami is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+
22
+ module Origami
23
+
24
+ #
25
+ # Module common to String objects.
26
+ #
27
+ module String
28
+
29
+ module Encoding
30
+ class EncodingError < Exception #:nodoc:
31
+ end
32
+
33
+ module PDFDocEncoding
34
+
35
+ CHARMAP =
36
+ [
37
+ "\x00\x00", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd",
38
+ "\xff\xfd", "\x00\x09", "\x00\x0a", "\xff\xfd", "\x00\x0c", "\x00\x0d", "\xff\xfd", "\xff\xfd",
39
+ "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd", "\xff\xfd",
40
+ "\x02\xd8", "\x02\xc7", "\x02\xc6", "\x02\xd9", "\x02\xdd", "\x02\xdb", "\x02\xda", "\x02\xdc",
41
+ "\x00\x20", "\x00\x21", "\x00\x22", "\x00\x23", "\x00\x24", "\x00\x25", "\x00\x26", "\x00\x27",
42
+ "\x00\x28", "\x00\x29", "\x00\x2a", "\x00\x2b", "\x00\x2c", "\x00\x2d", "\x00\x2e", "\x00\x2f",
43
+ "\x00\x30", "\x00\x31", "\x00\x32", "\x00\x33", "\x00\x34", "\x00\x35", "\x00\x36", "\x00\x37",
44
+ "\x00\x38", "\x00\x39", "\x00\x3a", "\x00\x3b", "\x00\x3c", "\x00\x3d", "\x00\x3e", "\x00\x3f",
45
+ "\x00\x40", "\x00\x41", "\x00\x42", "\x00\x43", "\x00\x44", "\x00\x45", "\x00\x46", "\x00\x47",
46
+ "\x00\x48", "\x00\x49", "\x00\x4a", "\x00\x4b", "\x00\x4c", "\x00\x4d", "\x00\x4e", "\x00\x4f",
47
+ "\x00\x50", "\x00\x51", "\x00\x52", "\x00\x53", "\x00\x54", "\x00\x55", "\x00\x56", "\x00\x57",
48
+ "\x00\x58", "\x00\x59", "\x00\x5a", "\x00\x5b", "\x00\x5c", "\x00\x5d", "\x00\x5e", "\x00\x5f",
49
+ "\x00\x60", "\x00\x61", "\x00\x62", "\x00\x63", "\x00\x64", "\x00\x65", "\x00\x66", "\x00\x67",
50
+ "\x00\x68", "\x00\x69", "\x00\x6a", "\x00\x6b", "\x00\x6c", "\x00\x6d", "\x00\x6e", "\x00\x6f",
51
+ "\x00\x70", "\x00\x71", "\x00\x72", "\x00\x73", "\x00\x74", "\x00\x75", "\x00\x76", "\x00\x77",
52
+ "\x00\x78", "\x00\x79", "\x00\x7a", "\x00\x7b", "\x00\x7c", "\x00\x7d", "\x00\x7e", "\xff\xfd",
53
+ "\x20\x22", "\x20\x20", "\x20\x21", "\x20\x26", "\x20\x14", "\x20\x13", "\x01\x92", "\x20\x44",
54
+ "\x20\x39", "\x20\x3a", "\x22\x12", "\x20\x30", "\x20\x1e", "\x20\x1c", "\x20\x1d", "\x20\x18",
55
+ "\x20\x19", "\x20\x1a", "\x21\x22", "\xfb\x01", "\xfb\x02", "\x01\x41", "\x01\x52", "\x01\x60",
56
+ "\x01\x78", "\x01\x7d", "\x01\x31", "\x01\x42", "\x01\x53", "\x01\x61", "\x01\x7e", "\xff\xfd",
57
+ "\x20\xac", "\x00\xa1", "\x00\xa2", "\x00\xa3", "\x00\xa4", "\x00\xa5", "\x00\xa6", "\x00\xa7",
58
+ "\x00\xa8", "\x00\xa9", "\x00\xaa", "\x00\xab", "\x00\xac", "\xff\xfd", "\x00\xae", "\x00\xaf",
59
+ "\x00\xb0", "\x00\xb1", "\x00\xb2", "\x00\xb3", "\x00\xb4", "\x00\xb5", "\x00\xb6", "\x00\xb7",
60
+ "\x00\xb8", "\x00\xb9", "\x00\xba", "\x00\xbb", "\x00\xbc", "\x00\xbd", "\x00\xbe", "\x00\xbf",
61
+ "\x00\xc0", "\x00\xc1", "\x00\xc2", "\x00\xc3", "\x00\xc4", "\x00\xc5", "\x00\xc6", "\x00\xc7",
62
+ "\x00\xc8", "\x00\xc9", "\x00\xca", "\x00\xcb", "\x00\xcc", "\x00\xcd", "\x00\xce", "\x00\xcf",
63
+ "\x00\xd0", "\x00\xd1", "\x00\xd2", "\x00\xd3", "\x00\xd4", "\x00\xd5", "\x00\xd6", "\x00\xd7",
64
+ "\x00\xd8", "\x00\xd9", "\x00\xda", "\x00\xdb", "\x00\xdc", "\x00\xdd", "\x00\xde", "\x00\xdf",
65
+ "\x00\xe0", "\x00\xe1", "\x00\xe2", "\x00\xe3", "\x00\xe4", "\x00\xe5", "\x00\xe6", "\x00\xe7",
66
+ "\x00\xe8", "\x00\xe9", "\x00\xea", "\x00\xeb", "\x00\xec", "\x00\xed", "\x00\xee", "\x00\xef",
67
+ "\x00\xf0", "\x00\xf1", "\x00\xf2", "\x00\xf3", "\x00\xf4", "\x00\xf5", "\x00\xf6", "\x00\xf7",
68
+ "\x00\xf8", "\x00\xf9", "\x00\xfa", "\x00\xfb", "\x00\xfc", "\x00\xfd", "\x00\xfe", "\x00\xff"
69
+ ]
70
+
71
+ def PDFDocEncoding.to_utf16be(pdfdocstr)
72
+
73
+ utf16bestr = "#{UTF16BE::MAGIC}"
74
+ pdfdocstr.each_byte do |byte|
75
+ utf16bestr << CHARMAP[byte]
76
+ end
77
+
78
+ utf16bestr
79
+ end
80
+
81
+ def PDFDocEncoding.to_pdfdoc(str)
82
+ str
83
+ end
84
+
85
+ end
86
+
87
+ module UTF16BE
88
+
89
+ MAGIC = "\xFE\xFF"
90
+
91
+ def UTF16BE.to_utf16be(str)
92
+ str
93
+ end
94
+
95
+ def UTF16BE.to_pdfdoc(str)
96
+ pdfdoc = []
97
+ i = 2
98
+
99
+ while i < str.size
100
+ char = PDFDocEncoding::CHARMAP.index(str[i,2])
101
+ raise EncodingError, "Can't convert UTF16-BE character to PDFDocEncoding" if char.nil?
102
+ pdfdoc << char
103
+ i = i + 2
104
+ end
105
+
106
+ pdfdoc.pack("C*")
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+
113
+ include Origami::Object
114
+
115
+ attr_accessor :encoding
116
+
117
+ def real_type ; Origami::String end
118
+
119
+ def initialize(str) #:nodoc:
120
+ infer_encoding
121
+ super(str)
122
+ end
123
+
124
+ #
125
+ # Convert String object to an UTF8 encoded Ruby string.
126
+ #
127
+ def to_utf8
128
+ require 'iconv'
129
+
130
+ infer_encoding
131
+ i = Iconv.new("UTF-8", "UTF-16")
132
+ utf8str = i.iconv(self.encoding.to_utf16be(self.value))
133
+ i.close
134
+
135
+ utf8str
136
+ end
137
+
138
+ #
139
+ # Convert String object to an UTF16-BE encoded Ruby string.
140
+ #
141
+ def to_utf16be
142
+ infer_encoding
143
+ self.encoding.to_utf16be(self.value)
144
+ end
145
+
146
+ #
147
+ # Convert String object to a PDFDocEncoding encoded Ruby string.
148
+ #
149
+ def to_pdfdoc
150
+ infer_encoding
151
+ self.encoding.to_pdfdoc(self.value)
152
+ end
153
+
154
+ def infer_encoding #:nodoc:
155
+ @encoding =
156
+ if self.value[0,2] == Encoding::UTF16BE::MAGIC
157
+ Encoding::UTF16BE
158
+ else
159
+ Encoding::PDFDocEncoding
160
+ end
161
+ end
162
+ end
163
+
164
+ class InvalidHexaStringObjectError < InvalidObjectError #:nodoc:
165
+ end
166
+
167
+ #
168
+ # Class representing an hexadecimal-writen String Object.
169
+ #
170
+ class HexaString < ::String
171
+ include String
172
+
173
+ TOKENS = %w{ < > } #:nodoc:
174
+
175
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
176
+ @@regexp_close = Regexp.new(TOKENS.last)
177
+
178
+ #
179
+ # Creates a new PDF hexadecimal String.
180
+ # _str_:: The string value.
181
+ #
182
+ def initialize(str = "")
183
+
184
+ unless str.is_a?(::String)
185
+ raise TypeError, "Expected type String, received #{str.class}."
186
+ end
187
+
188
+ super(str)
189
+ end
190
+
191
+ def self.parse(stream) #:nodoc:
192
+
193
+ offset = stream.pos
194
+
195
+ if stream.skip(@@regexp_open).nil?
196
+ raise InvalidHexaStringObjectError, "Hexadecimal string shall start with a '#{TOKENS.first}' token"
197
+ end
198
+
199
+ hexa = stream.scan_until(@@regexp_close)
200
+ if hexa.nil?
201
+ raise InvalidHexaStringObjectError, "Hexadecimal string shall end with a '#{TOKENS.last}' token"
202
+ end
203
+
204
+ decoded = Filter::ASCIIHex.decode(hexa.chomp!(TOKENS.last))
205
+
206
+ hexastr = HexaString.new(decoded)
207
+ hexastr.file_offset = offset
208
+
209
+ hexastr
210
+ end
211
+
212
+ def to_s #:nodoc:
213
+ super(TOKENS.first + Filter::ASCIIHex.encode(to_str) + TOKENS.last)
214
+ end
215
+
216
+ #
217
+ # Converts self to ByteString
218
+ #
219
+ def to_raw
220
+ ByteString.new(self.value)
221
+ end
222
+
223
+ def value
224
+ self.decrypt! if self.is_a?(Encryption::EncryptedString) and not @decrypted
225
+
226
+ to_str
227
+ end
228
+ end
229
+
230
+ class InvalidByteStringObjectError < InvalidObjectError #:nodoc:
231
+ end
232
+
233
+ #
234
+ # Class representing an ASCII String Object.
235
+ #
236
+ class ByteString < ::String
237
+
238
+ include String
239
+
240
+ TOKENS = %w{ ( ) } #:nodoc:
241
+
242
+ @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first))
243
+ @@regexp_close = Regexp.new(Regexp.escape(TOKENS.last))
244
+
245
+ #
246
+ # Creates a new PDF String.
247
+ # _str_:: The string value.
248
+ #
249
+ def initialize(str = "")
250
+
251
+ unless str.is_a?(::String)
252
+ raise TypeError, "Expected type String, received #{str.class}."
253
+ end
254
+
255
+ super(str)
256
+ end
257
+
258
+ def self.parse(stream) #:nodoc:
259
+
260
+ offset = stream.pos
261
+
262
+ if not stream.skip(@@regexp_open)
263
+ raise InvalidByteStringObjectError, "No literal string start token found"
264
+ end
265
+
266
+ result = ""
267
+ depth = 0
268
+ while depth != 0 or stream.peek(1) != TOKENS.last do
269
+
270
+ if stream.eos?
271
+ raise InvalidByteStringObjectError, "Non-terminated string"
272
+ end
273
+
274
+ c = stream.get_byte
275
+ case c
276
+ when "\\"
277
+ if stream.match?(/\d{1,3}/)
278
+ oct = stream.peek(3).oct.chr
279
+ stream.pos += 3
280
+ result << oct
281
+ elsif stream.match?(/((\r?\n)|(\r\n?))/)
282
+
283
+ stream.skip(/((\r?\n)|(\r\n?))/)
284
+ next
285
+
286
+ else
287
+ flag = stream.get_byte
288
+ case flag
289
+ when "n" then result << "\n"
290
+ when "r" then result << "\r"
291
+ when "t" then result << "\t"
292
+ when "b" then result << "\b"
293
+ when "f" then result << "\f"
294
+ when "(" then result << "("
295
+ when ")" then result << ")"
296
+ when "\\" then result << "\\"
297
+ when "\r"
298
+ if str.peek(1) == "\n" then stream.pos += 1 end
299
+ when "\n"
300
+ else
301
+ result << flag
302
+ end
303
+ end
304
+
305
+ when "(" then
306
+ depth = depth + 1
307
+ result << c
308
+ when ")" then
309
+ depth = depth - 1
310
+ result << c
311
+ else
312
+ result << c
313
+ end
314
+
315
+ end
316
+
317
+ if not stream.skip(@@regexp_close)
318
+ raise InvalidByteStringObjectError, "Byte string shall be terminated with '#{TOKENS.last}'"
319
+ end
320
+
321
+ bytestr = ByteString.new(result)
322
+ bytestr.file_offset
323
+
324
+ bytestr
325
+ end
326
+
327
+ def expand #:nodoc:
328
+
329
+ extended = self.gsub("\\", "\\\\\\\\")
330
+ extended.gsub!(/\)/, "\\)")
331
+ extended.gsub!("\n", "\\n")
332
+ extended.gsub!("\r", "\\r")
333
+ extended.gsub!(/\(/, "\\(")
334
+
335
+ extended
336
+ end
337
+
338
+ def to_s #:nodoc:
339
+ super(TOKENS.first + self.expand + TOKENS.last)
340
+ end
341
+
342
+ #
343
+ # Converts self to HexaString
344
+ #
345
+ def to_hex
346
+ HexaString.new(self.value)
347
+ end
348
+
349
+ def value
350
+ self.decrypt! if self.is_a?(Encryption::EncryptedString) and not @decrypted
351
+
352
+ to_str
353
+ end
354
+ end
355
+
356
+ #
357
+ # Class representing a Date string.
358
+ # _Not used_
359
+ # _Not tested_
360
+ #
361
+ class Date < ByteString #:nodoc:
362
+
363
+ REGEXP_TOKEN = "(D:)?(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(?:([\\+-Z])(?:(\\d{2})')?(?:(\\d{2})')?)?"
364
+
365
+ def initialize(year, month = nil, day = nil, hour = nil, minute = nil, second = nil, ut_sign = nil, ut_hours = nil, ut_min = nil)
366
+
367
+ year_str = '%04d' % year
368
+ month_str = month.nil? ? '01' : '%02d' % month
369
+ day_str = day.nil? ? '01' : '%02d' % day
370
+ hour_str = '%02d' % hour
371
+ minute_str = '%02d' % minute
372
+ second_str = '%02d' % second
373
+
374
+ date_str = "D:#{year_str}#{month_str}#{day_str}#{hour_str}#{minute_str}#{second_str}"
375
+ date_str << "#{ut_sign}#{'%02d' % ut_hours}'#{'%02d' % ut_min}" unless ut_sign.nil?
376
+
377
+ super(date_str)
378
+ end
379
+
380
+ def self.parse(stream) #:nodoc:
381
+
382
+ dateReg = Regexp.new(REGEXP_TOKEN)
383
+
384
+ raise InvalidDate if stream.scan(dateReg).nil?
385
+
386
+ year = stream[2].to_i
387
+ month = stream[3] and stream[3].to_i
388
+ day = stream[4] and stream[4].to_i
389
+ hour = stream[5] and stream[5].to_i
390
+ min = stream[6] and stream[6].to_i
391
+ sec = stream[7] and stream[7].to_i
392
+ ut_sign = stream[8]
393
+ ut_hours = stream[9] and stream[9].to_i
394
+ ut_min = stream[10] and stream[10].to_i
395
+
396
+ Origami::Date.new(year, month, day, hour, min, sec, ut_sign, ut_hours, ut_min)
397
+ end
398
+
399
+ #
400
+ # Returns current Date String in UTC time.
401
+ #
402
+ def self.now
403
+ now = Time.now.getutc
404
+ year = now.strftime("%Y").to_i
405
+ month = now.strftime("%m").to_i
406
+ day = now.strftime("%d").to_i
407
+ hour = now.strftime("%H").to_i
408
+ min = now.strftime("%M").to_i
409
+ sec = now.strftime("%S").to_i
410
+
411
+ Origami::Date.new(year, month, day, hour, min, sec, 'Z', 0, 0)
412
+ end
413
+
414
+ end
415
+
416
+ end
@@ -0,0 +1,173 @@
1
+ =begin
2
+
3
+ = File
4
+ trailer.rb
5
+
6
+ = Info
7
+ Origami is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ Origami is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+
22
+ require 'digest/md5'
23
+
24
+ module Origami
25
+
26
+ class PDF
27
+
28
+ private
29
+
30
+ def has_attr?(attr) #:nodoc:
31
+ not get_doc_attr(attr).nil?
32
+ end
33
+
34
+ def get_doc_attr(attr) #:nodoc:
35
+
36
+ @revisions.reverse_each do |rev|
37
+ if rev.trailer.has_dictionary? and not rev.trailer.dictionary[attr].nil?
38
+ return rev.trailer.send(attr)
39
+ else
40
+ xrefstm = get_object_by_offset(rev.trailer.startxref)
41
+ if xrefstm.is_a?(XRefStream) and xrefstm.has_field?(attr)
42
+ return xrefstm.send(attr)
43
+ end
44
+ end
45
+ end
46
+
47
+ nil
48
+ end
49
+
50
+ def get_trailer_info #:nodoc:
51
+
52
+ #
53
+ # First look for a standard trailer dictionary
54
+ #
55
+ if @revisions.last.trailer.has_dictionary?
56
+ @revisions.last.trailer
57
+
58
+ #
59
+ # Otherwise look for a xref stream.
60
+ #
61
+ else
62
+ xrefstm = get_object_by_offset(@revisions.last.trailer.startxref)
63
+ xrefstm if xrefstm.is_a?(XRefStream)
64
+ end
65
+ end
66
+
67
+ def gen_id
68
+ fileInfo = get_trailer_info
69
+ if fileInfo.nil?
70
+ raise InvalidPDFError, "Cannot access trailer information"
71
+ end
72
+
73
+ id = Digest::MD5.hexdigest( rand.to_s )
74
+ fileInfo.ID = [ id, id ]
75
+ end
76
+
77
+ end
78
+
79
+ class InvalidTrailerError < Exception #:nodoc:
80
+ end
81
+
82
+ #
83
+ # Class representing a PDF file Trailer.
84
+ #
85
+ class Trailer
86
+
87
+ include StandardObject
88
+
89
+ TOKENS = %w{ trailer %%EOF } #:nodoc:
90
+ XREF_TOKEN = "startxref" #:nodoc:
91
+
92
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first + WHITESPACES)
93
+ @@regexp_xref = Regexp.new(WHITESPACES + XREF_TOKEN + WHITESPACES + "(\\d+)")
94
+ @@regexp_close = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
95
+
96
+ attr_accessor :pdf
97
+ attr_accessor :startxref
98
+ attr_reader :dictionary
99
+
100
+ field :Size, :Type => Integer, :Required => true
101
+ field :Prev, :Type => Integer
102
+ field :Root, :Type => Dictionary, :Required => true
103
+ field :Encrypt, :Type => Dictionary
104
+ field :Info, :Type => Dictionary
105
+ field :ID, :Type => Array
106
+ field :XRefStm, :Type => Integer
107
+
108
+ #
109
+ # Creates a new Trailer.
110
+ # _startxref_:: The file _offset_ to the XRef::Section.
111
+ # _dictionary_:: A hash of attributes to set in the Trailer Dictionary.
112
+ #
113
+ def initialize(startxref = 0, dictionary = {})
114
+
115
+ @startxref, self.dictionary = startxref, dictionary && Dictionary.new(dictionary)
116
+ end
117
+
118
+ def self.parse(stream) #:nodoc:
119
+
120
+ if stream.skip(@@regexp_open)
121
+ dictionary = Dictionary.parse(stream)
122
+ else
123
+ dictionary = nil
124
+ end
125
+
126
+ if not stream.scan(@@regexp_xref)
127
+ #raise InvalidTrailerError, "Cannot get startxref value"
128
+ end
129
+
130
+ startxref = (stream[3] && stream[3].to_i)
131
+
132
+ if not stream.scan(@@regexp_close)
133
+ #raise InvalidTrailerError, "No %%EOF token found"
134
+ end
135
+
136
+ Trailer.new(startxref, dictionary && dictionary.to_h)
137
+ end
138
+
139
+ def [](key)
140
+ @dictionary[key] if has_dictionary?
141
+ end
142
+
143
+ def []=(key,val)
144
+ @dictionary[key] = val
145
+ end
146
+
147
+ def dictionary=(dict)
148
+ dict.parent = self if dict
149
+ @dictionary = dict
150
+ end
151
+
152
+ def has_dictionary?
153
+ not @dictionary.nil?
154
+ end
155
+
156
+ #
157
+ # Outputs self into PDF code.
158
+ #
159
+ def to_s
160
+
161
+ content = ""
162
+ if self.has_dictionary?
163
+ content << TOKENS.first << EOL << @dictionary.to_s << EOL
164
+ end
165
+
166
+ content << XREF_TOKEN << EOL << @startxref.to_s << EOL << TOKENS.last << EOL
167
+
168
+ content
169
+ end
170
+
171
+ end
172
+
173
+ end