pdf-reader 1.1.1 → 2.5.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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +87 -2
  3. data/{README.rdoc → README.md} +43 -31
  4. data/Rakefile +21 -16
  5. data/bin/pdf_callbacks +1 -1
  6. data/bin/pdf_object +4 -1
  7. data/bin/pdf_text +1 -3
  8. data/examples/callbacks.rb +2 -1
  9. data/examples/extract_images.rb +11 -6
  10. data/examples/fuzzy_paragraphs.rb +24 -0
  11. data/lib/pdf/reader/afm/Courier-Bold.afm +342 -0
  12. data/lib/pdf/reader/afm/Courier-BoldOblique.afm +342 -0
  13. data/lib/pdf/reader/afm/Courier-Oblique.afm +342 -0
  14. data/lib/pdf/reader/afm/Courier.afm +342 -0
  15. data/lib/pdf/reader/afm/Helvetica-Bold.afm +2827 -0
  16. data/lib/pdf/reader/afm/Helvetica-BoldOblique.afm +2827 -0
  17. data/lib/pdf/reader/afm/Helvetica-Oblique.afm +3051 -0
  18. data/lib/pdf/reader/afm/Helvetica.afm +3051 -0
  19. data/lib/pdf/reader/afm/MustRead.html +19 -0
  20. data/lib/pdf/reader/afm/Symbol.afm +213 -0
  21. data/lib/pdf/reader/afm/Times-Bold.afm +2588 -0
  22. data/lib/pdf/reader/afm/Times-BoldItalic.afm +2384 -0
  23. data/lib/pdf/reader/afm/Times-Italic.afm +2667 -0
  24. data/lib/pdf/reader/afm/Times-Roman.afm +2419 -0
  25. data/lib/pdf/reader/afm/ZapfDingbats.afm +225 -0
  26. data/lib/pdf/reader/buffer.rb +90 -63
  27. data/lib/pdf/reader/cid_widths.rb +63 -0
  28. data/lib/pdf/reader/cmap.rb +69 -38
  29. data/lib/pdf/reader/encoding.rb +74 -48
  30. data/lib/pdf/reader/error.rb +24 -4
  31. data/lib/pdf/reader/filter/ascii85.rb +28 -0
  32. data/lib/pdf/reader/filter/ascii_hex.rb +30 -0
  33. data/lib/pdf/reader/filter/depredict.rb +141 -0
  34. data/lib/pdf/reader/filter/flate.rb +53 -0
  35. data/lib/pdf/reader/filter/lzw.rb +21 -0
  36. data/lib/pdf/reader/filter/null.rb +18 -0
  37. data/lib/pdf/reader/filter/run_length.rb +45 -0
  38. data/lib/pdf/reader/filter.rb +15 -234
  39. data/lib/pdf/reader/font.rb +107 -43
  40. data/lib/pdf/reader/font_descriptor.rb +80 -0
  41. data/lib/pdf/reader/form_xobject.rb +26 -4
  42. data/lib/pdf/reader/glyph_hash.rb +56 -18
  43. data/lib/pdf/reader/lzw.rb +6 -4
  44. data/lib/pdf/reader/null_security_handler.rb +17 -0
  45. data/lib/pdf/reader/object_cache.rb +40 -16
  46. data/lib/pdf/reader/object_hash.rb +94 -40
  47. data/lib/pdf/reader/object_stream.rb +1 -0
  48. data/lib/pdf/reader/orientation_detector.rb +34 -0
  49. data/lib/pdf/reader/overlapping_runs_filter.rb +65 -0
  50. data/lib/pdf/reader/page.rb +48 -3
  51. data/lib/pdf/reader/page_layout.rb +125 -0
  52. data/lib/pdf/reader/page_state.rb +185 -70
  53. data/lib/pdf/reader/page_text_receiver.rb +70 -20
  54. data/lib/pdf/reader/pages_strategy.rb +4 -293
  55. data/lib/pdf/reader/parser.rb +37 -61
  56. data/lib/pdf/reader/print_receiver.rb +6 -0
  57. data/lib/pdf/reader/reference.rb +4 -1
  58. data/lib/pdf/reader/register_receiver.rb +17 -31
  59. data/lib/pdf/reader/resource_methods.rb +1 -0
  60. data/lib/pdf/reader/standard_security_handler.rb +82 -42
  61. data/lib/pdf/reader/standard_security_handler_v5.rb +91 -0
  62. data/lib/pdf/reader/stream.rb +5 -2
  63. data/lib/pdf/reader/synchronized_cache.rb +33 -0
  64. data/lib/pdf/reader/text_run.rb +99 -0
  65. data/lib/pdf/reader/token.rb +4 -1
  66. data/lib/pdf/reader/transformation_matrix.rb +195 -0
  67. data/lib/pdf/reader/unimplemented_security_handler.rb +17 -0
  68. data/lib/pdf/reader/width_calculator/built_in.rb +67 -0
  69. data/lib/pdf/reader/width_calculator/composite.rb +28 -0
  70. data/lib/pdf/reader/width_calculator/true_type.rb +56 -0
  71. data/lib/pdf/reader/width_calculator/type_one_or_three.rb +33 -0
  72. data/lib/pdf/reader/width_calculator/type_zero.rb +25 -0
  73. data/lib/pdf/reader/width_calculator.rb +12 -0
  74. data/lib/pdf/reader/xref.rb +41 -9
  75. data/lib/pdf/reader.rb +45 -104
  76. data/lib/pdf-reader.rb +4 -1
  77. metadata +220 -101
  78. data/bin/pdf_list_callbacks +0 -17
  79. data/lib/pdf/hash.rb +0 -15
  80. data/lib/pdf/reader/abstract_strategy.rb +0 -81
  81. data/lib/pdf/reader/metadata_strategy.rb +0 -56
  82. data/lib/pdf/reader/text_receiver.rb +0 -264
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'ascii85'
5
+
6
+ class PDF::Reader
7
+ module Filter # :nodoc:
8
+ # implementation of the Ascii85 filter
9
+ class Ascii85
10
+ def initialize(options = {})
11
+ @options = options
12
+ end
13
+
14
+ ################################################################################
15
+ # Decode the specified data using the Ascii85 algorithm. Relies on the AScii85
16
+ # rubygem.
17
+ #
18
+ def filter(data)
19
+ data = "<~#{data}" unless data.to_s[0,2] == "<~"
20
+ ::Ascii85::decode(data)
21
+ rescue Exception => e
22
+ # Oops, there was a problem decoding the stream
23
+ raise MalformedPDFError,
24
+ "Error occured while decoding an ASCII85 stream (#{e.class.to_s}: #{e.to_s})"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ class PDF::Reader
6
+ module Filter # :nodoc:
7
+ # implementation of the AsciiHex stream filter
8
+ class AsciiHex
9
+ def initialize(options = {})
10
+ @options = options
11
+ end
12
+
13
+ ################################################################################
14
+ # Decode the specified data using the AsciiHex algorithm.
15
+ #
16
+ def filter(data)
17
+ data.chop! if data[-1,1] == ">"
18
+ data = data[1,data.size] if data[0,1] == "<"
19
+ data.gsub!(/[^A-Fa-f0-9]/,"")
20
+ data << "0" if data.size % 2 == 1
21
+ data.scan(/.{2}/).map { |s| s.hex.chr }.join("")
22
+ rescue Exception => e
23
+ # Oops, there was a problem decoding the stream
24
+ raise MalformedPDFError,
25
+ "Error occured while decoding an ASCIIHex stream (#{e.class.to_s}: #{e.to_s})"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,141 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ class PDF::Reader
5
+ module Filter # :nodoc:
6
+ # some filter implementations support preprocessing of the data to
7
+ # improve compression
8
+ class Depredict
9
+ def initialize(options = {})
10
+ @options = options || {}
11
+ end
12
+
13
+ ################################################################################
14
+ # Streams can be preprocessed to improve compression. This reverses the
15
+ # preprocessing
16
+ #
17
+ def filter(data)
18
+ predictor = @options[:Predictor].to_i
19
+
20
+ case predictor
21
+ when 0, 1 then
22
+ data
23
+ when 2 then
24
+ tiff_depredict(data)
25
+ when 10, 11, 12, 13, 14, 15 then
26
+ png_depredict(data)
27
+ else
28
+ raise MalformedPDFError, "Unrecognised predictor value (#{predictor})"
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ ################################################################################
35
+ def tiff_depredict(data)
36
+ data = data.unpack("C*")
37
+ unfiltered = []
38
+ bpc = @options[:BitsPerComponent] || 8
39
+ pixel_bits = bpc * @options[:Colors]
40
+ pixel_bytes = pixel_bits / 8
41
+ line_len = (pixel_bytes * @options[:Columns])
42
+ pos = 0
43
+
44
+ if bpc != 8
45
+ raise UnsupportedFeatureError, "TIFF predictor onlys supports 8 Bits Per Component"
46
+ end
47
+
48
+ until pos > data.size
49
+ row_data = data[pos, line_len]
50
+ row_data.each_with_index do |byte, index|
51
+ left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
52
+ row_data[index] = (byte + left) % 256
53
+ end
54
+ unfiltered += row_data
55
+ pos += line_len
56
+ end
57
+
58
+ unfiltered.pack("C*")
59
+ end
60
+ ################################################################################
61
+ def png_depredict(data)
62
+ return data if @options[:Predictor].to_i < 10
63
+
64
+ data = data.unpack("C*")
65
+
66
+ pixel_bytes = @options[:Colors] || 1
67
+ scanline_length = (pixel_bytes * @options[:Columns]) + 1
68
+ row = 0
69
+ pixels = []
70
+ paeth, pa, pb, pc = nil
71
+ until data.empty? do
72
+ row_data = data.slice! 0, scanline_length
73
+ filter = row_data.shift
74
+ case filter
75
+ when 0 # None
76
+ when 1 # Sub
77
+ row_data.each_with_index do |byte, index|
78
+ left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
79
+ row_data[index] = (byte + left) % 256
80
+ #p [byte, left, row_data[index]]
81
+ end
82
+ when 2 # Up
83
+ row_data.each_with_index do |byte, index|
84
+ col = index / pixel_bytes
85
+ upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
86
+ row_data[index] = (upper + byte) % 256
87
+ end
88
+ when 3 # Average
89
+ row_data.each_with_index do |byte, index|
90
+ col = index / pixel_bytes
91
+ upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
92
+ left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
93
+
94
+ row_data[index] = (byte + ((left + upper)/2).floor) % 256
95
+ end
96
+ when 4 # Paeth
97
+ left = upper = upper_left = nil
98
+ row_data.each_with_index do |byte, index|
99
+ col = index / pixel_bytes
100
+
101
+ left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
102
+ if row.zero?
103
+ upper = upper_left = 0
104
+ else
105
+ upper = pixels[row-1][col][index % pixel_bytes]
106
+ upper_left = col.zero? ? 0 :
107
+ pixels[row-1][col-1][index % pixel_bytes]
108
+ end
109
+
110
+ p = left + upper - upper_left
111
+ pa = (p - left).abs
112
+ pb = (p - upper).abs
113
+ pc = (p - upper_left).abs
114
+
115
+ paeth = if pa <= pb && pa <= pc
116
+ left
117
+ elsif pb <= pc
118
+ upper
119
+ else
120
+ upper_left
121
+ end
122
+
123
+ row_data[index] = (byte + paeth) % 256
124
+ end
125
+ else
126
+ raise ArgumentError, "Invalid filter algorithm #{filter}"
127
+ end
128
+
129
+ s = []
130
+ row_data.each_slice pixel_bytes do |slice|
131
+ s << slice
132
+ end
133
+ pixels << s
134
+ row += 1
135
+ end
136
+
137
+ pixels.map { |bytes| bytes.flatten.pack("C*") }.join("")
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,53 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+
5
+ require 'zlib'
6
+
7
+ class PDF::Reader
8
+ module Filter # :nodoc:
9
+ # implementation of the Flate (zlib) stream filter
10
+ class Flate
11
+ ZLIB_AUTO_DETECT_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
12
+ ZLIB_RAW_DEFLATE = -15 # Zlib::MAX_WBITS * -1
13
+
14
+ def initialize(options = {})
15
+ @options = options
16
+ end
17
+
18
+ ################################################################################
19
+ # Decode the specified data with the Zlib compression algorithm
20
+ def filter(data)
21
+ deflated = zlib_inflate(data) || zlib_inflate(data[0, data.bytesize-1])
22
+
23
+ if deflated.nil?
24
+ raise MalformedPDFError,
25
+ "Error while inflating a compressed stream (no suitable inflation algorithm found)"
26
+ end
27
+ Depredict.new(@options).filter(deflated)
28
+ end
29
+
30
+ private
31
+
32
+ def zlib_inflate(data)
33
+ begin
34
+ return Zlib::Inflate.new(ZLIB_AUTO_DETECT_ZLIB_OR_GZIP).inflate(data)
35
+ rescue Zlib::DataError
36
+ # by default, Ruby's Zlib assumes the data it's inflating
37
+ # is RFC1951 deflated data, wrapped in a RFC1950 zlib container. If that
38
+ # fails, swallow the exception and attempt to inflate the data as a raw
39
+ # RFC1951 stream.
40
+ end
41
+
42
+ begin
43
+ return Zlib::Inflate.new(ZLIB_RAW_DEFLATE).inflate(data)
44
+ rescue StandardError
45
+ # swallow this one too, so we can try some other fallback options
46
+ end
47
+
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ class PDF::Reader
6
+ module Filter # :nodoc:
7
+ # implementation of the LZW stream filter
8
+ class Lzw
9
+ def initialize(options = {})
10
+ @options = options
11
+ end
12
+
13
+ ################################################################################
14
+ # Decode the specified data with the LZW compression algorithm
15
+ def filter(data)
16
+ data = PDF::Reader::LZW.decode(data)
17
+ Depredict.new(@options).filter(data)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ class PDF::Reader
6
+ module Filter # :nodoc:
7
+ # implementation of the null stream filter
8
+ class Null
9
+ def initialize(options = {})
10
+ @options = options
11
+ end
12
+
13
+ def filter(data)
14
+ data
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ class PDF::Reader # :nodoc:
6
+ module Filter # :nodoc:
7
+ # implementation of the run length stream filter
8
+ class RunLength
9
+ def initialize(options = {})
10
+ @options = options
11
+ end
12
+
13
+ ################################################################################
14
+ # Decode the specified data with the RunLengthDecode compression algorithm
15
+ def filter(data)
16
+ pos = 0
17
+ out = "".dup
18
+
19
+ while pos < data.length
20
+ length = data.getbyte(pos)
21
+ pos += 1
22
+
23
+ case
24
+ when length == 128
25
+ break
26
+ when length < 128
27
+ # When the length is < 128, we copy the following length+1 bytes
28
+ # literally.
29
+ out << data[pos, length + 1]
30
+ pos += length
31
+ else
32
+ # When the length is > 128, we copy the next byte (257 - length)
33
+ # times; i.e., "\xFA\x00" ([250, 0]) will expand to
34
+ # "\x00\x00\x00\x00\x00\x00\x00".
35
+ out << data[pos, 1] * (257 - length)
36
+ end
37
+
38
+ pos += 1
39
+ end
40
+
41
+ Depredict.new(@options).filter(out)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,6 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
1
4
  ################################################################################
