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/filters.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
filters.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume Delugr� <guillaume@security-labs.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/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
#
|
29
|
+
# Filters are algorithms used to encode data into a PDF Stream.
|
30
|
+
#
|
31
|
+
module Filter
|
32
|
+
|
33
|
+
module Utils
|
34
|
+
|
35
|
+
class BitWriterError < Exception #:nodoc:
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Class used to forge a String from a stream of bits.
|
40
|
+
# Internally used by some filters.
|
41
|
+
#
|
42
|
+
class BitWriter
|
43
|
+
def initialize
|
44
|
+
@data = ''
|
45
|
+
@last_byte = nil
|
46
|
+
@ptr_bit = 0
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Writes _data_ represented as Fixnum to a _length_ number of bits.
|
51
|
+
#
|
52
|
+
def write(data, length)
|
53
|
+
return BitWriterError, "Invalid data length" unless length > 0 and (1 << length) > data
|
54
|
+
|
55
|
+
# optimization for aligned byte writing
|
56
|
+
if length == 8 and @last_byte.nil? and @ptr_bit == 0
|
57
|
+
@data << data.chr
|
58
|
+
return self
|
59
|
+
end
|
60
|
+
|
61
|
+
while length > 0
|
62
|
+
if length >= 8 - @ptr_bit
|
63
|
+
length -= 8 - @ptr_bit
|
64
|
+
@last_byte = 0 unless @last_byte
|
65
|
+
@last_byte |= (data >> length) & ((1 << (8 - @ptr_bit)) - 1)
|
66
|
+
|
67
|
+
data &= (1 << length) - 1
|
68
|
+
@data << @last_byte.chr
|
69
|
+
@last_byte = nil
|
70
|
+
@ptr_bit = 0
|
71
|
+
else
|
72
|
+
@last_byte = 0 unless @last_byte
|
73
|
+
@last_byte |= (data & ((1 << length) - 1)) << (8 - @ptr_bit - length)
|
74
|
+
@ptr_bit += length
|
75
|
+
|
76
|
+
if @ptr_bit == 8
|
77
|
+
@data << @last_byte.chr
|
78
|
+
@last_byte = nil
|
79
|
+
@ptr_bit = 0
|
80
|
+
end
|
81
|
+
|
82
|
+
length = 0
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Returns the data size in bits.
|
91
|
+
#
|
92
|
+
def size
|
93
|
+
(@data.size << 3) + @ptr_bit
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Finalizes the stream.
|
98
|
+
#
|
99
|
+
def final
|
100
|
+
@data << @last_byte.chr if @last_byte
|
101
|
+
@last_byte = nil
|
102
|
+
@p = 0
|
103
|
+
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Outputs the stream as a String.
|
109
|
+
#
|
110
|
+
def to_s
|
111
|
+
@data.dup
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class BitReaderError < Exception #:nodoc:
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Class used to read a String as a stream of bits.
|
120
|
+
# Internally used by some filters.
|
121
|
+
#
|
122
|
+
class BitReader
|
123
|
+
def initialize(data)
|
124
|
+
@data = data
|
125
|
+
reset
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Resets the read pointer.
|
130
|
+
#
|
131
|
+
def reset
|
132
|
+
@ptr_byte, @ptr_bit = 0, 0
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Returns true if end of data has been reached.
|
138
|
+
#
|
139
|
+
def eod?
|
140
|
+
@ptr_byte >= @data.size
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Returns the read pointer position in bits.
|
145
|
+
#
|
146
|
+
def pos
|
147
|
+
(@ptr_byte << 3) + @ptr_bit
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Returns the data size in bits.
|
152
|
+
#
|
153
|
+
def size
|
154
|
+
@data.size << 3
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# Sets the read pointer position in bits.
|
159
|
+
#
|
160
|
+
def pos=(bits)
|
161
|
+
raise BitReaderError, "Pointer position out of data" if bits > self.size
|
162
|
+
|
163
|
+
pbyte = bits >> 3
|
164
|
+
pbit = bits - (pbyte << 3)
|
165
|
+
@ptr_byte, @ptr_bit = pbyte, pbit
|
166
|
+
|
167
|
+
bits
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Reads _length_ bits as a Fixnum and advances read pointer.
|
172
|
+
#
|
173
|
+
def read(length)
|
174
|
+
n = self.peek(length)
|
175
|
+
self.pos += length
|
176
|
+
|
177
|
+
n
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Reads _length_ bits as a Fixnum. Does not advance read pointer.
|
182
|
+
#
|
183
|
+
def peek(length)
|
184
|
+
return BitReaderError, "Invalid read length" unless length > 0
|
185
|
+
return BitReaderError, "Insufficient data" if self.pos + length > self.size
|
186
|
+
|
187
|
+
n = 0
|
188
|
+
ptr_byte, ptr_bit = @ptr_byte, @ptr_bit
|
189
|
+
|
190
|
+
while length > 0
|
191
|
+
byte = @data[ptr_byte].ord
|
192
|
+
|
193
|
+
if length > 8 - ptr_bit
|
194
|
+
length -= 8 - ptr_bit
|
195
|
+
n |= ( byte & ((1 << (8 - ptr_bit)) - 1) ) << length
|
196
|
+
|
197
|
+
ptr_byte += 1
|
198
|
+
ptr_bit = 0
|
199
|
+
else
|
200
|
+
n |= (byte >> (8 - ptr_bit - length)) & ((1 << length) - 1)
|
201
|
+
length = 0
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
n
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
module ClassMethods
|
212
|
+
#
|
213
|
+
# Decodes the given data.
|
214
|
+
# _stream_:: The data to decode.
|
215
|
+
#
|
216
|
+
def decode(stream, params = {})
|
217
|
+
self.new(params).decode(stream)
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# Encodes the given data.
|
222
|
+
# _stream_:: The data to encode.
|
223
|
+
#
|
224
|
+
def encode(stream, params = {})
|
225
|
+
self.new(params).encode(stream)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def initialize(parameters = {})
|
230
|
+
@params = parameters
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.included(receiver)
|
234
|
+
receiver.extend(ClassMethods)
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
require 'origami/filters/ascii'
|
242
|
+
require 'origami/filters/lzw'
|
243
|
+
require 'origami/filters/flate'
|
244
|
+
require 'origami/filters/runlength'
|
245
|
+
require 'origami/filters/ccitt'
|
246
|
+
require 'origami/filters/dct'
|
247
|
+
require 'origami/filters/jbig2'
|
248
|
+
require 'origami/filters/jpx'
|
249
|
+
require 'origami/filters/crypt'
|
250
|
+
|
@@ -0,0 +1,189 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
filters/ascii.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume Delugré <guillaume@security-labs.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/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
module Filter
|
29
|
+
|
30
|
+
class InvalidASCIIHexStringError < Exception #:nodoc:
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Class representing a filter used to encode and decode data written into hexadecimal.
|
35
|
+
#
|
36
|
+
class ASCIIHex
|
37
|
+
include Filter
|
38
|
+
|
39
|
+
EOD = ">" #:nodoc:
|
40
|
+
|
41
|
+
#
|
42
|
+
# Encodes given data into upcase hexadecimal representation.
|
43
|
+
# _stream_:: The data to encode.
|
44
|
+
#
|
45
|
+
def encode(stream)
|
46
|
+
stream.unpack("H2" * stream.size).join.upcase
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Decodes given data writen into upcase hexadecimal representation.
|
51
|
+
# _string_:: The data to decode.
|
52
|
+
#
|
53
|
+
def decode(string)
|
54
|
+
|
55
|
+
input = string.include?(?>) ? string[0..string.index(?>) - 1] : string
|
56
|
+
digits = input.delete(" \f\t\r\n\0").split(/(..)/).delete_if{|digit| digit.empty?}
|
57
|
+
|
58
|
+
if not digits.all? { |d| d =~ /[a-fA-F0-9]{1,2}/ }
|
59
|
+
raise InvalidASCIIHexStringError, input
|
60
|
+
end
|
61
|
+
|
62
|
+
digits.pack("H2" * digits.size)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
class InvalidASCII85StringError < Exception #:nodoc:
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Class representing a filter used to encode and decode data written in base85 encoding.
|
72
|
+
#
|
73
|
+
class ASCII85
|
74
|
+
include Filter
|
75
|
+
|
76
|
+
EOD = "~>" #:nodoc:
|
77
|
+
|
78
|
+
#
|
79
|
+
# Encodes given data into base85.
|
80
|
+
# _stream_:: The data to encode.
|
81
|
+
#
|
82
|
+
def encode(stream)
|
83
|
+
|
84
|
+
i = 0
|
85
|
+
code = ""
|
86
|
+
input = stream.dup
|
87
|
+
|
88
|
+
while i < input.size do
|
89
|
+
|
90
|
+
if input.length - i < 4
|
91
|
+
addend = 4 - (input.length - i)
|
92
|
+
input << "\0" * addend
|
93
|
+
else
|
94
|
+
addend = 0
|
95
|
+
end
|
96
|
+
|
97
|
+
inblock = (input[i].ord * 256**3 + input[i+1].ord * 256**2 + input[i+2].ord * 256 + input[i+3].ord)
|
98
|
+
outblock = ""
|
99
|
+
|
100
|
+
5.times do |p|
|
101
|
+
c = inblock / 85 ** (4 - p)
|
102
|
+
outblock << ("!"[0].ord + c).chr
|
103
|
+
|
104
|
+
inblock -= c * 85 ** (4 - p)
|
105
|
+
end
|
106
|
+
|
107
|
+
outblock = "z" if outblock == "!!!!!" and addend == 0
|
108
|
+
|
109
|
+
if addend != 0
|
110
|
+
outblock = outblock[0,(4 - addend) + 1]
|
111
|
+
end
|
112
|
+
|
113
|
+
code << outblock
|
114
|
+
|
115
|
+
i = i + 4
|
116
|
+
end
|
117
|
+
|
118
|
+
code
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Decodes the given data encoded in base85.
|
123
|
+
# _string_:: The data to decode.
|
124
|
+
#
|
125
|
+
def decode(string)
|
126
|
+
|
127
|
+
input = (string.include?(EOD) ? string[0..string.index(EOD) - 1] : string).delete(" \f\t\r\n\0")
|
128
|
+
|
129
|
+
i = 0
|
130
|
+
result = ""
|
131
|
+
while i < input.size do
|
132
|
+
|
133
|
+
outblock = ""
|
134
|
+
|
135
|
+
if input[i].ord == "z"[0].ord
|
136
|
+
inblock = 0
|
137
|
+
codelen = 1
|
138
|
+
else
|
139
|
+
|
140
|
+
inblock = 0
|
141
|
+
codelen = 5
|
142
|
+
|
143
|
+
if input.length - i < 5
|
144
|
+
raise InvalidASCII85StringError, "Invalid length" if input.length - i == 1
|
145
|
+
|
146
|
+
addend = 5 - (input.length - i)
|
147
|
+
input << "u" * addend
|
148
|
+
else
|
149
|
+
addend = 0
|
150
|
+
end
|
151
|
+
|
152
|
+
# Checking if this string is in base85
|
153
|
+
5.times do |j|
|
154
|
+
if input[i+j].ord > "u"[0].ord or input[i+j].ord < "!"[0].ord
|
155
|
+
raise InvalidASCII85StringError, "Invalid character sequence: #{input[i,5].inspect}"
|
156
|
+
else
|
157
|
+
inblock += (input[i+j].ord - "!"[0].ord) * 85 ** (4 - j)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if inblock >= 2**32
|
162
|
+
raise InvalidASCII85StringError, "Invalid value (#{inblock}) for block #{input[i,5].inspect}"
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
4.times do |p|
|
168
|
+
c = inblock / 256 ** (3 - p)
|
169
|
+
outblock << c.chr
|
170
|
+
|
171
|
+
inblock -= c * 256 ** (3 - p)
|
172
|
+
end
|
173
|
+
|
174
|
+
if addend != 0
|
175
|
+
outblock = outblock[0, 4 - addend]
|
176
|
+
end
|
177
|
+
|
178
|
+
result << outblock
|
179
|
+
|
180
|
+
i = i + codelen
|
181
|
+
end
|
182
|
+
|
183
|
+
result
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
@@ -0,0 +1,515 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
filters/ccitt.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume Delugré <guillaume@security-labs.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/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
module Filter
|
29
|
+
|
30
|
+
class InvalidCCITTFaxDataError < Exception #:nodoc:
|
31
|
+
end
|
32
|
+
|
33
|
+
class CCITTFaxFilterError < Exception #:nodoc:
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Class representing a Filter used to encode and decode data with CCITT-facsimile compression algorithm.
|
38
|
+
#
|
39
|
+
class CCITTFax
|
40
|
+
include Filter
|
41
|
+
|
42
|
+
class DecodeParms < Dictionary
|
43
|
+
include StandardObject
|
44
|
+
|
45
|
+
field :K, :Type => Integer, :Default => 0
|
46
|
+
field :EndOfLine, :Type => Boolean, :Default => false
|
47
|
+
field :EncodedByteAlign, :Type => Boolean, :Default => false
|
48
|
+
field :Columns, :Type => Integer, :Default => 1728
|
49
|
+
field :Rows, :Type => Integer, :Default => 0
|
50
|
+
field :EndOfBlock, :Type => Boolean, :Default => true
|
51
|
+
field :BlackIs1, :Type => Boolean, :Default => false
|
52
|
+
field :DamagedRowsBeforeError, :Type => :Integer, :Default => 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.codeword(str) #:nodoc:
|
56
|
+
[ str.to_i(2), str.length ]
|
57
|
+
end
|
58
|
+
|
59
|
+
EOL = codeword('000000000001')
|
60
|
+
RTC = codeword('000000000001' * 6)
|
61
|
+
|
62
|
+
WHITE_TERMINAL_ENCODE_TABLE =
|
63
|
+
{
|
64
|
+
0 => codeword('00110101'),
|
65
|
+
1 => codeword('000111'),
|
66
|
+
2 => codeword('0111'),
|
67
|
+
3 => codeword('1000'),
|
68
|
+
4 => codeword('1011'),
|
69
|
+
5 => codeword('1100'),
|
70
|
+
6 => codeword('1110'),
|
71
|
+
7 => codeword('1111'),
|
72
|
+
8 => codeword('10011'),
|
73
|
+
9 => codeword('10100'),
|
74
|
+
10 => codeword('00111'),
|
75
|
+
11 => codeword('01000'),
|
76
|
+
12 => codeword('001000'),
|
77
|
+
13 => codeword('000011'),
|
78
|
+
14 => codeword('110100'),
|
79
|
+
15 => codeword('110101'),
|
80
|
+
16 => codeword('101010'),
|
81
|
+
17 => codeword('101011'),
|
82
|
+
18 => codeword('0100111'),
|
83
|
+
19 => codeword('0001100'),
|
84
|
+
20 => codeword('0001000'),
|
85
|
+
21 => codeword('0010111'),
|
86
|
+
22 => codeword('0000011'),
|
87
|
+
23 => codeword('0000100'),
|
88
|
+
24 => codeword('0101000'),
|
89
|
+
25 => codeword('0101011'),
|
90
|
+
26 => codeword('0010011'),
|
91
|
+
27 => codeword('0100100'),
|
92
|
+
28 => codeword('0011000'),
|
93
|
+
29 => codeword('00000010'),
|
94
|
+
30 => codeword('00000011'),
|
95
|
+
31 => codeword('00011010'),
|
96
|
+
32 => codeword('00011011'),
|
97
|
+
33 => codeword('00010010'),
|
98
|
+
34 => codeword('00010011'),
|
99
|
+
35 => codeword('00010100'),
|
100
|
+
36 => codeword('00010101'),
|
101
|
+
37 => codeword('00010110'),
|
102
|
+
38 => codeword('00010111'),
|
103
|
+
39 => codeword('00101000'),
|
104
|
+
40 => codeword('00101001'),
|
105
|
+
41 => codeword('00101010'),
|
106
|
+
42 => codeword('00101011'),
|
107
|
+
43 => codeword('00101100'),
|
108
|
+
44 => codeword('00101101'),
|
109
|
+
45 => codeword('00000100'),
|
110
|
+
46 => codeword('00000101'),
|
111
|
+
47 => codeword('00001010'),
|
112
|
+
48 => codeword('00001011'),
|
113
|
+
49 => codeword('01010010'),
|
114
|
+
50 => codeword('01010011'),
|
115
|
+
51 => codeword('01010100'),
|
116
|
+
52 => codeword('01010101'),
|
117
|
+
53 => codeword('00100100'),
|
118
|
+
54 => codeword('00100101'),
|
119
|
+
55 => codeword('01011000'),
|
120
|
+
56 => codeword('01011001'),
|
121
|
+
57 => codeword('01011010'),
|
122
|
+
58 => codeword('01011011'),
|
123
|
+
59 => codeword('01001010'),
|
124
|
+
60 => codeword('01001011'),
|
125
|
+
61 => codeword('00110010'),
|
126
|
+
62 => codeword('00110011'),
|
127
|
+
63 => codeword('00110100')
|
128
|
+
}
|
129
|
+
WHITE_TERMINAL_DECODE_TABLE = WHITE_TERMINAL_ENCODE_TABLE.invert
|
130
|
+
|
131
|
+
BLACK_TERMINAL_ENCODE_TABLE =
|
132
|
+
{
|
133
|
+
0 => codeword('0000110111'),
|
134
|
+
1 => codeword('010'),
|
135
|
+
2 => codeword('11'),
|
136
|
+
3 => codeword('10'),
|
137
|
+
4 => codeword('011'),
|
138
|
+
5 => codeword('0011'),
|
139
|
+
6 => codeword('0010'),
|
140
|
+
7 => codeword('00011'),
|
141
|
+
8 => codeword('000101'),
|
142
|
+
9 => codeword('000100'),
|
143
|
+
10 => codeword('0000100'),
|
144
|
+
11 => codeword('0000101'),
|
145
|
+
12 => codeword('0000111'),
|
146
|
+
13 => codeword('00000100'),
|
147
|
+
14 => codeword('00000111'),
|
148
|
+
15 => codeword('000011000'),
|
149
|
+
16 => codeword('0000010111'),
|
150
|
+
17 => codeword('0000011000'),
|
151
|
+
18 => codeword('0000001000'),
|
152
|
+
19 => codeword('00001100111'),
|
153
|
+
20 => codeword('00001101000'),
|
154
|
+
21 => codeword('00001101100'),
|
155
|
+
22 => codeword('00000110111'),
|
156
|
+
23 => codeword('00000101000'),
|
157
|
+
24 => codeword('00000010111'),
|
158
|
+
25 => codeword('00000011000'),
|
159
|
+
26 => codeword('000011001010'),
|
160
|
+
27 => codeword('000011001011'),
|
161
|
+
28 => codeword('000011001100'),
|
162
|
+
29 => codeword('000011001101'),
|
163
|
+
30 => codeword('000001101000'),
|
164
|
+
31 => codeword('000001101001'),
|
165
|
+
32 => codeword('000001101010'),
|
166
|
+
33 => codeword('000001101011'),
|
167
|
+
34 => codeword('000011010010'),
|
168
|
+
35 => codeword('000011010011'),
|
169
|
+
36 => codeword('000011010100'),
|
170
|
+
37 => codeword('000011010101'),
|
171
|
+
38 => codeword('000011010110'),
|
172
|
+
39 => codeword('000011010111'),
|
173
|
+
40 => codeword('000001101100'),
|
174
|
+
41 => codeword('000001101101'),
|
175
|
+
42 => codeword('000011011010'),
|
176
|
+
43 => codeword('000011011011'),
|
177
|
+
44 => codeword('000001010100'),
|
178
|
+
45 => codeword('000001010101'),
|
179
|
+
46 => codeword('000001010110'),
|
180
|
+
47 => codeword('000001010111'),
|
181
|
+
48 => codeword('000001100100'),
|
182
|
+
49 => codeword('000001100101'),
|
183
|
+
50 => codeword('000001010010'),
|
184
|
+
51 => codeword('000001010011'),
|
185
|
+
52 => codeword('000000100100'),
|
186
|
+
53 => codeword('000000110111'),
|
187
|
+
54 => codeword('000000111000'),
|
188
|
+
55 => codeword('000000100111'),
|
189
|
+
56 => codeword('000000101000'),
|
190
|
+
57 => codeword('000001011000'),
|
191
|
+
58 => codeword('000001011001'),
|
192
|
+
59 => codeword('000000101011'),
|
193
|
+
60 => codeword('000000101100'),
|
194
|
+
61 => codeword('000001011010'),
|
195
|
+
62 => codeword('000001100110'),
|
196
|
+
63 => codeword('000001100111')
|
197
|
+
}
|
198
|
+
BLACK_TERMINAL_DECODE_TABLE = BLACK_TERMINAL_ENCODE_TABLE.invert
|
199
|
+
|
200
|
+
WHITE_CONFIGURATION_ENCODE_TABLE =
|
201
|
+
{
|
202
|
+
64 => codeword('11011'),
|
203
|
+
128 => codeword('10010'),
|
204
|
+
192 => codeword('010111'),
|
205
|
+
256 => codeword('0110111'),
|
206
|
+
320 => codeword('00110110'),
|
207
|
+
384 => codeword('00110111'),
|
208
|
+
448 => codeword('01100100'),
|
209
|
+
512 => codeword('01100101'),
|
210
|
+
576 => codeword('01101000'),
|
211
|
+
640 => codeword('01100111'),
|
212
|
+
704 => codeword('011001100'),
|
213
|
+
768 => codeword('011001101'),
|
214
|
+
832 => codeword('011010010'),
|
215
|
+
896 => codeword('011010011'),
|
216
|
+
960 => codeword('011010100'),
|
217
|
+
1024 => codeword('011010101'),
|
218
|
+
1088 => codeword('011010110'),
|
219
|
+
1152 => codeword('011010111'),
|
220
|
+
1216 => codeword('011011000'),
|
221
|
+
1280 => codeword('011011001'),
|
222
|
+
1344 => codeword('011011010'),
|
223
|
+
1408 => codeword('011011011'),
|
224
|
+
1472 => codeword('010011000'),
|
225
|
+
1536 => codeword('010011001'),
|
226
|
+
1600 => codeword('010011010'),
|
227
|
+
1664 => codeword('011000'),
|
228
|
+
1728 => codeword('010011011'),
|
229
|
+
|
230
|
+
1792 => codeword('00000001000'),
|
231
|
+
1856 => codeword('00000001100'),
|
232
|
+
1920 => codeword('00000001001'),
|
233
|
+
1984 => codeword('000000010010'),
|
234
|
+
2048 => codeword('000000010011'),
|
235
|
+
2112 => codeword('000000010100'),
|
236
|
+
2176 => codeword('000000010101'),
|
237
|
+
2240 => codeword('000000010110'),
|
238
|
+
2340 => codeword('000000010111'),
|
239
|
+
2368 => codeword('000000011100'),
|
240
|
+
2432 => codeword('000000011101'),
|
241
|
+
2496 => codeword('000000011110'),
|
242
|
+
2560 => codeword('000000011111')
|
243
|
+
}
|
244
|
+
WHITE_CONFIGURATION_DECODE_TABLE = WHITE_CONFIGURATION_ENCODE_TABLE.invert
|
245
|
+
|
246
|
+
BLACK_CONFIGURATION_ENCODE_TABLE =
|
247
|
+
{
|
248
|
+
64 => codeword('0000001111'),
|
249
|
+
128 => codeword('000011001000'),
|
250
|
+
192 => codeword('000011001001'),
|
251
|
+
256 => codeword('000001011011'),
|
252
|
+
320 => codeword('000000110011'),
|
253
|
+
384 => codeword('000000110100'),
|
254
|
+
448 => codeword('000000110101'),
|
255
|
+
512 => codeword('0000001101100'),
|
256
|
+
576 => codeword('0000001101101'),
|
257
|
+
640 => codeword('0000001001010'),
|
258
|
+
704 => codeword('0000001001011'),
|
259
|
+
768 => codeword('0000001001100'),
|
260
|
+
832 => codeword('0000001001101'),
|
261
|
+
896 => codeword('0000001110010'),
|
262
|
+
960 => codeword('0000001110011'),
|
263
|
+
1024 => codeword('0000001110100'),
|
264
|
+
1088 => codeword('0000001110101'),
|
265
|
+
1152 => codeword('0000001110110'),
|
266
|
+
1216 => codeword('0000001110111'),
|
267
|
+
1280 => codeword('0000001010010'),
|
268
|
+
1344 => codeword('0000001010011'),
|
269
|
+
1408 => codeword('0000001010100'),
|
270
|
+
1472 => codeword('0000001010101'),
|
271
|
+
1536 => codeword('0000001011010'),
|
272
|
+
1600 => codeword('0000001011011'),
|
273
|
+
1664 => codeword('0000001100100'),
|
274
|
+
1728 => codeword('0000001100101'),
|
275
|
+
|
276
|
+
1792 => codeword('00000001000'),
|
277
|
+
1856 => codeword('00000001100'),
|
278
|
+
1920 => codeword('00000001001'),
|
279
|
+
1984 => codeword('000000010010'),
|
280
|
+
2048 => codeword('000000010011'),
|
281
|
+
2112 => codeword('000000010100'),
|
282
|
+
2176 => codeword('000000010101'),
|
283
|
+
2240 => codeword('000000010110'),
|
284
|
+
2340 => codeword('000000010111'),
|
285
|
+
2368 => codeword('000000011100'),
|
286
|
+
2432 => codeword('000000011101'),
|
287
|
+
2496 => codeword('000000011110'),
|
288
|
+
2560 => codeword('000000011111')
|
289
|
+
}
|
290
|
+
BLACK_CONFIGURATION_DECODE_TABLE = BLACK_CONFIGURATION_ENCODE_TABLE.invert
|
291
|
+
|
292
|
+
#
|
293
|
+
# Creates a new CCITT Fax Filter.
|
294
|
+
#
|
295
|
+
def initialize(parameters = {})
|
296
|
+
super(DecodeParms.new(parameters))
|
297
|
+
end
|
298
|
+
|
299
|
+
#
|
300
|
+
# Encodes data using CCITT-facsimile compression method.
|
301
|
+
#
|
302
|
+
def encode(stream)
|
303
|
+
|
304
|
+
if @params.has_key?(:K) and @params.K != 0
|
305
|
+
raise NotImplementedError, "CCITT encoding scheme not supported"
|
306
|
+
end
|
307
|
+
|
308
|
+
columns = @params.has_key?(:Columns) ? @params.Columns.value : (stream.size << 3)
|
309
|
+
unless columns.is_a?(::Integer) and columns > 0 and columns % 8 == 0
|
310
|
+
raise CCITTFaxFilterError, "Invalid value for parameter `Columns'"
|
311
|
+
end
|
312
|
+
|
313
|
+
if stream.size % (columns >> 3) != 0
|
314
|
+
raise CCITTFaxFilterError, "Data size is not a multiple of image width"
|
315
|
+
end
|
316
|
+
|
317
|
+
white, black = (@params.BlackIs1 == true) ? [0,1] : [1,0]
|
318
|
+
|
319
|
+
bitr = Utils::BitReader.new(stream)
|
320
|
+
bitw = Utils::BitWriter.new
|
321
|
+
|
322
|
+
until bitr.eod?
|
323
|
+
|
324
|
+
bitw.write(*EOL)
|
325
|
+
scan_len = 0
|
326
|
+
current_color = white
|
327
|
+
|
328
|
+
# Process each bit in line.
|
329
|
+
begin
|
330
|
+
if bitr.read(1) == current_color
|
331
|
+
scan_len += 1
|
332
|
+
else
|
333
|
+
if current_color == white
|
334
|
+
put_white_bits(bitw, scan_len)
|
335
|
+
else
|
336
|
+
put_black_bits(bitw, scan_len)
|
337
|
+
end
|
338
|
+
|
339
|
+
current_color ^= 1
|
340
|
+
scan_len = 1
|
341
|
+
end
|
342
|
+
end while bitr.pos % columns != 0
|
343
|
+
|
344
|
+
if current_color == white
|
345
|
+
put_white_bits(bitw, scan_len)
|
346
|
+
else
|
347
|
+
put_black_bits(bitw, scan_len)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Align encoded lign on a 8-bit boundary.
|
351
|
+
if @params.EncodedByteAlign == true and bitw.pos % 8 != 0
|
352
|
+
bitw.write(0, 8 - (bitw.pos % 8))
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Emit return-to-control code
|
357
|
+
bitw.write(*RTC)
|
358
|
+
|
359
|
+
bitw.final.to_s
|
360
|
+
end
|
361
|
+
|
362
|
+
#
|
363
|
+
# Decodes data using CCITT-facsimile compression method.
|
364
|
+
#
|
365
|
+
def decode(stream)
|
366
|
+
|
367
|
+
if @params.has_key?(:K) and @params.K != 0
|
368
|
+
raise NotImplementedError, "CCITT encoding scheme not supported"
|
369
|
+
end
|
370
|
+
|
371
|
+
columns = @params.has_key?(:Columns) ? @params.Columns.value : 1728
|
372
|
+
unless columns.is_a?(::Integer) and columns > 0 and columns % 8 == 0
|
373
|
+
raise CCITTFaxFilterError, "Invalid value for parameter `Columns'"
|
374
|
+
end
|
375
|
+
|
376
|
+
white, black = (@params.BlackIs1 == true) ? [0,1] : [1,0]
|
377
|
+
aligned = @params.EncodedByteAlign == true
|
378
|
+
has_eob = @params.EndOfBlock.nil? or @params.EndOfBlock == true
|
379
|
+
has_eol = @params.EndOfLine == true
|
380
|
+
|
381
|
+
unless has_eob
|
382
|
+
unless @params.has_key?(:Rows) and @params.Rows.is_a?(::Integer) and @params.Rows.value > 0
|
383
|
+
raise CCITTFaxFilterError, "Invalid value for parameter `Rows'"
|
384
|
+
end
|
385
|
+
|
386
|
+
rows = @params.Rows.to_i
|
387
|
+
end
|
388
|
+
|
389
|
+
bitr = Utils::BitReader.new(stream)
|
390
|
+
bitw = Utils::BitWriter.new
|
391
|
+
|
392
|
+
current_color = white
|
393
|
+
until bitr.eod? or rows == 0
|
394
|
+
|
395
|
+
# realign the read line on a 8-bit boundary if required
|
396
|
+
if aligned and bitr.pos % 8 != 0
|
397
|
+
bitr.pos += 8 - (bitr.pos % 8)
|
398
|
+
end
|
399
|
+
|
400
|
+
# received return-to-control code
|
401
|
+
if has_eob and bitr.peek(RTC[1]) == RTC[0]
|
402
|
+
bitr.pos += RTC[1]
|
403
|
+
break
|
404
|
+
end
|
405
|
+
|
406
|
+
# checking for the presence of EOL
|
407
|
+
if bitr.peek(EOL[1]) != EOL[0]
|
408
|
+
raise CCITTFaxFilterError, "No end-of-line pattern found (at bit pos #{bitr.pos}/#{bitr.size}})" if has_eol
|
409
|
+
else
|
410
|
+
bitr.pos += EOL[1]
|
411
|
+
end
|
412
|
+
|
413
|
+
line_length = 0
|
414
|
+
while line_length < columns
|
415
|
+
if current_color == white
|
416
|
+
bit_length = get_white_bits(bitr)
|
417
|
+
else
|
418
|
+
bit_length = get_black_bits(bitr)
|
419
|
+
end
|
420
|
+
|
421
|
+
raise CCITTFaxFilterError, "Unfinished line (at bit pos #{bitr.pos}/#{bitr.size}})" if bit_length.nil?
|
422
|
+
|
423
|
+
line_length += bit_length
|
424
|
+
raise CCITTFaxFilterError, "Line is too long (at bit pos #{bitr.pos}/#{bitr.size}})" if line_length > columns
|
425
|
+
|
426
|
+
write_bit_range(bitw, current_color, bit_length)
|
427
|
+
current_color ^= 1
|
428
|
+
end
|
429
|
+
|
430
|
+
rows -= 1 unless has_eob
|
431
|
+
end
|
432
|
+
|
433
|
+
bitw.final.to_s
|
434
|
+
end
|
435
|
+
|
436
|
+
private
|
437
|
+
|
438
|
+
def get_white_bits(bitr) #:nodoc:
|
439
|
+
get_color_bits(bitr, WHITE_CONFIGURATION_DECODE_TABLE, WHITE_TERMINAL_DECODE_TABLE)
|
440
|
+
end
|
441
|
+
|
442
|
+
def get_black_bits(bitr) #:nodoc:
|
443
|
+
get_color_bits(bitr, BLACK_CONFIGURATION_DECODE_TABLE, BLACK_TERMINAL_DECODE_TABLE)
|
444
|
+
end
|
445
|
+
|
446
|
+
def get_color_bits(bitr, config_words, term_words) #:nodoc:
|
447
|
+
bits = 0
|
448
|
+
check_conf = true
|
449
|
+
|
450
|
+
while check_conf
|
451
|
+
check_conf = false
|
452
|
+
(2..13).each do |length|
|
453
|
+
codeword = bitr.peek(length)
|
454
|
+
config_value = config_words[[codeword, length]]
|
455
|
+
|
456
|
+
if config_value
|
457
|
+
bitr.pos += length
|
458
|
+
bits += config_value
|
459
|
+
check_conf = true if config_value == 2560
|
460
|
+
break
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
(2..13).each do |length|
|
466
|
+
codeword = bitr.peek(length)
|
467
|
+
term_value = term_words[[codeword, length]]
|
468
|
+
|
469
|
+
if term_value
|
470
|
+
bitr.pos += length
|
471
|
+
bits += term_value
|
472
|
+
|
473
|
+
return bits
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
nil
|
478
|
+
end
|
479
|
+
|
480
|
+
def lookup_bits(table, codeword, length)
|
481
|
+
table.rassoc [codeword, length]
|
482
|
+
end
|
483
|
+
|
484
|
+
def put_white_bits(bitw, length) #:nodoc:
|
485
|
+
put_color_bits(bitw, length, WHITE_CONFIGURATION_ENCODE_TABLE, WHITE_TERMINAL_ENCODE_TABLE)
|
486
|
+
end
|
487
|
+
|
488
|
+
def put_black_bits(bitw, length) #:nodoc:
|
489
|
+
put_color_bits(bitw, length, BLACK_CONFIGURATION_ENCODE_TABLE, BLACK_TERMINAL_ENCODE_TABLE)
|
490
|
+
end
|
491
|
+
|
492
|
+
def put_color_bits(bitw, length, config_words, term_words) #:nodoc:
|
493
|
+
while length > 2559
|
494
|
+
bitw.write(*config_words[2560])
|
495
|
+
length -= 2560
|
496
|
+
end
|
497
|
+
|
498
|
+
if length > 63
|
499
|
+
conf_length = (length >> 6) << 6
|
500
|
+
bitw.write(*config_words[conf_length])
|
501
|
+
length -= conf_length
|
502
|
+
end
|
503
|
+
|
504
|
+
bitw.write(*term_words[length])
|
505
|
+
end
|
506
|
+
|
507
|
+
def write_bit_range(bitw, bit_value, length) #:nodoc:
|
508
|
+
bitw.write((bit_value << length) - bit_value, length)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
end
|
513
|
+
|
514
|
+
end
|
515
|
+
|