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.
- data/COPYING.LESSER +165 -0
- data/README +77 -0
- data/VERSION +1 -0
- data/bin/config/pdfcop.conf.yml +237 -0
- data/bin/gui/about.rb +46 -0
- data/bin/gui/config.rb +132 -0
- data/bin/gui/file.rb +385 -0
- data/bin/gui/hexdump.rb +74 -0
- data/bin/gui/hexview.rb +91 -0
- data/bin/gui/imgview.rb +72 -0
- data/bin/gui/menu.rb +392 -0
- data/bin/gui/properties.rb +132 -0
- data/bin/gui/signing.rb +635 -0
- data/bin/gui/textview.rb +107 -0
- data/bin/gui/treeview.rb +409 -0
- data/bin/gui/walker.rb +282 -0
- data/bin/gui/xrefs.rb +79 -0
- data/bin/pdf2graph +121 -0
- data/bin/pdf2ruby +353 -0
- data/bin/pdfcocoon +104 -0
- data/bin/pdfcop +455 -0
- data/bin/pdfdecompress +104 -0
- data/bin/pdfdecrypt +95 -0
- data/bin/pdfencrypt +112 -0
- data/bin/pdfextract +221 -0
- data/bin/pdfmetadata +123 -0
- data/bin/pdfsh +13 -0
- data/bin/pdfwalker +7 -0
- data/bin/shell/.irbrc +104 -0
- data/bin/shell/console.rb +136 -0
- data/bin/shell/hexdump.rb +83 -0
- data/origami.rb +36 -0
- data/origami/3d.rb +239 -0
- data/origami/acroform.rb +321 -0
- data/origami/actions.rb +299 -0
- data/origami/adobe/fdf.rb +259 -0
- data/origami/adobe/ppklite.rb +489 -0
- data/origami/annotations.rb +775 -0
- data/origami/array.rb +187 -0
- data/origami/boolean.rb +101 -0
- data/origami/catalog.rb +486 -0
- data/origami/destinations.rb +213 -0
- data/origami/dictionary.rb +188 -0
- data/origami/docmdp.rb +96 -0
- data/origami/encryption.rb +1293 -0
- data/origami/export.rb +283 -0
- data/origami/file.rb +222 -0
- data/origami/filters.rb +250 -0
- data/origami/filters/ascii.rb +189 -0
- data/origami/filters/ccitt.rb +515 -0
- data/origami/filters/crypt.rb +47 -0
- data/origami/filters/dct.rb +61 -0
- data/origami/filters/flate.rb +112 -0
- data/origami/filters/jbig2.rb +63 -0
- data/origami/filters/jpx.rb +53 -0
- data/origami/filters/lzw.rb +195 -0
- data/origami/filters/predictors.rb +276 -0
- data/origami/filters/runlength.rb +117 -0
- data/origami/font.rb +209 -0
- data/origami/functions.rb +93 -0
- data/origami/graphics.rb +33 -0
- data/origami/graphics/colors.rb +191 -0
- data/origami/graphics/instruction.rb +126 -0
- data/origami/graphics/path.rb +154 -0
- data/origami/graphics/patterns.rb +180 -0
- data/origami/graphics/state.rb +164 -0
- data/origami/graphics/text.rb +224 -0
- data/origami/graphics/xobject.rb +493 -0
- data/origami/header.rb +90 -0
- data/origami/linearization.rb +318 -0
- data/origami/metadata.rb +114 -0
- data/origami/name.rb +170 -0
- data/origami/null.rb +75 -0
- data/origami/numeric.rb +188 -0
- data/origami/obfuscation.rb +233 -0
- data/origami/object.rb +527 -0
- data/origami/outline.rb +59 -0
- data/origami/page.rb +559 -0
- data/origami/parser.rb +268 -0
- data/origami/parsers/fdf.rb +45 -0
- data/origami/parsers/pdf.rb +27 -0
- data/origami/parsers/pdf/linear.rb +113 -0
- data/origami/parsers/ppklite.rb +86 -0
- data/origami/pdf.rb +1144 -0
- data/origami/reference.rb +113 -0
- data/origami/signature.rb +474 -0
- data/origami/stream.rb +575 -0
- data/origami/string.rb +416 -0
- data/origami/trailer.rb +173 -0
- data/origami/webcapture.rb +87 -0
- data/origami/xfa.rb +3027 -0
- data/origami/xreftable.rb +447 -0
- data/templates/patterns.rb +66 -0
- data/templates/widgets.rb +173 -0
- data/templates/xdp.rb +92 -0
- data/tests/dataset/test.dummycrt +28 -0
- data/tests/dataset/test.dummykey +27 -0
- data/tests/tc_actions.rb +32 -0
- data/tests/tc_annotations.rb +85 -0
- data/tests/tc_pages.rb +37 -0
- data/tests/tc_pdfattach.rb +24 -0
- data/tests/tc_pdfencrypt.rb +110 -0
- data/tests/tc_pdfnew.rb +32 -0
- data/tests/tc_pdfparse.rb +98 -0
- data/tests/tc_pdfsig.rb +37 -0
- data/tests/tc_streams.rb +129 -0
- data/tests/ts_pdf.rb +45 -0
- metadata +193 -0
data/origami/string.rb
ADDED
@@ -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
|
data/origami/trailer.rb
ADDED
@@ -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
|