origami 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/COPYING.LESSER +165 -0
  2. data/README +77 -0
  3. data/VERSION +1 -0
  4. data/bin/config/pdfcop.conf.yml +237 -0
  5. data/bin/gui/about.rb +46 -0
  6. data/bin/gui/config.rb +132 -0
  7. data/bin/gui/file.rb +385 -0
  8. data/bin/gui/hexdump.rb +74 -0
  9. data/bin/gui/hexview.rb +91 -0
  10. data/bin/gui/imgview.rb +72 -0
  11. data/bin/gui/menu.rb +392 -0
  12. data/bin/gui/properties.rb +132 -0
  13. data/bin/gui/signing.rb +635 -0
  14. data/bin/gui/textview.rb +107 -0
  15. data/bin/gui/treeview.rb +409 -0
  16. data/bin/gui/walker.rb +282 -0
  17. data/bin/gui/xrefs.rb +79 -0
  18. data/bin/pdf2graph +121 -0
  19. data/bin/pdf2ruby +353 -0
  20. data/bin/pdfcocoon +104 -0
  21. data/bin/pdfcop +455 -0
  22. data/bin/pdfdecompress +104 -0
  23. data/bin/pdfdecrypt +95 -0
  24. data/bin/pdfencrypt +112 -0
  25. data/bin/pdfextract +221 -0
  26. data/bin/pdfmetadata +123 -0
  27. data/bin/pdfsh +13 -0
  28. data/bin/pdfwalker +7 -0
  29. data/bin/shell/.irbrc +104 -0
  30. data/bin/shell/console.rb +136 -0
  31. data/bin/shell/hexdump.rb +83 -0
  32. data/origami.rb +36 -0
  33. data/origami/3d.rb +239 -0
  34. data/origami/acroform.rb +321 -0
  35. data/origami/actions.rb +299 -0
  36. data/origami/adobe/fdf.rb +259 -0
  37. data/origami/adobe/ppklite.rb +489 -0
  38. data/origami/annotations.rb +775 -0
  39. data/origami/array.rb +187 -0
  40. data/origami/boolean.rb +101 -0
  41. data/origami/catalog.rb +486 -0
  42. data/origami/destinations.rb +213 -0
  43. data/origami/dictionary.rb +188 -0
  44. data/origami/docmdp.rb +96 -0
  45. data/origami/encryption.rb +1293 -0
  46. data/origami/export.rb +283 -0
  47. data/origami/file.rb +222 -0
  48. data/origami/filters.rb +250 -0
  49. data/origami/filters/ascii.rb +189 -0
  50. data/origami/filters/ccitt.rb +515 -0
  51. data/origami/filters/crypt.rb +47 -0
  52. data/origami/filters/dct.rb +61 -0
  53. data/origami/filters/flate.rb +112 -0
  54. data/origami/filters/jbig2.rb +63 -0
  55. data/origami/filters/jpx.rb +53 -0
  56. data/origami/filters/lzw.rb +195 -0
  57. data/origami/filters/predictors.rb +276 -0
  58. data/origami/filters/runlength.rb +117 -0
  59. data/origami/font.rb +209 -0
  60. data/origami/functions.rb +93 -0
  61. data/origami/graphics.rb +33 -0
  62. data/origami/graphics/colors.rb +191 -0
  63. data/origami/graphics/instruction.rb +126 -0
  64. data/origami/graphics/path.rb +154 -0
  65. data/origami/graphics/patterns.rb +180 -0
  66. data/origami/graphics/state.rb +164 -0
  67. data/origami/graphics/text.rb +224 -0
  68. data/origami/graphics/xobject.rb +493 -0
  69. data/origami/header.rb +90 -0
  70. data/origami/linearization.rb +318 -0
  71. data/origami/metadata.rb +114 -0
  72. data/origami/name.rb +170 -0
  73. data/origami/null.rb +75 -0
  74. data/origami/numeric.rb +188 -0
  75. data/origami/obfuscation.rb +233 -0
  76. data/origami/object.rb +527 -0
  77. data/origami/outline.rb +59 -0
  78. data/origami/page.rb +559 -0
  79. data/origami/parser.rb +268 -0
  80. data/origami/parsers/fdf.rb +45 -0
  81. data/origami/parsers/pdf.rb +27 -0
  82. data/origami/parsers/pdf/linear.rb +113 -0
  83. data/origami/parsers/ppklite.rb +86 -0
  84. data/origami/pdf.rb +1144 -0
  85. data/origami/reference.rb +113 -0
  86. data/origami/signature.rb +474 -0
  87. data/origami/stream.rb +575 -0
  88. data/origami/string.rb +416 -0
  89. data/origami/trailer.rb +173 -0
  90. data/origami/webcapture.rb +87 -0
  91. data/origami/xfa.rb +3027 -0
  92. data/origami/xreftable.rb +447 -0
  93. data/templates/patterns.rb +66 -0
  94. data/templates/widgets.rb +173 -0
  95. data/templates/xdp.rb +92 -0
  96. data/tests/dataset/test.dummycrt +28 -0
  97. data/tests/dataset/test.dummykey +27 -0
  98. data/tests/tc_actions.rb +32 -0
  99. data/tests/tc_annotations.rb +85 -0
  100. data/tests/tc_pages.rb +37 -0
  101. data/tests/tc_pdfattach.rb +24 -0
  102. data/tests/tc_pdfencrypt.rb +110 -0
  103. data/tests/tc_pdfnew.rb +32 -0
  104. data/tests/tc_pdfparse.rb +98 -0
  105. data/tests/tc_pdfsig.rb +37 -0
  106. data/tests/tc_streams.rb +129 -0
  107. data/tests/ts_pdf.rb +45 -0
  108. metadata +193 -0
@@ -0,0 +1,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
+