origamindee 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +89 -0
  3. data/COPYING.LESSER +165 -0
  4. data/README.md +131 -0
  5. data/bin/config/pdfcop.conf.yml +236 -0
  6. data/bin/pdf2pdfa +87 -0
  7. data/bin/pdf2ruby +333 -0
  8. data/bin/pdfcop +476 -0
  9. data/bin/pdfdecompress +97 -0
  10. data/bin/pdfdecrypt +91 -0
  11. data/bin/pdfencrypt +113 -0
  12. data/bin/pdfexplode +223 -0
  13. data/bin/pdfextract +277 -0
  14. data/bin/pdfmetadata +143 -0
  15. data/bin/pdfsh +12 -0
  16. data/bin/shell/console.rb +128 -0
  17. data/bin/shell/hexdump.rb +59 -0
  18. data/bin/shell/irbrc +69 -0
  19. data/examples/README.md +34 -0
  20. data/examples/attachments/attachment.rb +38 -0
  21. data/examples/attachments/nested_document.rb +51 -0
  22. data/examples/encryption/encryption.rb +28 -0
  23. data/examples/events/events.rb +72 -0
  24. data/examples/flash/flash.rb +37 -0
  25. data/examples/flash/helloworld.swf +0 -0
  26. data/examples/forms/javascript.rb +54 -0
  27. data/examples/forms/xfa.rb +115 -0
  28. data/examples/javascript/hello_world.rb +22 -0
  29. data/examples/javascript/js_emulation.rb +54 -0
  30. data/examples/loop/goto.rb +32 -0
  31. data/examples/loop/named.rb +33 -0
  32. data/examples/signature/signature.rb +65 -0
  33. data/examples/uri/javascript.rb +56 -0
  34. data/examples/uri/open-uri.rb +21 -0
  35. data/examples/uri/submitform.rb +47 -0
  36. data/lib/origami/3d.rb +364 -0
  37. data/lib/origami/acroform.rb +321 -0
  38. data/lib/origami/actions.rb +318 -0
  39. data/lib/origami/annotations.rb +711 -0
  40. data/lib/origami/array.rb +242 -0
  41. data/lib/origami/boolean.rb +90 -0
  42. data/lib/origami/catalog.rb +418 -0
  43. data/lib/origami/collections.rb +144 -0
  44. data/lib/origami/compound.rb +161 -0
  45. data/lib/origami/destinations.rb +252 -0
  46. data/lib/origami/dictionary.rb +192 -0
  47. data/lib/origami/encryption.rb +1084 -0
  48. data/lib/origami/extensions/fdf.rb +347 -0
  49. data/lib/origami/extensions/ppklite.rb +422 -0
  50. data/lib/origami/filespec.rb +197 -0
  51. data/lib/origami/filters/ascii.rb +211 -0
  52. data/lib/origami/filters/ccitt/tables.rb +267 -0
  53. data/lib/origami/filters/ccitt.rb +357 -0
  54. data/lib/origami/filters/crypt.rb +38 -0
  55. data/lib/origami/filters/dct.rb +54 -0
  56. data/lib/origami/filters/flate.rb +69 -0
  57. data/lib/origami/filters/jbig2.rb +57 -0
  58. data/lib/origami/filters/jpx.rb +47 -0
  59. data/lib/origami/filters/lzw.rb +170 -0
  60. data/lib/origami/filters/predictors.rb +292 -0
  61. data/lib/origami/filters/runlength.rb +129 -0
  62. data/lib/origami/filters.rb +364 -0
  63. data/lib/origami/font.rb +196 -0
  64. data/lib/origami/functions.rb +79 -0
  65. data/lib/origami/graphics/colors.rb +230 -0
  66. data/lib/origami/graphics/instruction.rb +98 -0
  67. data/lib/origami/graphics/path.rb +182 -0
  68. data/lib/origami/graphics/patterns.rb +174 -0
  69. data/lib/origami/graphics/render.rb +62 -0
  70. data/lib/origami/graphics/state.rb +149 -0
  71. data/lib/origami/graphics/text.rb +225 -0
  72. data/lib/origami/graphics/xobject.rb +918 -0
  73. data/lib/origami/graphics.rb +38 -0
  74. data/lib/origami/header.rb +75 -0
  75. data/lib/origami/javascript.rb +713 -0
  76. data/lib/origami/linearization.rb +330 -0
  77. data/lib/origami/metadata.rb +172 -0
  78. data/lib/origami/name.rb +135 -0
  79. data/lib/origami/null.rb +65 -0
  80. data/lib/origami/numeric.rb +181 -0
  81. data/lib/origami/obfuscation.rb +245 -0
  82. data/lib/origami/object.rb +760 -0
  83. data/lib/origami/optionalcontent.rb +183 -0
  84. data/lib/origami/outline.rb +54 -0
  85. data/lib/origami/outputintents.rb +85 -0
  86. data/lib/origami/page.rb +722 -0
  87. data/lib/origami/parser.rb +269 -0
  88. data/lib/origami/parsers/fdf.rb +56 -0
  89. data/lib/origami/parsers/pdf/lazy.rb +176 -0
  90. data/lib/origami/parsers/pdf/linear.rb +122 -0
  91. data/lib/origami/parsers/pdf.rb +118 -0
  92. data/lib/origami/parsers/ppklite.rb +57 -0
  93. data/lib/origami/pdf.rb +1108 -0
  94. data/lib/origami/reference.rb +134 -0
  95. data/lib/origami/signature.rb +702 -0
  96. data/lib/origami/stream.rb +705 -0
  97. data/lib/origami/string.rb +444 -0
  98. data/lib/origami/template/patterns.rb +56 -0
  99. data/lib/origami/template/widgets.rb +151 -0
  100. data/lib/origami/trailer.rb +190 -0
  101. data/lib/origami/tree.rb +62 -0
  102. data/lib/origami/version.rb +23 -0
  103. data/lib/origami/webcapture.rb +100 -0
  104. data/lib/origami/xfa/config.rb +453 -0
  105. data/lib/origami/xfa/connectionset.rb +146 -0
  106. data/lib/origami/xfa/datasets.rb +49 -0
  107. data/lib/origami/xfa/localeset.rb +42 -0
  108. data/lib/origami/xfa/package.rb +59 -0
  109. data/lib/origami/xfa/pdf.rb +73 -0
  110. data/lib/origami/xfa/signature.rb +42 -0
  111. data/lib/origami/xfa/sourceset.rb +43 -0
  112. data/lib/origami/xfa/stylesheet.rb +44 -0
  113. data/lib/origami/xfa/template.rb +1691 -0
  114. data/lib/origami/xfa/xdc.rb +42 -0
  115. data/lib/origami/xfa/xfa.rb +146 -0
  116. data/lib/origami/xfa/xfdf.rb +43 -0
  117. data/lib/origami/xfa/xmpmeta.rb +43 -0
  118. data/lib/origami/xfa.rb +62 -0
  119. data/lib/origami/xreftable.rb +557 -0
  120. data/lib/origami.rb +47 -0
  121. data/test/dataset/calc.pdf +85 -0
  122. data/test/dataset/crypto.pdf +36 -0
  123. data/test/dataset/empty.pdf +49 -0
  124. data/test/test_actions.rb +27 -0
  125. data/test/test_annotations.rb +68 -0
  126. data/test/test_forms.rb +30 -0
  127. data/test/test_native_types.rb +83 -0
  128. data/test/test_object_tree.rb +33 -0
  129. data/test/test_pages.rb +60 -0
  130. data/test/test_pdf.rb +20 -0
  131. data/test/test_pdf_attachment.rb +34 -0
  132. data/test/test_pdf_create.rb +24 -0
  133. data/test/test_pdf_encrypt.rb +102 -0
  134. data/test/test_pdf_parse.rb +134 -0
  135. data/test/test_pdf_parse_lazy.rb +69 -0
  136. data/test/test_pdf_sign.rb +97 -0
  137. data/test/test_streams.rb +184 -0
  138. data/test/test_xrefs.rb +67 -0
  139. metadata +280 -0
