origami 1.0.2

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