2
5
  #
3
6
  # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
@@ -22,7 +25,6 @@
22
25
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
26
  #
24
27
  ################################################################################
25
- require 'zlib'
26
28
 
27
29
  class PDF::Reader
28
30
  ################################################################################
@@ -30,7 +32,7 @@ class PDF::Reader
30
32
  # support for features like compression and encryption. This class is for decoding that
31
33
  # content.
32
34
  #
33
- class Filter # :nodoc:
35
+ module Filter # :nodoc:
34
36
 
35
37
  ################################################################################
36
38
  # creates a new filter for decoding content.
@@ -38,242 +40,21 @@ class PDF::Reader
38
40
  # Filters that are only used to encode image data are accepted, but the data is
39
41
  # returned untouched. At this stage PDF::Reader has no need to decode images.
40
42
  #
41
- def initialize (name, options = nil)
42
- @options = options
43
-
43
+ def self.with(name, options = {})
44
44
  case name.to_sym
45
- when :ASCII85Decode then @filter = :ascii85
46
- when :ASCIIHexDecode then @filter = :asciihex
47
- when :CCITTFaxDecode then @filter = nil
48
- when :DCTDecode then @filter = nil
49
- when :FlateDecode then @filter = :flate
50
- when :JBIG2Decode then @filter = nil
51
- when :JPXDecode then @filter = nil
52
- when :LZWDecode then @filter = :lzw
53
- when :RunLengthDecode then @filter = :runlength
45
+ when :ASCII85Decode then PDF::Reader::Filter::Ascii85.new(options)
46
+ when :ASCIIHexDecode then PDF::Reader::Filter::AsciiHex.new(options)
47
+ when :CCITTFaxDecode then PDF::Reader::Filter::Null.new(options)
48
+ when :DCTDecode then PDF::Reader::Filter::Null.new(options)
49
+ when :FlateDecode then PDF::Reader::Filter::Flate.new(options)
50
+ when :Fl then PDF::Reader::Filter::Flate.new(options)
51
+ when :JBIG2Decode then PDF::Reader::Filter::Null.new(options)
52
+ when :JPXDecode then PDF::Reader::Filter::Null.new(options)
53
+ when :LZWDecode then PDF::Reader::Filter::Lzw.new(options)
54
+ when :RunLengthDecode then PDF::Reader::Filter::RunLength.new(options)
54
55
  else
