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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/README.md +112 -0
- data/bin/config/pdfcop.conf.yml +232 -233
- data/bin/gui/about.rb +27 -37
- data/bin/gui/config.rb +108 -117
- data/bin/gui/file.rb +416 -365
- data/bin/gui/gtkhex.rb +1138 -1153
- data/bin/gui/hexview.rb +55 -57
- data/bin/gui/imgview.rb +48 -51
- data/bin/gui/menu.rb +388 -386
- data/bin/gui/properties.rb +114 -130
- data/bin/gui/signing.rb +571 -617
- data/bin/gui/textview.rb +77 -95
- data/bin/gui/treeview.rb +382 -387
- data/bin/gui/walker.rb +227 -232
- data/bin/gui/xrefs.rb +56 -60
- data/bin/pdf2pdfa +53 -57
- data/bin/pdf2ruby +212 -228
- data/bin/pdfcop +338 -348
- data/bin/pdfdecompress +58 -65
- data/bin/pdfdecrypt +56 -60
- data/bin/pdfencrypt +75 -80
- data/bin/pdfexplode +185 -182
- data/bin/pdfextract +201 -218
- data/bin/pdfmetadata +83 -82
- data/bin/pdfsh +4 -5
- data/bin/pdfwalker +1 -2
- data/bin/shell/.irbrc +45 -82
- data/bin/shell/console.rb +105 -130
- data/bin/shell/hexdump.rb +40 -64
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
- data/examples/flash/flash.rb +37 -0
- data/{samples → examples}/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami.rb +29 -42
- data/lib/origami/3d.rb +350 -225
- data/lib/origami/acroform.rb +262 -288
- data/lib/origami/actions.rb +268 -288
- data/lib/origami/annotations.rb +697 -722
- data/lib/origami/array.rb +258 -184
- data/lib/origami/boolean.rb +74 -84
- data/lib/origami/catalog.rb +397 -434
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/destinations.rb +233 -194
- data/lib/origami/dictionary.rb +253 -232
- data/lib/origami/encryption.rb +1274 -1243
- data/lib/origami/export.rb +232 -268
- data/lib/origami/extensions/fdf.rb +307 -220
- data/lib/origami/extensions/ppklite.rb +368 -435
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters.rb +301 -295
- data/lib/origami/filters/ascii.rb +177 -180
- data/lib/origami/filters/ccitt.rb +528 -535
- data/lib/origami/filters/crypt.rb +26 -35
- data/lib/origami/filters/dct.rb +46 -52
- data/lib/origami/filters/flate.rb +95 -94
- data/lib/origami/filters/jbig2.rb +49 -55
- data/lib/origami/filters/jpx.rb +38 -44
- data/lib/origami/filters/lzw.rb +189 -183
- data/lib/origami/filters/predictors.rb +221 -235
- data/lib/origami/filters/runlength.rb +103 -104
- data/lib/origami/font.rb +173 -186
- data/lib/origami/functions.rb +67 -81
- data/lib/origami/graphics.rb +25 -21
- data/lib/origami/graphics/colors.rb +178 -187
- data/lib/origami/graphics/instruction.rb +79 -85
- data/lib/origami/graphics/path.rb +142 -148
- data/lib/origami/graphics/patterns.rb +160 -167
- data/lib/origami/graphics/render.rb +43 -50
- data/lib/origami/graphics/state.rb +138 -153
- data/lib/origami/graphics/text.rb +188 -205
- data/lib/origami/graphics/xobject.rb +819 -815
- data/lib/origami/header.rb +63 -78
- data/lib/origami/javascript.rb +596 -597
- data/lib/origami/linearization.rb +285 -290
- data/lib/origami/metadata.rb +139 -148
- data/lib/origami/name.rb +112 -148
- data/lib/origami/null.rb +53 -62
- data/lib/origami/numeric.rb +162 -175
- data/lib/origami/obfuscation.rb +186 -174
- data/lib/origami/object.rb +593 -573
- data/lib/origami/outline.rb +42 -47
- data/lib/origami/outputintents.rb +73 -82
- data/lib/origami/page.rb +703 -592
- data/lib/origami/parser.rb +238 -290
- data/lib/origami/parsers/fdf.rb +41 -33
- data/lib/origami/parsers/pdf.rb +75 -95
- data/lib/origami/parsers/pdf/lazy.rb +137 -0
- data/lib/origami/parsers/pdf/linear.rb +64 -66
- data/lib/origami/parsers/ppklite.rb +34 -70
- data/lib/origami/pdf.rb +1030 -1005
- data/lib/origami/reference.rb +102 -102
- data/lib/origami/signature.rb +591 -609
- data/lib/origami/stream.rb +668 -551
- data/lib/origami/string.rb +397 -373
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +144 -158
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +88 -79
- data/lib/origami/xfa.rb +2863 -2882
- data/lib/origami/xreftable.rb +472 -384
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +82 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +90 -0
- data/test/test_pages.rb +31 -0
- data/test/test_pdf.rb +16 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +95 -0
- data/test/test_pdf_parse.rb +96 -0
- data/test/test_pdf_sign.rb +58 -0
- data/test/test_streams.rb +182 -0
- data/test/test_xrefs.rb +67 -0
- metadata +88 -58
- data/README +0 -67
- data/bin/pdf2graph +0 -121
- data/bin/pdfcocoon +0 -104
- data/lib/origami/file.rb +0 -233
- data/samples/README.txt +0 -45
- data/samples/actions/launch/calc.rb +0 -87
- data/samples/actions/launch/winparams.rb +0 -22
- data/samples/actions/loop/loopgoto.rb +0 -24
- data/samples/actions/loop/loopnamed.rb +0 -21
- data/samples/actions/named/named.rb +0 -31
- data/samples/actions/samba/smbrelay.rb +0 -26
- data/samples/actions/webbug/submitform.js +0 -26
- data/samples/actions/webbug/webbug-browser.rb +0 -68
- data/samples/actions/webbug/webbug-js.rb +0 -67
- data/samples/actions/webbug/webbug-reader.rb +0 -90
- data/samples/attachments/attach.rb +0 -40
- data/samples/attachments/attached.txt +0 -1
- data/samples/crypto/crypto.rb +0 -28
- data/samples/digsig/signed.rb +0 -46
- data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
- data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
- data/samples/exploits/exploit_customdictopen.rb +0 -55
- data/samples/exploits/getannots.rb +0 -69
- data/samples/flash/flash.rb +0 -31
- data/samples/javascript/attached.txt +0 -1
- data/samples/javascript/js.rb +0 -52
- data/templates/patterns.rb +0 -66
- data/templates/widgets.rb +0 -173
- data/templates/xdp.rb +0 -92
- data/test/ts_pdf.rb +0 -50
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
|
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
|
5
|
+
|
|
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.
|
|
10
|
+
|
|
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.
|
|
15
|
+
|
|
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/>.
|
|
18
|
+
|
|
19
|
+
=end
|
|
20
|
+
|
|
21
|
+
module Origami
|
|
22
|
+
|
|
23
|
+
class PDF
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# Attachs an embedded file to the PDF.
|
|
27
|
+
# _path_:: The path to the file to attach.
|
|
28
|
+
# _register_:: Whether the file shall be registered in the name directory.
|
|
29
|
+
# _name_:: The embedded file name of the attachment.
|
|
30
|
+
# _filter_:: The stream filter used to store the file contents.
|
|
31
|
+
#
|
|
32
|
+
def attach_file(path, register: true, name: nil, filter: :FlateDecode)
|
|
33
|
+
|
|
34
|
+
if path.is_a? FileSpec
|
|
35
|
+
filespec = path
|
|
36
|
+
name ||= ''
|
|
37
|
+
else
|
|
38
|
+
if path.respond_to?(:read)
|
|
39
|
+
data = path.read.force_encoding('binary')
|
|
40
|
+
name ||= ''
|
|
41
|
+
else
|
|
42
|
+
data = File.binread(File.expand_path(path))
|
|
43
|
+
name ||= File.basename(path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
fstream = EmbeddedFileStream.new
|
|
47
|
+
fstream.data = data
|
|
48
|
+
|
|
49
|
+
fstream.Filter = filter
|
|
50
|
+
filespec = FileSpec.new(:F => fstream)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
fspec = FileSpec.new.setType(:Filespec).setF(name.dup).setEF(filespec)
|
|
54
|
+
|
|
55
|
+
self.register(
|
|
56
|
+
Names::EMBEDDED_FILES,
|
|
57
|
+
name.dup,
|
|
58
|
+
fspec
|
|
59
|
+
) if register
|
|
60
|
+
|
|
61
|
+
fspec
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Lookup embedded file in the embedded files name directory.
|
|
66
|
+
#
|
|
67
|
+
def get_embedded_file_by_name(name)
|
|
68
|
+
resolve_name Names::EMBEDDED_FILES, name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# Calls block for each named embedded file.
|
|
73
|
+
#
|
|
74
|
+
def each_named_embedded_file(&b)
|
|
75
|
+
each_name(Names::EMBEDDED_FILES, &b)
|
|
76
|
+
end
|
|
77
|
+
alias each_attachment each_named_embedded_file
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
# Class used to convert system-dependent pathes into PDF pathes.
|
|
82
|
+
# PDF path specification offers a single form for representing file pathes over operating systems.
|
|
83
|
+
#
|
|
84
|
+
class Filename
|
|
85
|
+
|
|
86
|
+
class << self
|
|
87
|
+
#
|
|
88
|
+
# Converts UNIX file path into PDF file path.
|
|
89
|
+
#
|
|
90
|
+
def Unix(file)
|
|
91
|
+
LiteralString.new(file)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
# Converts MacOS file path into PDF file path.
|
|
96
|
+
#
|
|
97
|
+
def Mac(file)
|
|
98
|
+
LiteralString.new("/" + file.gsub(":", "/"))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#
|
|
102
|
+
# Converts Windows file path into PDF file path.
|
|
103
|
+
#
|
|
104
|
+
def DOS(file)
|
|
105
|
+
path = ""
|
|
106
|
+
# Absolute vs relative path
|
|
107
|
+
if file.include? ":"
|
|
108
|
+
path << "/"
|
|
109
|
+
file.sub!(":","")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
file.gsub!("\\", "/")
|
|
113
|
+
LiteralString.new(path + file)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#
|
|
119
|
+
# Class representing a file specification.
|
|
120
|
+
# File specifications can be used to reference external files, as well as embedded files and URIs.
|
|
121
|
+
#
|
|
122
|
+
class FileSpec < Dictionary
|
|
123
|
+
include StandardObject
|
|
124
|
+
|
|
125
|
+
field :Type, :Type => Name, :Default => :FileSpec
|
|
126
|
+
field :FS, :Type => Name, :Default => :URL
|
|
127
|
+
field :F, :Type => [ String, Stream ]
|
|
128
|
+
field :UF, :Type => String
|
|
129
|
+
field :DOS, :Type => String
|
|
130
|
+
field :Mac, :Type => String
|
|
131
|
+
field :Unix, :Type => String
|
|
132
|
+
field :ID, :Type => Array
|
|
133
|
+
field :V, :Type => Boolean, :Default => false, :Version => "1.2"
|
|
134
|
+
field :EF, :Type => Dictionary, :Version => "1.3"
|
|
135
|
+
field :RF, :Type => Dictionary, :Version => "1.3"
|
|
136
|
+
field :Desc, :Type => String, :Version => "1.6"
|
|
137
|
+
field :CI, :Type => Dictionary, :Version => "1.7"
|
|
138
|
+
field :Thumb, :Type => Stream, :Version => "1.7", :ExtensionLevel => 3
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
#
|
|
142
|
+
# Class representing a Uniform Resource Locator (URL)
|
|
143
|
+
#
|
|
144
|
+
class URL < FileSpec
|
|
145
|
+
field :Type, :Type => Name, :Default => :URL, :Required => true
|
|
146
|
+
|
|
147
|
+
def initialize(url)
|
|
148
|
+
super(:F => url)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
#
|
|
153
|
+
# A class representing a file outside the current PDF file.
|
|
154
|
+
#
|
|
155
|
+
class ExternalFile < FileSpec
|
|
156
|
+
field :Type, :Type => Name, :Default => :FileSpec #, :Required => true
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Creates a new external file specification.
|
|
160
|
+
# _dos_:: The Windows path to this file.
|
|
161
|
+
# _mac_:: The MacOS path to this file.
|
|
162
|
+
# _unix_:: The UNIX path to this file.
|
|
163
|
+
#
|
|
164
|
+
def initialize(dos, mac: "", unix: "")
|
|
165
|
+
if not mac.empty? or not unix.empty?
|
|
166
|
+
super(:DOS => Filename.DOS(dos), :Mac => Filename.Mac(mac), :Unix => Filename.Unix(unix))
|
|
167
|
+
else
|
|
168
|
+
super(:F => dos)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
# Class representing parameters for a EmbeddedFileStream.
|
|
175
|
+
#
|
|
176
|
+
class EmbeddedFileParameters < Dictionary
|
|
177
|
+
include StandardObject
|
|
178
|
+
|
|
179
|
+
field :Size, :Type => Integer
|
|
180
|
+
field :CreationDate, :Type => String
|
|
181
|
+
field :ModDate, :Type => String
|
|
182
|
+
field :Mac, :Type => Dictionary
|
|
183
|
+
field :Checksum, :Type => String
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
#
|
|
187
|
+
# Class representing the data of an embedded file.
|
|
188
|
+
#
|
|
189
|
+
class EmbeddedFileStream < Stream
|
|
190
|
+
include StandardObject
|
|
191
|
+
|
|
192
|
+
field :Type, :Type => Name, :Default => :EmbeddedFile
|
|
193
|
+
field :Subtype, :Type => Name
|
|
194
|
+
field :Params, :Type => EmbeddedFileParameters
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
end
|
data/lib/origami/filters.rb
CHANGED
|
@@ -1,333 +1,340 @@
|
|
|
1
1
|
=begin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
# Filters are algorithms used to encode data into a PDF Stream.
|
|
30
|
-
#
|
|
31
|
-
module Filter
|
|
32
|
-
|
|
33
|
-
class InvalidFilterDataError < Exception # :nodoc:
|
|
34
|
-
attr_reader :decoded_data
|
|
35
|
-
|
|
36
|
-
def initialize(message, decoded_data = nil)
|
|
37
|
-
super(message)
|
|
38
|
-
|
|
39
|
-
@decoded_data = decoded_data
|
|
40
|
-
end
|
|
41
|
-
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.
|
|
42
10
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
# Class used to forge a String from a stream of bits.
|
|
50
|
-
# Internally used by some filters.
|
|
51
|
-
#
|
|
52
|
-
class BitWriter
|
|
53
|
-
def initialize
|
|
54
|
-
@data = ''
|
|
55
|
-
@last_byte = nil
|
|
56
|
-
@ptr_bit = 0
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
-
# Writes _data_ represented as Fixnum to a _length_ number of bits.
|
|
61
|
-
#
|
|
62
|
-
def write(data, length)
|
|
63
|
-
return BitWriterError, "Invalid data length" unless length > 0 and (1 << length) > data
|
|
64
|
-
|
|
65
|
-
# optimization for aligned byte writing
|
|
66
|
-
if length == 8 and @last_byte.nil? and @ptr_bit == 0
|
|
67
|
-
@data << data.chr
|
|
68
|
-
return self
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
while length > 0
|
|
72
|
-
if length >= 8 - @ptr_bit
|
|
73
|
-
length -= 8 - @ptr_bit
|
|
74
|
-
@last_byte ||= 0
|
|
75
|
-
@last_byte |= (data >> length) & ((1 << (8 - @ptr_bit)) - 1)
|
|
76
|
-
|
|
77
|
-
data &= (1 << length) - 1
|
|
78
|
-
@data << @last_byte.chr
|
|
79
|
-
@last_byte = nil
|
|
80
|
-
@ptr_bit = 0
|
|
81
|
-
else
|
|
82
|
-
@last_byte ||= 0
|
|
83
|
-
@last_byte |= (data & ((1 << length) - 1)) << (8 - @ptr_bit - length)
|
|
84
|
-
@ptr_bit += length
|
|
85
|
-
|
|
86
|
-
if @ptr_bit == 8
|
|
87
|
-
@data << @last_byte.chr
|
|
88
|
-
@last_byte = nil
|
|
89
|
-
@ptr_bit = 0
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
length = 0
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
self
|
|
97
|
-
end
|
|
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.
|
|
98
15
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
#
|
|
102
|
-
def size
|
|
103
|
-
(@data.size << 3) + @ptr_bit
|
|
104
|
-
end
|
|
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/>.
|
|
105
18
|
|
|
106
|
-
|
|
107
|
-
# Finalizes the stream.
|
|
108
|
-
#
|
|
109
|
-
def final
|
|
110
|
-
@data << @last_byte.chr if @last_byte
|
|
111
|
-
@last_byte = nil
|
|
112
|
-
@p = 0
|
|
19
|
+
=end
|
|
113
20
|
|
|
114
|
-
|
|
115
|
-
end
|
|
21
|
+
module Origami
|
|
116
22
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@data.dup
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
class BitReaderError < Exception #:nodoc:
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
#
|
|
129
|
-
# Class used to read a String as a stream of bits.
|
|
130
|
-
# Internally used by some filters.
|
|
131
|
-
#
|
|
132
|
-
class BitReader
|
|
133
|
-
BRUIJIN_TABLE = ::Array.new(32)
|
|
134
|
-
BRUIJIN_TABLE.size.times { |i|
|
|
135
|
-
BRUIJIN_TABLE[((0x77cb531 * (1 << i)) >> 27) & 31] = i
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
def initialize(data)
|
|
139
|
-
@data = data
|
|
140
|
-
reset
|
|
141
|
-
end
|
|
23
|
+
#
|
|
24
|
+
# Filters are algorithms used to encode data into a PDF Stream.
|
|
25
|
+
#
|
|
26
|
+
module Filter
|
|
142
27
|
|
|
143
28
|
#
|
|
144
|
-
#
|
|
29
|
+
# Base class for filter Exceptions.
|
|
145
30
|
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
self
|
|
149
|
-
end
|
|
31
|
+
class Error < Origami::Error
|
|
32
|
+
attr_accessor :input_data, :decoded_data
|
|
150
33
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
#
|
|
154
|
-
def eod?
|
|
155
|
-
@ptr_byte >= @data.size
|
|
156
|
-
end
|
|
34
|
+
def initialize(message, input_data: nil, decoded_data: nil)
|
|
35
|
+
super(message)
|
|
157
36
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
#
|
|
161
|
-
def pos
|
|
162
|
-
(@ptr_byte << 3) + @ptr_bit
|
|
37
|
+
@input_data, @decoded_data = input_data, decoded_data
|
|
38
|
+
end
|
|
163
39
|
end
|
|
164
40
|
|
|
165
41
|
#
|
|
166
|
-
#
|
|
42
|
+
# Exception class for unsupported filters or unsupported filter parameters.
|
|
167
43
|
#
|
|
168
|
-
|
|
169
|
-
@data.size << 3
|
|
170
|
-
end
|
|
44
|
+
class NotImplementedError < Error; end
|
|
171
45
|
|
|
172
46
|
#
|
|
173
|
-
#
|
|
47
|
+
# Exception class for errors occuring during decode operations.
|
|
174
48
|
#
|
|
175
|
-
|
|
176
|
-
raise BitReaderError, "Pointer position out of data" if bits > self.size
|
|
177
|
-
|
|
178
|
-
pbyte = bits >> 3
|
|
179
|
-
pbit = bits - (pbyte << 3)
|
|
180
|
-
@ptr_byte, @ptr_bit = pbyte, pbit
|
|
181
|
-
|
|
182
|
-
bits
|
|
183
|
-
end
|
|
49
|
+
class DecodeError < Error; end
|
|
184
50
|
|
|
185
|
-
|
|
186
|
-
# Reads _length_ bits as a Fixnum and advances read pointer.
|
|
187
|
-
#
|
|
188
|
-
def read(length)
|
|
189
|
-
n = self.peek(length)
|
|
190
|
-
self.pos += length
|
|
51
|
+
module Utils
|
|
191
52
|
|
|
192
|
-
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
#
|
|
196
|
-
# Reads _length_ bits as a Fixnum. Does not advance read pointer.
|
|
197
|
-
#
|
|
198
|
-
def peek(length)
|
|
199
|
-
return BitReaderError, "Invalid read length" unless length > 0
|
|
200
|
-
return BitReaderError, "Insufficient data" if self.pos + length > self.size
|
|
201
|
-
|
|
202
|
-
n = 0
|
|
203
|
-
ptr_byte, ptr_bit = @ptr_byte, @ptr_bit
|
|
204
|
-
|
|
205
|
-
while length > 0
|
|
206
|
-
byte = @data[ptr_byte].ord
|
|
207
|
-
|
|
208
|
-
if length > 8 - ptr_bit
|
|
209
|
-
length -= 8 - ptr_bit
|
|
210
|
-
n |= ( byte & ((1 << (8 - ptr_bit)) - 1) ) << length
|
|
211
|
-
|
|
212
|
-
ptr_byte += 1
|
|
213
|
-
ptr_bit = 0
|
|
214
|
-
else
|
|
215
|
-
n |= (byte >> (8 - ptr_bit - length)) & ((1 << length) - 1)
|
|
216
|
-
length = 0
|
|
53
|
+
class BitWriterError < Error #:nodoc:
|
|
217
54
|
end
|
|
218
|
-
end
|
|
219
55
|
|
|
220
|
-
|
|
221
|
-
|
|
56
|
+
#
|
|
57
|
+
# Class used to forge a String from a stream of bits.
|
|
58
|
+
# Internally used by some filters.
|
|
59
|
+
#
|
|
60
|
+
class BitWriter
|
|
61
|
+
def initialize
|
|
62
|
+
@data = ''
|
|
63
|
+
@last_byte = nil
|
|
64
|
+
@ptr_bit = 0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#
|
|
68
|
+
# Writes _data_ represented as Fixnum to a _length_ number of bits.
|
|
69
|
+
#
|
|
70
|
+
def write(data, length)
|
|
71
|
+
return BitWriterError, "Invalid data length" unless length > 0 and (1 << length) > data
|
|
72
|
+
|
|
73
|
+
# optimization for aligned byte writing
|
|
74
|
+
if length == 8 and @last_byte.nil? and @ptr_bit == 0
|
|
75
|
+
@data << data.chr
|
|
76
|
+
return self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
while length > 0
|
|
80
|
+
if length >= 8 - @ptr_bit
|
|
81
|
+
length -= 8 - @ptr_bit
|
|
82
|
+
@last_byte ||= 0
|
|
83
|
+
@last_byte |= (data >> length) & ((1 << (8 - @ptr_bit)) - 1)
|
|
84
|
+
|
|
85
|
+
data &= (1 << length) - 1
|
|
86
|
+
@data << @last_byte.chr
|
|
87
|
+
@last_byte = nil
|
|
88
|
+
@ptr_bit = 0
|
|
89
|
+
else
|
|
90
|
+
@last_byte ||= 0
|
|
91
|
+
@last_byte |= (data & ((1 << length) - 1)) << (8 - @ptr_bit - length)
|
|
92
|
+
@ptr_bit += length
|
|
93
|
+
|
|
94
|
+
if @ptr_bit == 8
|
|
95
|
+
@data << @last_byte.chr
|
|
96
|
+
@last_byte = nil
|
|
97
|
+
@ptr_bit = 0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
length = 0
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
#
|
|
108
|
+
# Returns the data size in bits.
|
|
109
|
+
#
|
|
110
|
+
def size
|
|
111
|
+
(@data.size << 3) + @ptr_bit
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
#
|
|
115
|
+
# Finalizes the stream.
|
|
116
|
+
#
|
|
117
|
+
def final
|
|
118
|
+
@data << @last_byte.chr if @last_byte
|
|
119
|
+
@last_byte = nil
|
|
120
|
+
@p = 0
|
|
121
|
+
|
|
122
|
+
self
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
#
|
|
126
|
+
# Outputs the stream as a String.
|
|
127
|
+
#
|
|
128
|
+
def to_s
|
|
129
|
+
@data.dup
|
|
130
|
+
end
|
|
131
|
+
end
|
|
222
132
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# Counts leading zeros. Does not advance read pointer.
|
|
226
|
-
#
|
|
227
|
-
def clz
|
|
228
|
-
count = 0
|
|
229
|
-
if @ptr_bit != 0
|
|
230
|
-
bits = peek(8 - @ptr_bit)
|
|
231
|
-
count = clz32(bits << (32 - (8 - @ptr_bit)))
|
|
232
|
-
|
|
233
|
-
return count if count < (8 - @ptr_bit)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
delta = 0
|
|
237
|
-
while @data.size > @ptr_byte + delta * 4
|
|
238
|
-
word = @data[@ptr_byte + delta * 4, 4] # next 32 bits
|
|
239
|
-
z = clz32((word << (4 - word.size)).unpack("N")[0])
|
|
240
|
-
|
|
241
|
-
count += z
|
|
242
|
-
delta += 1
|
|
243
|
-
|
|
244
|
-
return count if z < 32 - ((4 - word.size) << 3)
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
count
|
|
248
|
-
end
|
|
133
|
+
class BitReaderError < Error #:nodoc:
|
|
134
|
+
end
|
|
249
135
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
136
|
+
#
|
|
137
|
+
# Class used to read a String as a stream of bits.
|
|
138
|
+
# Internally used by some filters.
|
|
139
|
+
#
|
|
140
|
+
class BitReader
|
|
141
|
+
BRUIJIN_TABLE = ::Array.new(32)
|
|
142
|
+
BRUIJIN_TABLE.size.times do |i|
|
|
143
|
+
BRUIJIN_TABLE[((0x77cb531 * (1 << i)) >> 27) & 31] = i
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def initialize(data)
|
|
147
|
+
@data = data
|
|
148
|
+
reset
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
#
|
|
152
|
+
# Resets the read pointer.
|
|
153
|
+
#
|
|
154
|
+
def reset
|
|
155
|
+
@ptr_byte, @ptr_bit = 0, 0
|
|
156
|
+
self
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
#
|
|
160
|
+
# Returns true if end of data has been reached.
|
|
161
|
+
#
|
|
162
|
+
def eod?
|
|
163
|
+
@ptr_byte >= @data.size
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# Returns the read pointer position in bits.
|
|
168
|
+
#
|
|
169
|
+
def pos
|
|
170
|
+
(@ptr_byte << 3) + @ptr_bit
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
# Returns the data size in bits.
|
|
175
|
+
#
|
|
176
|
+
def size
|
|
177
|
+
@data.size << 3
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
#
|
|
181
|
+
# Sets the read pointer position in bits.
|
|
182
|
+
#
|
|
183
|
+
def pos=(bits)
|
|
184
|
+
raise BitReaderError, "Pointer position out of data" if bits > self.size
|
|
185
|
+
|
|
186
|
+
pbyte = bits >> 3
|
|
187
|
+
pbit = bits - (pbyte << 3)
|
|
188
|
+
@ptr_byte, @ptr_bit = pbyte, pbit
|
|
189
|
+
|
|
190
|
+
bits
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#
|
|
194
|
+
# Reads _length_ bits as a Fixnum and advances read pointer.
|
|
195
|
+
#
|
|
196
|
+
def read(length)
|
|
197
|
+
n = self.peek(length)
|
|
198
|
+
self.pos += length
|
|
199
|
+
|
|
200
|
+
n
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
#
|
|
204
|
+
# Reads _length_ bits as a Fixnum. Does not advance read pointer.
|
|
205
|
+
#
|
|
206
|
+
def peek(length)
|
|
207
|
+
return BitReaderError, "Invalid read length" unless length > 0
|
|
208
|
+
return BitReaderError, "Insufficient data" if self.pos + length > self.size
|
|
209
|
+
|
|
210
|
+
n = 0
|
|
211
|
+
ptr_byte, ptr_bit = @ptr_byte, @ptr_bit
|
|
212
|
+
|
|
213
|
+
while length > 0
|
|
214
|
+
byte = @data[ptr_byte].ord
|
|
215
|
+
|
|
216
|
+
if length > 8 - ptr_bit
|
|
217
|
+
length -= 8 - ptr_bit
|
|
218
|
+
n |= ( byte & ((1 << (8 - ptr_bit)) - 1) ) << length
|
|
219
|
+
|
|
220
|
+
ptr_byte += 1
|
|
221
|
+
ptr_bit = 0
|
|
222
|
+
else
|
|
223
|
+
n |= (byte >> (8 - ptr_bit - length)) & ((1 << length) - 1)
|
|
224
|
+
length = 0
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
n
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
#
|
|
232
|
+
# Used for bit scanning.
|
|
233
|
+
# Counts leading zeros. Does not advance read pointer.
|
|
234
|
+
#
|
|
235
|
+
def clz
|
|
236
|
+
count = 0
|
|
237
|
+
if @ptr_bit != 0
|
|
238
|
+
bits = peek(8 - @ptr_bit)
|
|
239
|
+
count = clz32(bits << (32 - (8 - @ptr_bit)))
|
|
240
|
+
|
|
241
|
+
return count if count < (8 - @ptr_bit)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
delta = 0
|
|
245
|
+
while @data.size > @ptr_byte + delta * 4
|
|
246
|
+
word = @data[@ptr_byte + delta * 4, 4] # next 32 bits
|
|
247
|
+
z = clz32((word << (4 - word.size)).unpack("N")[0])
|
|
248
|
+
|
|
249
|
+
count += z
|
|
250
|
+
delta += 1
|
|
251
|
+
|
|
252
|
+
return count if z < 32 - ((4 - word.size) << 3)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
count
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
#
|
|
259
|
+
# Used for bit scanning.
|
|
260
|
+
# Count leading ones. Does not advance read pointer.
|
|
261
|
+
#
|
|
262
|
+
def clo
|
|
263
|
+
count = 0
|
|
264
|
+
if @ptr_bit != 0
|
|
265
|
+
bits = peek(8 - @ptr_bit)
|
|
266
|
+
count = clz32(~(bits << (32 - (8 - @ptr_bit))) & 0xff)
|
|
267
|
+
|
|
268
|
+
return count if count < (8 - @ptr_bit)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
delta = 0
|
|
272
|
+
while @data.size > @ptr_byte + delta * 4
|
|
273
|
+
word = @data[@ptr_byte + delta * 4, 4] # next 32 bits
|
|
274
|
+
z = clz32(~((word << (4 - word.size)).unpack("N")[0]) & 0xffff_ffff)
|
|
275
|
+
|
|
276
|
+
count += z
|
|
277
|
+
delta += 1
|
|
278
|
+
|
|
279
|
+
return count if z < 32 - ((4 - word.size) << 3)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
count
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
private
|
|
286
|
+
|
|
287
|
+
def bitswap8(i) #:nodoc
|
|
288
|
+
((i * 0x0202020202) & 0x010884422010) % 1023
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def bitswap32(i) #:nodoc:
|
|
292
|
+
(bitswap8((i >> 0) & 0xff) << 24) |
|
|
293
|
+
(bitswap8((i >> 8) & 0xff) << 16) |
|
|
294
|
+
(bitswap8((i >> 16) & 0xff) << 8) |
|
|
295
|
+
(bitswap8((i >> 24) & 0xff) << 0)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def ctz32(i) #:nodoc:
|
|
299
|
+
if i == 0 then 32
|
|
300
|
+
else
|
|
301
|
+
BRUIJIN_TABLE[(((i & -i) * 0x77cb531) >> 27) & 31]
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def clz32(i) #:nodoc:
|
|
306
|
+
ctz32 bitswap32 i
|
|
307
|
+
end
|
|
308
|
+
end
|
|
275
309
|
end
|
|
276
310
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
311
|
+
module ClassMethods
|
|
312
|
+
#
|
|
313
|
+
# Decodes the given data.
|
|
314
|
+
# _stream_:: The data to decode.
|
|
315
|
+
#
|
|
316
|
+
def decode(stream, params = {})
|
|
317
|
+
self.new(params).decode(stream)
|
|
318
|
+
end
|
|
282
319
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
320
|
+
#
|
|
321
|
+
# Encodes the given data.
|
|
322
|
+
# _stream_:: The data to encode.
|
|
323
|
+
#
|
|
324
|
+
def encode(stream, params = {})
|
|
325
|
+
self.new(params).encode(stream)
|
|
326
|
+
end
|
|
288
327
|
end
|
|
289
328
|
|
|
290
|
-
def
|
|
291
|
-
|
|
292
|
-
else
|
|
293
|
-
BRUIJIN_TABLE[(((i & -i) * 0x77cb531) >> 27) & 31]
|
|
294
|
-
end
|
|
329
|
+
def initialize(parameters = {})
|
|
330
|
+
@params = parameters
|
|
295
331
|
end
|
|
296
332
|
|
|
297
|
-
def
|
|
298
|
-
|
|
333
|
+
def self.included(receiver)
|
|
334
|
+
receiver.extend(ClassMethods)
|
|
299
335
|
end
|
|
300
|
-
end
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
module ClassMethods
|
|
304
|
-
#
|
|
305
|
-
# Decodes the given data.
|
|
306
|
-
# _stream_:: The data to decode.
|
|
307
|
-
#
|
|
308
|
-
def decode(stream, params = {})
|
|
309
|
-
self.new(params).decode(stream)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
#
|
|
313
|
-
# Encodes the given data.
|
|
314
|
-
# _stream_:: The data to encode.
|
|
315
|
-
#
|
|
316
|
-
def encode(stream, params = {})
|
|
317
|
-
self.new(params).encode(stream)
|
|
318
|
-
end
|
|
319
336
|
end
|
|
320
337
|
|
|
321
|
-
def initialize(parameters = {})
|
|
322
|
-
@params = parameters
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
def self.included(receiver)
|
|
326
|
-
receiver.extend(ClassMethods)
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
end
|
|
330
|
-
|
|
331
338
|
end
|
|
332
339
|
|
|
333
340
|
require 'origami/filters/ascii'
|
|
@@ -339,4 +346,3 @@ require 'origami/filters/dct'
|
|
|
339
346
|
require 'origami/filters/jbig2'
|
|
340
347
|
require 'origami/filters/jpx'
|
|
341
348
|
require 'origami/filters/crypt'
|
|
342
|
-
|