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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +89 -0
- data/COPYING.LESSER +165 -0
- data/README.md +131 -0
- data/bin/config/pdfcop.conf.yml +236 -0
- data/bin/pdf2pdfa +87 -0
- data/bin/pdf2ruby +333 -0
- data/bin/pdfcop +476 -0
- data/bin/pdfdecompress +97 -0
- data/bin/pdfdecrypt +91 -0
- data/bin/pdfencrypt +113 -0
- data/bin/pdfexplode +223 -0
- data/bin/pdfextract +277 -0
- data/bin/pdfmetadata +143 -0
- data/bin/pdfsh +12 -0
- data/bin/shell/console.rb +128 -0
- data/bin/shell/hexdump.rb +59 -0
- data/bin/shell/irbrc +69 -0
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/examples/events/events.rb +72 -0
- data/examples/flash/flash.rb +37 -0
- data/examples/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami/3d.rb +364 -0
- data/lib/origami/acroform.rb +321 -0
- data/lib/origami/actions.rb +318 -0
- data/lib/origami/annotations.rb +711 -0
- data/lib/origami/array.rb +242 -0
- data/lib/origami/boolean.rb +90 -0
- data/lib/origami/catalog.rb +418 -0
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/compound.rb +161 -0
- data/lib/origami/destinations.rb +252 -0
- data/lib/origami/dictionary.rb +192 -0
- data/lib/origami/encryption.rb +1084 -0
- data/lib/origami/extensions/fdf.rb +347 -0
- data/lib/origami/extensions/ppklite.rb +422 -0
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters/ascii.rb +211 -0
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/ccitt.rb +357 -0
- data/lib/origami/filters/crypt.rb +38 -0
- data/lib/origami/filters/dct.rb +54 -0
- data/lib/origami/filters/flate.rb +69 -0
- data/lib/origami/filters/jbig2.rb +57 -0
- data/lib/origami/filters/jpx.rb +47 -0
- data/lib/origami/filters/lzw.rb +170 -0
- data/lib/origami/filters/predictors.rb +292 -0
- data/lib/origami/filters/runlength.rb +129 -0
- data/lib/origami/filters.rb +364 -0
- data/lib/origami/font.rb +196 -0
- data/lib/origami/functions.rb +79 -0
- data/lib/origami/graphics/colors.rb +230 -0
- data/lib/origami/graphics/instruction.rb +98 -0
- data/lib/origami/graphics/path.rb +182 -0
- data/lib/origami/graphics/patterns.rb +174 -0
- data/lib/origami/graphics/render.rb +62 -0
- data/lib/origami/graphics/state.rb +149 -0
- data/lib/origami/graphics/text.rb +225 -0
- data/lib/origami/graphics/xobject.rb +918 -0
- data/lib/origami/graphics.rb +38 -0
- data/lib/origami/header.rb +75 -0
- data/lib/origami/javascript.rb +713 -0
- data/lib/origami/linearization.rb +330 -0
- data/lib/origami/metadata.rb +172 -0
- data/lib/origami/name.rb +135 -0
- data/lib/origami/null.rb +65 -0
- data/lib/origami/numeric.rb +181 -0
- data/lib/origami/obfuscation.rb +245 -0
- data/lib/origami/object.rb +760 -0
- data/lib/origami/optionalcontent.rb +183 -0
- data/lib/origami/outline.rb +54 -0
- data/lib/origami/outputintents.rb +85 -0
- data/lib/origami/page.rb +722 -0
- data/lib/origami/parser.rb +269 -0
- data/lib/origami/parsers/fdf.rb +56 -0
- data/lib/origami/parsers/pdf/lazy.rb +176 -0
- data/lib/origami/parsers/pdf/linear.rb +122 -0
- data/lib/origami/parsers/pdf.rb +118 -0
- data/lib/origami/parsers/ppklite.rb +57 -0
- data/lib/origami/pdf.rb +1108 -0
- data/lib/origami/reference.rb +134 -0
- data/lib/origami/signature.rb +702 -0
- data/lib/origami/stream.rb +705 -0
- data/lib/origami/string.rb +444 -0
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +190 -0
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +100 -0
- data/lib/origami/xfa/config.rb +453 -0
- data/lib/origami/xfa/connectionset.rb +146 -0
- data/lib/origami/xfa/datasets.rb +49 -0
- data/lib/origami/xfa/localeset.rb +42 -0
- data/lib/origami/xfa/package.rb +59 -0
- data/lib/origami/xfa/pdf.rb +73 -0
- data/lib/origami/xfa/signature.rb +42 -0
- data/lib/origami/xfa/sourceset.rb +43 -0
- data/lib/origami/xfa/stylesheet.rb +44 -0
- data/lib/origami/xfa/template.rb +1691 -0
- data/lib/origami/xfa/xdc.rb +42 -0
- data/lib/origami/xfa/xfa.rb +146 -0
- data/lib/origami/xfa/xfdf.rb +43 -0
- data/lib/origami/xfa/xmpmeta.rb +43 -0
- data/lib/origami/xfa.rb +62 -0
- data/lib/origami/xreftable.rb +557 -0
- data/lib/origami.rb +47 -0
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +36 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +68 -0
- data/test/test_forms.rb +30 -0
- data/test/test_native_types.rb +83 -0
- data/test/test_object_tree.rb +33 -0
- data/test/test_pages.rb +60 -0
- data/test/test_pdf.rb +20 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +102 -0
- data/test/test_pdf_parse.rb +134 -0
- data/test/test_pdf_parse_lazy.rb +69 -0
- data/test/test_pdf_sign.rb +97 -0
- data/test/test_streams.rb +184 -0
- data/test/test_xrefs.rb +67 -0
- 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
|