origami 1.0.2

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