55
56
  raise UnsupportedFeatureError, "Unknown filter: #{name}"
56
57
  end
57
58
  end
58
- ################################################################################
59
- # attempts to decode the specified data with the current filter
60
- #
61
- # Filters that are only used to encode image data are accepted, but the data is
62
- # returned untouched. At this stage PDF::Reader has no need to decode images.
63
- #
64
- def filter (data)
65
- # leave the data untouched if we don't support the required filter
66
- return data if @filter.nil?
67
-
68
- # decode the data
69
- self.send(@filter, data)
70
- end
71
- ################################################################################
72
- # Decode the specified data using the Ascii85 algorithm. Relies on the AScii85
73
- # rubygem.
74
- #
75
- def ascii85(data)
76
- data = "<~#{data}" unless data.to_s[0,2] == "<~"
77
- Ascii85::decode(data)
78
- rescue Exception => e
79
- # Oops, there was a problem decoding the stream
80
- raise MalformedPDFError, "Error occured while decoding an ASCII85 stream (#{e.class.to_s}: #{e.to_s})"
81
- end
82
- ################################################################################
83
- # Decode the specified data using the AsciiHex algorithm.
84
- #
85
- def asciihex(data)
86
- data.chop! if data[-1,1] == ">"
87
- data = data[1,data.size] if data[0,1] == "<"
88
- data.gsub!(/[^A-Fa-f0-9]/,"")
89
- data << "0" if data.size % 2 == 1
90
- data.scan(/.{2}/).map { |s| s.hex.chr }.join("")
91
- rescue Exception => e
92
- # Oops, there was a problem decoding the stream
93
- raise MalformedPDFError, "Error occured while decoding an ASCIIHex stream (#{e.class.to_s}: #{e.to_s})"
94
- end
95
- ################################################################################
96
- # Decode the specified data with the Zlib compression algorithm
97
- def flate (data)
98
- deflated = nil
99
- begin
100
- deflated = Zlib::Inflate.new.inflate(data)
101
- rescue Zlib::DataError => e
102
- # by default, Ruby's Zlib assumes the data it's inflating
103
- # is RFC1951 deflated data, wrapped in a RFC1951 zlib container.
104
- # If that fails, then use an undocumented 'feature' to attempt to inflate
105
- # the data as a raw RFC1951 stream.
106
- #
107
- # See
108
- # - http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/243545
109
- # - http://www.gzip.org/zlib/zlib_faq.html#faq38
110
- deflated = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(data)
111
- end
112
- depredict(deflated, @options)
113
- rescue Exception => e
114
- # Oops, there was a problem inflating the stream
115
- raise MalformedPDFError, "Error occured while inflating a compressed stream (#{e.class.to_s}: #{e.to_s})"
116
- end
117
- ################################################################################
118
- # Decode the specified data with the LZW compression algorithm
119
- def lzw(data)
120
- data = PDF::Reader::LZW.decode(data)
121
- depredict(data, @options)
122
- end
123
- ################################################################################
124
- # Decode the specified data with the RunLengthDecode compression algorithm
125
- def runlength(data)
126
- pos = 0
127
- out = ""
128
-
129
- while pos < data.length
130
- if data.respond_to?(:getbyte)
131
- length = data.getbyte(pos)
132
- else
133
- length = data[pos]
134
- end
135
- pos += 1
136
-
137
- case
138
- when length == 128
139
- break
140
- when length < 128
141
- # When the length is < 128, we copy the following length+1 bytes
142
- # literally.
143
- out << data[pos, length + 1]
144
- pos += length
145
- else
146
- # When the length is > 128, we copy the next byte (257 - length)
147
- # times; i.e., "\xFA\x00" ([250, 0]) will expand to
148
- # "\x00\x00\x00\x00\x00\x00\x00".
149
- out << data[pos, 1] * (257 - length)
150
- end
151
-
152
- pos += 1
153
- end
154
-
155
- out
156
- end
157
- ################################################################################
158
- def depredict(data, opts = {})
159
- predictor = (opts || {})[:Predictor].to_i
160
-
161
- case predictor
162
- when 0, 1 then
163
- data
164
- when 2 then
165
- tiff_depredict(data, opts)
166
- when 10, 11, 12, 13, 14, 15 then
167
- png_depredict(data, opts)
168
- else
169
- raise MalformedPDFError, "Unrecognised predictor value (#{predictor})"
170
- end
171
- end
172
- ################################################################################
173
- def tiff_depredict(data, opts = {})
174
- data = data.unpack("C*")
175
- unfiltered = []
176
- bpc = opts[:BitsPerComponent] || 8
177
- pixel_bits = bpc * opts[:Colors]
178
- pixel_bytes = pixel_bits / 8
179
- line_len = (pixel_bytes * opts[:Columns])
180
- pos = 0
181
-
182
- if bpc != 8
183
- raise UnsupportedFeatureError, "TIFF predictor onlys supports 8 Bits Per Component"
184
- end
185
-
186
- until pos > data.size
187
- row_data = data[pos, line_len]
188
- row_data.each_with_index do |byte, index|
189
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
190
- row_data[index] = (byte + left) % 256
191
- end
192
- unfiltered += row_data
193
- pos += line_len
194
- end
195
-
196
- unfiltered.pack("C*")
197
- end
198
- ################################################################################
199
- def png_depredict(data, opts = {})
200
- return data if opts.nil? || opts[:Predictor].to_i < 10
201
-
202
- data = data.unpack("C*")
203
-
204
- pixel_bytes = opts[:Colors] || 1
205
- scanline_length = (pixel_bytes * opts[:Columns]) + 1
206
- row = 0
207
- pixels = []
208
- paeth, pa, pb, pc = nil
209
- until data.empty? do
210
- row_data = data.slice! 0, scanline_length
211
- filter = row_data.shift
212
- case filter
213
- when 0 # None
214
- when 1 # Sub
215
- row_data.each_with_index do |byte, index|
216
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
217
- row_data[index] = (byte + left) % 256
218
- #p [byte, left, row_data[index]]
219
- end
220
- when 2 # Up
221
- row_data.each_with_index do |byte, index|
222
- col = index / pixel_bytes
223
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
224
- row_data[index] = (upper + byte) % 256
225
- end
226
- when 3 # Average
227
- row_data.each_with_index do |byte, index|
228
- col = index / pixel_bytes
229
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
230
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
231
-
232
- row_data[index] = (byte + ((left + upper)/2).floor) % 256
233
- end
234
- when 4 # Paeth
235
- left = upper = upper_left = nil
236
- row_data.each_with_index do |byte, index|
237
- col = index / pixel_bytes
238
-
239
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
240
- if row.zero?
241
- upper = upper_left = 0
242
- else
243
- upper = pixels[row-1][col][index % pixel_bytes]
244
- upper_left = col.zero? ? 0 :
245
- pixels[row-1][col-1][index % pixel_bytes]
246
- end
247
-
248
- p = left + upper - upper_left
249
- pa = (p - left).abs
250
- pb = (p - upper).abs
251
- pc = (p - upper_left).abs
252
-
253
- paeth = if pa <= pb && pa <= pc
254
- left
255
- elsif pb <= pc
256
- upper
257
- else
258
- upper_left
259
- end
260
-
261
- row_data[index] = (byte + paeth) % 256
262
- end
263
- else
264
- raise ArgumentError, "Invalid filter algorithm #{filter}"
265
- end
266
-
267
- s = []
268
- row_data.each_slice pixel_bytes do |slice|
269
- s << slice
270
- end
271
- pixels << s
272
- row += 1
273
- end
274
-
275
- pixels.map { |bytes| bytes.flatten.pack("C*") }.join("")
276
- end
277
59
  end
278
60
  end
279
- ################################################################################