@@ -0,0 +1,170 @@
1
+ =begin
2
+
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
5
+
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ require 'origami/filters/predictors'
22
+
23
+ module Origami
24
+
25
+ module Filter
26
+
27
+ class InvalidLZWDataError < DecodeError #:nodoc:
28
+ end
29
+
30
+ #
31
+ # Class representing a filter used to encode and decode data with LZW compression algorithm.
32
+ #
33
+ class LZW
34
+ include Filter
35
+ include Predictor
36
+
37
+ EOD = 257 #:nodoc:
38
+ CLEARTABLE = 256 #:nodoc:
39
+
40
+ #
41
+ # Encodes given data using LZW compression method.
42
+ # _stream_:: The data to encode.
43
+ #
44
+ def encode(string)
45
+ input = pre_prediction(string)
46
+
47
+ table, codesize = reset_state
48
+ result = Utils::BitWriter.new
49
+ result.write(CLEARTABLE, codesize)
50
+
51
+ s = ''
52
+ input.each_byte do |byte|
53
+ char = byte.chr
54
+
55
+ if table.size == 4096
56
+ result.write(CLEARTABLE, codesize)
57
+ table, _ = reset_state
58
+ end
59
+
60
+ codesize = table.size.bit_length
61
+
62
+ it = s + char
63
+ if table.has_key?(it)
64
+ s = it
65
+ else
66
+ result.write(table[s], codesize)
67
+ table[it] = table.size
68
+ s = char
69
+ end
70
+ end
71
+
72
+ result.write(table[s], codesize) unless s.empty?
73
+ result.write(EOD, codesize)
74
+
75
+ result.final.to_s
76
+ end
77
+
78
+ #
79
+ # Decodes given data using LZW compression method.
80
+ # _stream_:: The data to decode.
81
+ #
82
+ def decode(string)
83
+ result = "".b
84
+ bstring = Utils::BitReader.new(string)
85
+ table, codesize = reset_state
86
+ prevbyte = nil
87
+
88
+ until bstring.eod? do
89
+ byte = bstring.read(codesize)
90
+ break if byte == EOD
91
+
92
+ if byte == CLEARTABLE
93
+ table, codesize = reset_state
94
+ prevbyte = nil
95
+ redo
96
+ end
97
+
98
+ begin
99
+ codesize = decode_codeword_size(table)
100
+ result << decode_byte(table, prevbyte, byte, codesize)
101
+ rescue InvalidLZWDataError => error
102
+ error.message.concat " (bit pos #{bstring.pos - codesize})"
103
+ error.input_data = string
104
+ error.decoded_data = result
105
+ raise(error)
106
+ end
107
+
108
+ prevbyte = byte
109
+ end
110
+
111
+ post_prediction(result)
112
+ end
113
+
114
+ private
115
+
116
+ def decode_codeword_size(table)
117
+ case table.size
118
+ when 258...510 then 9
119
+ when 510...1022 then 10
120
+ when 1022...2046 then 11
121
+ when 2046...4095 then 12
122
+ else
123
+ raise InvalidLZWDataError, "LZW table is full and no clear flag was set"
124
+ end
125
+ end
126
+
127
+ def decode_byte(table, previous_byte, byte, codesize) #:nodoc:
128
+
129
+ # Ensure the codeword can be decoded in the current state.
130
+ check_codeword(table, previous_byte, byte, codesize)
131
+
132
+ if previous_byte.nil?
133
+ table.key(byte)
134
+ else
135
+ if table.value?(byte)
136
+ entry = table.key(byte)
137
+ else
138
+ entry = table.key(previous_byte)
139
+ entry += entry[0, 1]
140
+ end
141
+
142
+ table[table.key(previous_byte) + entry[0,1]] = table.size
143
+
144
+ entry
145
+ end
146
+ end
147
+
148
+ def check_codeword(table, previous_byte, byte, codesize) #:nodoc:
149
+ if (previous_byte.nil? and not table.value?(byte)) or (previous_byte and not table.value?(previous_byte))
150
+ codeword = previous_byte || byte
151
+ raise InvalidLZWDataError, "No entry for codeword #{codeword.to_s(2).rjust(codesize, '0')}"
152
+ end
153
+ end
154
+
155
+ def reset_state #:nodoc:
156
+ table = {}
157
+ 256.times do |i|
158
+ table[i.chr] = i
159
+ end
160
+
161
+ table[CLEARTABLE] = CLEARTABLE
162
+ table[EOD] = EOD
163
+
164
+ # Codeword table, codeword size
165
+ [table, 9]
166
+ end
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,292 @@
1
+ =begin
2
+
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
5
+
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ module Origami
22
+
23
+ module Filter
24
+
25
+ class PredictorError < Error #:nodoc:
26
+ end
27
+
28
+ module Predictor
29
+
30
+ NONE = 1
31
+ TIFF = 2
32
+ PNG_NONE = 10
33
+ PNG_SUB = 11
34
+ PNG_UP = 12
35
+ PNG_AVERAGE = 13
36
+ PNG_PAETH = 14
37
+ PNG_OPTIMUM = 15
38
+
39
+ class DecodeParms < Dictionary
40
+ include StandardObject
41
+
42
+ field :Predictor, :Type => Integer, :Default => 1
43
+ field :Colors, :Type => Integer, :Default => 1
44
+ field :BitsPerComponent, :Type => Integer, :Default => 8
45
+ field :Columns, :Type => Integer, :Default => 1
46
+ end
47
+
48
+ def self.included(receiver)
49
+ raise TypeError, "Predictors only applies to Filters" unless receiver.include?(Filter)
50
+ end
51
+
52
+ #
53
+ # Create a new predictive Filter.
54
+ # _parameters_:: A hash of filter options.
55
+ #
56
+ def initialize(parameters = {})
57
+ super(DecodeParms.new(parameters))
58
+ end
59
+
60
+ private
61
+
62
+ def pre_prediction(data)
63
+ return data unless @params.Predictor.is_a?(Integer)
64
+
65
+ apply_pre_prediction(data, **prediction_parameters)
66
+ end
67
+
68
+ def post_prediction(data)
69
+ return data unless @params.Predictor.is_a?(Integer)
70
+
71
+ apply_post_prediction(data, **prediction_parameters)
72
+ end
73
+
74
+ def prediction_parameters
75
+ {
76
+ predictor: @params.Predictor.to_i,
77
+ colors: @params.Colors.is_a?(Integer) ? @params.Colors.to_i : 1,
78
+ bpc: @params.BitsPerComponent.is_a?(Integer) ? @params.BitsPerComponent.to_i : 8,
79
+ columns: @params.Columns.is_a?(Integer) ? @params.Columns.to_i : 1,
80
+ }
81
+ end
82
+
83
+ def apply_pre_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
84
+ return data if data.empty? or predictor == NONE
85
+
86
+ bpp, bpr = compute_bpp_bpr(data, columns, colors, bpc)
87
+
88
+ unless data.size % bpr == 0
89
+ raise PredictorError.new("Invalid data size #{data.size}, should be multiple of bpr=#{bpr}",
90
+ input_data: data)
91
+ end
92
+
93
+ if predictor == TIFF
94
+ tiff_pre_prediction(data, colors, bpc, columns)
95
+ elsif predictor >= 10 # PNG
96
+ png_pre_prediction(data, predictor, bpp, bpr)
97
+ else
98
+ raise PredictorError.new("Unknown predictor : #{predictor}", input_data: data)
99
+ end
100
+ end
101
+
102
+ def apply_post_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
103
+ return data if data.empty? or predictor == NONE
104
+
105
+ bpp, bpr = compute_bpp_bpr(data, columns, colors, bpc)
106
+
107
+ if predictor == TIFF
108
+ tiff_post_prediction(data, colors, bpc, columns)
109
+ elsif predictor >= 10 # PNG
110
+ # Each line has an extra predictor byte.
111
+ png_post_prediction(data, bpp, bpr + 1)
112
+ else
113
+ raise PredictorError.new("Unknown predictor : #{predictor}", input_data: data)
114
+ end
115
+ end
116
+
117
+ #
118
+ # Computes the number of bytes per pixel and number of bytes per row.
119
+ #
120
+ def compute_bpp_bpr(data, columns, colors, bpc)
121
+ unless colors.between?(1, 4)
122
+ raise PredictorError.new("Colors must be between 1 and 4", input_data: data)
123
+ end
124
+
125
+ unless [1,2,4,8,16].include?(bpc)
126
+ raise PredictorError.new("BitsPerComponent must be in 1, 2, 4, 8 or 16", input_data: data)
127
+ end
128
+
129
+ # components per line
130
+ nvals = columns * colors
131
+
132
+ # bytes per pixel
133
+ bpp = (colors * bpc + 7) >> 3
134
+
135
+ # bytes per row
136
+ bpr = (nvals * bpc + 7) >> 3
137
+
138
+ [ bpp, bpr ]
139
+ end
140
+
141
+ #
142
+ # Decodes the PNG input data.
143
+ # Each line should be prepended by a byte identifying a PNG predictor.
144
+ #
145
+ def png_post_prediction(data, bpp, bpr)
146
+ result = ""
147
+ uprow = "\0" * bpr
148
+ thisrow = "\0" * bpr
149
+ nrows = (data.size + bpr - 1) / bpr
150
+
151
+ nrows.times do |irow|
152
+ line = data[irow * bpr, bpr]
153
+ predictor = 10 + line[0].ord
154
+ line[0] = "\0"
155
+
156
+ for i in (1..line.size-1)
157
+ up = uprow[i].ord
158
+
159
+ if bpp > i
160
+ left = upleft = 0
161
+ else
162
+ left = line[i - bpp].ord
163
+ upleft = uprow[i - bpp].ord
164
+ end
165
+
166
+ begin
167
+ thisrow[i] = png_apply_prediction(predictor, line[i].ord, up, left, upleft, &:+)
168
+ rescue PredictorError => error
169
+ thisrow[i] = line[i] if Origami::OPTIONS[:ignore_png_errors]
170
+
171
+ error.input_data = data
172
+ error.decoded_data = result
173
+ raise(error)
174
+ end
175
+ end
176
+
177
+ result << thisrow[1..-1]
178
+ uprow = thisrow
179
+ end
180
+
181
+ result
182
+ end
183
+
184
+ #
185
+ # Encodes the input data given a PNG predictor.
186
+ #
187
+ def png_pre_prediction(data, predictor, bpp, bpr)
188
+ result = ""
189
+ nrows = data.size / bpr
190
+
191
+ line = "\0" + data[-bpr, bpr]
192
+
193
+ (nrows - 1).downto(0) do |irow|
194
+ uprow =
195
+ if irow == 0
196
+ "\0" * (bpr + 1)
197
+ else
198
+ "\0" + data[(irow - 1) * bpr, bpr]
199
+ end
200
+
201
+ bpr.downto(1) do |i|
202
+ up = uprow[i].ord
203
+ left = line[i - bpp].ord
204
+ upleft = uprow[i - bpp].ord
205
+
206
+ line[i] = png_apply_prediction(predictor, line[i].ord, up, left, upleft, &:-)
207
+ end
208
+
209
+ line[0] = (predictor - 10).chr
210
+ result = line + result
211
+
212
+ line = uprow
213
+ end
214
+
215
+ result
216
+ end
217
+
218
+ #
219
+ # Computes the next component value given a predictor and adjacent components.
220
+ # A block must be passed to apply the operation.
221
+ #
222
+ def png_apply_prediction(predictor, value, up, left, upleft)
223
+
224
+ result =
225
+ case predictor
226
+ when PNG_NONE
227
+ value
228
+ when PNG_SUB
229
+ yield(value, left)
230
+ when PNG_UP
231
+ yield(value, up)
232
+ when PNG_AVERAGE
233
+ yield(value, (left + up) / 2)
234
+ when PNG_PAETH
235
+ yield(value, png_paeth_choose(up, left, upleft))
236
+ else
237
+ raise PredictorError, "Unsupported PNG predictor : #{predictor}"
238
+ end
239
+
240
+ (result & 0xFF).chr
241
+ end
242
+
243
+ #
244
+ # Choose the preferred value in a PNG paeth predictor given the left, up and up left samples.
245
+ #
246
+ def png_paeth_choose(left, up, upleft)
247
+ p = left + up - upleft
248
+ pa, pb, pc = (p - left).abs, (p - up).abs, (p - upleft).abs
249
+
250
+ case [pa, pb, pc].min
251
+ when pa then left
252
+ when pb then up
253
+ when pc then upleft
254
+ end
255
+ end
256
+
257
+ def tiff_post_prediction(data, colors, bpc, columns) #:nodoc:
258
+ tiff_apply_prediction(data, colors, bpc, columns, &:+)
259
+ end
260
+
261
+ def tiff_pre_prediction(data, colors, bpc, columns) #:nodoc:
262
+ tiff_apply_prediction(data, colors, bpc, columns, &:-)
263
+ end
264
+
265
+ def tiff_apply_prediction(data, colors, bpc, columns) #:nodoc:
266
+ bpr = (colors * bpc * columns + 7) >> 3
267
+ nrows = data.size / bpr
268
+ bitmask = (1 << bpc) - 1
269
+ result = Utils::BitWriter.new
270
+
271
+ nrows.times do |irow|
272
+ line = Utils::BitReader.new(data[irow * bpr, bpr])
273
+
274
+ diffpixel = ::Array.new(colors, 0)
275
+ columns.times do
276
+ pixel = ::Array.new(colors) { line.read(bpc) }
277
+ diffpixel = diffpixel.zip(pixel).map!{|diff, c| yield(c, diff) & bitmask}
278
+
279
+ diffpixel.each do |c|
280
+ result.write(c, bpc)
281
+ end
282
+ end
283
+
284
+ result.final
285
+ end
286
+
287
+ result.final.to_s
288
+ end
289
+ end
290
+
291
+ end
292
+ end
@@ -0,0 +1,129 @@
1
+ =begin
2
+
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
5
+
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ module Origami
22
+
23
+ module Filter
24
+
25
+ class InvalidRunLengthDataError < DecodeError #:nodoc:
26
+ end
27
+
28
+ #
29
+ # Class representing a Filter used to encode and decode data using RLE compression algorithm.
30
+ #
31
+ class RunLength
32
+ include Filter
33
+
34
+ EOD = 128 #:nodoc:
35
+
36
+ #
37
+ # Encodes data using RLE compression method.
38
+ # _stream_:: The data to encode.
39
+ #
40
+ def encode(stream)
41
+ result = "".b
42
+ i = 0
43
+
44
+ while i < stream.size
45
+
46
+ # How many identical bytes coming?
47
+ length = compute_run_length(stream, i)
48
+
49
+ # If more than 1, then compress them.
50
+ if length > 1
51
+ result << (257 - length).chr << stream[i]
52
+ i += length
53
+
54
+ # Otherwise how many different bytes to copy?
55
+ else
56
+ next_pos = find_next_run(stream, i)
57
+ length = next_pos - i
58
+
59
+ result << (length - 1).chr << stream[i, length]
60
+
61
+ i += length
62
+ end
63
+ end
64
+
65
+ result << EOD.chr
66
+ end
67
+
68
+ #
69
+ # Decodes data using RLE decompression method.
70
+ # _stream_:: The data to decode.
71
+ #
72
+ def decode(stream)
73
+ result = "".b
74
+
75
+ i = 0
76
+ until i >= stream.length or stream[i].ord == EOD do
77
+
78
+ # At least two bytes are required.
79
+ if i > stream.length - 2
80
+ raise InvalidRunLengthDataError.new("Truncated run-length data", input_data: stream, decoded_data: result)
81
+ end
82
+
83
+ length = stream[i].ord
84
+ if length < EOD
85
+ result << stream[i + 1, length + 1]
86
+ i = i + length + 2
87
+ else
88
+ result << stream[i + 1] * (257 - length)
89
+ i = i + 2
90
+ end
91
+ end
92
+
93
+ # Check if offset is beyond the end of data.
94
+ if i > stream.length
95
+ raise InvalidRunLengthDataError.new("Truncated run-length data", input_data: stream, decoded_data: result)
96
+ end
97
+
98
+ result
99
+ end
100
+
101
+ private
102
+
103
+ #
104
+ # Find the position of the next byte at which a new run starts.
105
+ #
106
+ def find_next_run(input, pos)
107
+ start = pos
108
+ pos += 1 while pos + 1 < input.size and (pos - start + 1) < EOD and input[pos] != input[pos + 1]
109
+
110
+ pos + 1
111
+ end
112
+
113
+ #
114
+ # Computes the length of the run at the given position.
115
+ #
116
+ def compute_run_length(input, pos)
117
+ run_length = 1
118
+ while pos + 1 < input.size and run_length < EOD and input[pos] == input[pos + 1]
119
+ run_length += 1
120
+ pos += 1
121
+ end
122
+
123
+ run_length
124
+ end
125
+ end
126
+ RL = RunLength
127
+
128
+ end
129
+ end