qr_reader 0.0.1

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE +14 -0
  5. data/README.md +222 -0
  6. data/exe/qr_reader +66 -0
  7. data/lib/qr_reader/binarizer.rb +170 -0
  8. data/lib/qr_reader/bit_matrix.rb +120 -0
  9. data/lib/qr_reader/decoder/bitstream_parser.rb +307 -0
  10. data/lib/qr_reader/decoder/character_set.rb +163 -0
  11. data/lib/qr_reader/decoder/codewords.rb +164 -0
  12. data/lib/qr_reader/decoder/data_mask.rb +57 -0
  13. data/lib/qr_reader/decoder/format_reader.rb +204 -0
  14. data/lib/qr_reader/decoder/function_patterns.rb +62 -0
  15. data/lib/qr_reader/decoder/galois_field.rb +69 -0
  16. data/lib/qr_reader/decoder/reed_solomon.rb +312 -0
  17. data/lib/qr_reader/decoder.rb +241 -0
  18. data/lib/qr_reader/detector/alignment_grid.rb +127 -0
  19. data/lib/qr_reader/detector/alignment_pattern_finder.rb +191 -0
  20. data/lib/qr_reader/detector/candidate/dimension.rb +237 -0
  21. data/lib/qr_reader/detector/candidate/strokes.rb +126 -0
  22. data/lib/qr_reader/detector/candidate.rb +436 -0
  23. data/lib/qr_reader/detector/finder_pattern.rb +63 -0
  24. data/lib/qr_reader/detector/finder_pattern_finder.rb +199 -0
  25. data/lib/qr_reader/detector/geometry.rb +118 -0
  26. data/lib/qr_reader/detector/grid_sampler.rb +350 -0
  27. data/lib/qr_reader/detector/pattern_sets.rb +235 -0
  28. data/lib/qr_reader/detector/perspective_transform.rb +117 -0
  29. data/lib/qr_reader/detector/pure_detector.rb +197 -0
  30. data/lib/qr_reader/detector.rb +215 -0
  31. data/lib/qr_reader/errors.rb +14 -0
  32. data/lib/qr_reader/format_information.rb +79 -0
  33. data/lib/qr_reader/formats/bmp.rb +434 -0
  34. data/lib/qr_reader/formats/ccitt.rb +81 -0
  35. data/lib/qr_reader/formats/gif.rb +272 -0
  36. data/lib/qr_reader/formats/jpeg.rb +152 -0
  37. data/lib/qr_reader/formats/pdf.rb +2935 -0
  38. data/lib/qr_reader/formats/png.rb +311 -0
  39. data/lib/qr_reader/formats/pnm.rb +286 -0
  40. data/lib/qr_reader/formats/tiff.rb +1345 -0
  41. data/lib/qr_reader/formats.rb +279 -0
  42. data/lib/qr_reader/image.rb +231 -0
  43. data/lib/qr_reader/kernels/binarizer.rb +475 -0
  44. data/lib/qr_reader/kernels/ccitt.rb +554 -0
  45. data/lib/qr_reader/kernels/detector.rb +587 -0
  46. data/lib/qr_reader/kernels/image.rb +65 -0
  47. data/lib/qr_reader/kernels/jpeg.rb +3421 -0
  48. data/lib/qr_reader/kernels/lzw.rb +173 -0
  49. data/lib/qr_reader/kernels/pixels.rb +116 -0
  50. data/lib/qr_reader/kernels/png.rb +1138 -0
  51. data/lib/qr_reader/kernels.rb +14 -0
  52. data/lib/qr_reader/native_kernels.rb +130 -0
  53. data/lib/qr_reader/reader.rb +348 -0
  54. data/lib/qr_reader/result.rb +49 -0
  55. data/lib/qr_reader/symbol_version.rb +173 -0
  56. data/lib/qr_reader/version.rb +5 -0
  57. data/lib/qr_reader.rb +77 -0
  58. metadata +103 -0
@@ -0,0 +1,2935 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+ require "zlib"
5
+ require_relative "../kernels/lzw"
6
+
7
+ module QrReader
8
+ module Formats
9
+ # Extracts the raster images of PDF documents page by page: scanned
10
+ # pages, faxes and photos placed in PDFs. Vector graphics (including
11
+ # vector-drawn QR codes) are not rendered.
12
+ #
13
+ # PDF.each_image(bytes) { |image, info| ... } # info: {page: 0, name: "Im0", width: 2480, height: 3508,
14
+ # # pixel_aspect: 1.0, dpi: [300.0, 300.0]}
15
+ # PDF.each_image(bytes) # => Enumerator
16
+ # PDF.each_image(bytes, raw_jpeg: true) { |image_or_jpeg, info| ... }
17
+ # PDF.images(bytes) # => [#<QrReader::Image 2480x3508>, ...]
18
+ #
19
+ # Structure: the header may sit anywhere in the first KB; classic
20
+ # cross-reference tables and cross-reference streams (incremental
21
+ # updates, hybrid files), object streams; when the cross-reference data
22
+ # is broken or missing, the file is scanned for "N G obj". Objects are
23
+ # parsed and streams decoded only when needed. Pages are walked through
24
+ # the page tree (inherited resources) and each page's content streams
25
+ # are run to find the images it draws, in drawing order: image
26
+ # XObjects (also inside form XObjects) and inline images
27
+ # (BI ... ID ... EI). Images are not rendered onto a page: each one is
28
+ # yielded as it is stored (an image drawn several times on a page is
29
+ # yielded once), except that bands of one picture - images drawn
30
+ # directly above each other with the same width and scale, as printer
31
+ # drivers write them - are joined into one image. +info[:name]+ is the
32
+ # (first) XObject's resource name (nil for inline images),
33
+ # +info[:page]+ the 0-based page index. +info[:pixel_aspect]+ is the
34
+ # width of an image pixel divided by its height as the page draws it
35
+ # (from the transformation matrix: 0.48 for a 204 x 98 dpi fax drawn
36
+ # at its true size; 1.0 when unknown), +info[:dpi]+ its resolution on
37
+ # the page ([x, y], nil when unknown).
38
+ #
39
+ # With +raw_jpeg: true+, DCT images whose luminance is exactly what
40
+ # JPEG.decode makes of their data (1 or 3 components with the default
41
+ # /Decode, or Adobe CMYK with an inverting one) are not decoded: the
42
+ # block gets the JPEG data (a binary String) in place of the Image,
43
+ # with +info[:format]+ :jpeg and the size of the JPEG frame, so that a
44
+ # caller can decode it at reduced size first (JPEG.decode(data,
45
+ # scale:)). JPEG.decode of such data may still raise ImageError.
46
+ #
47
+ # Images: 1/2/4/8/16 bits per component; DeviceGray/RGB/CMYK, CalGray,
48
+ # CalRGB, ICCBased (by /N), Indexed, Separation and DeviceN (tint =
49
+ # darkness), Lab (L*), named colour spaces; stencil masks; /Decode
50
+ # arrays. Filters: FlateDecode and LZWDecode (with PNG and TIFF
51
+ # predictors), RunLengthDecode, ASCIIHexDecode, ASCII85Decode,
52
+ # DCTDecode (JPEG), CCITTFaxDecode and JBIG2Decode (generic regions;
53
+ # symbol, halftone and refinement coding are skipped). JPEG 2000 images
54
+ # are skipped. Colour is reduced to luminance with Image.luma; alpha
55
+ # (/SMask) is ignored.
56
+ #
57
+ # Encrypted documents are decrypted when their user password is empty
58
+ # (standard security handler: RC4, AES-128, AES-256); other encrypted
59
+ # documents raise UnsupportedImageError.
60
+ #
61
+ # Images that cannot be decoded are skipped. Data that is not a PDF
62
+ # raises ImageError; a document without any decodable image raises
63
+ # UnsupportedImageError (ImageError when its images are corrupt).
64
+ module PDF
65
+ # Raised internally for malformed syntax.
66
+ class ParseError < ImageError; end
67
+
68
+ # An indirect reference "num gen R".
69
+ Ref = Data.define(:num, :gen)
70
+ # A bare keyword or operator met while parsing an object.
71
+ Keyword = Data.define(:word)
72
+ # A stream object: its dictionary and raw (encoded, possibly
73
+ # encrypted) data.
74
+ Stream = Struct.new(:dict, :raw, :num, :gen)
75
+ # A page: its dictionary and its (possibly inherited) resources; or,
76
+ # for documents without usable pages, a lone image stream.
77
+ Page = Struct.new(:dict, :resources, :orphan)
78
+ # An image found on a page: an image XObject (+stream+) or an inline
79
+ # image (+data+), with the resources that apply to it and the
80
+ # current transformation matrix it was drawn with.
81
+ ImageSource = Struct.new(:dict, :stream, :data, :resources, :name, :ctm)
82
+ # A DCT image handed over undecoded (each_image with raw_jpeg: true):
83
+ # the JPEG data and its frame size.
84
+ RawJpeg = Struct.new(:data, :width, :height)
85
+
86
+ # Largest decoded stream we produce for anything but image samples.
87
+ STREAM_LIMIT = 1 << 26
88
+ # Largest decoded image sample buffer.
89
+ SAMPLE_LIMIT = 1 << 28
90
+
91
+ # Errors that garbage input could trigger deep inside the decoders.
92
+ INTERNAL_ERRORS = [NoMethodError, TypeError, ArgumentError, RangeError, IndexError, ZeroDivisionError,
93
+ EncodingError, RegexpError, Zlib::Error].freeze
94
+
95
+ class << self
96
+ # Yields each raster image of the document with an info Hash
97
+ # ({page:, name:, width:, height:}), page by page in drawing order.
98
+ # Returns an Enumerator without a block.
99
+ def each_image(bytes, raw_jpeg: false)
100
+ return enum_for(:each_image, bytes, raw_jpeg: raw_jpeg) unless block_given?
101
+
102
+ document = guard { Document.new(binary(bytes)) }
103
+ pages = guard { document.pages }
104
+ failures = []
105
+ count = 0
106
+ pages.each_with_index do |page, index|
107
+ band = nil # the latest image, held back in case the next one continues it
108
+ sources(document, page, failures).each do |source|
109
+ part = decode(document, source, failures, raw_jpeg) or next
110
+ next if band&.join(part, source.ctm)
111
+
112
+ if band && (image = band.image)
113
+ count += 1
114
+ yield image, band.info(index, image)
115
+ end
116
+ band = Band.new([part], source.ctm, source.name)
117
+ end
118
+ next unless band && (image = band.image)
119
+
120
+ count += 1
121
+ yield image, band.info(index, image)
122
+ end
123
+ raise no_images(failures) if count.zero?
124
+
125
+ nil
126
+ end
127
+
128
+ # All raster images of the document (see #each_image).
129
+ def images(bytes)
130
+ each_image(bytes).map { |image, _info| image }
131
+ end
132
+
133
+ private
134
+
135
+ def binary(bytes)
136
+ raise ImageError, "expected a String of PDF data, got #{bytes.class}" unless bytes.is_a?(String)
137
+
138
+ bytes.encoding == Encoding::BINARY ? bytes : bytes.b
139
+ end
140
+
141
+ def guard
142
+ yield
143
+ rescue *INTERNAL_ERRORS => e
144
+ raise ImageError, "corrupt PDF (#{e.class}: #{e.message})"
145
+ end
146
+
147
+ def sources(document, page, failures)
148
+ document.page_images(page)
149
+ rescue Error, *INTERNAL_ERRORS => e
150
+ failures << e
151
+ []
152
+ end
153
+
154
+ def decode(document, source, failures, raw_jpeg)
155
+ ImageDecoder.new(document, source, raw_jpeg).decode
156
+ rescue Error, *INTERNAL_ERRORS => e
157
+ failures << e
158
+ nil
159
+ end
160
+
161
+ def no_images(failures)
162
+ return UnsupportedImageError.new("PDF contains no raster images") if failures.empty?
163
+
164
+ reason = failures.first.message
165
+ if failures.any?(UnsupportedImageError)
166
+ UnsupportedImageError.new("PDF contains no decodable raster image (#{reason})")
167
+ else
168
+ ImageError.new("PDF contains no decodable raster image (#{reason})")
169
+ end
170
+ end
171
+ end
172
+
173
+ # Images drawn directly above or below each other with the same width
174
+ # and scale are bands of one picture (scanner drivers that print
175
+ # through PostScript cut page images into strips), joined so that a
176
+ # code across a band boundary stays readable. +parts+ (Images or
177
+ # RawJpegs) are top to bottom; +ctm+ maps the unit square onto the
178
+ # joined picture.
179
+ Band = Struct.new(:parts, :ctm, :name) do
180
+ # The picture: an Image, the JPEG data of a lone RawJpeg, or nil
181
+ # when a joined RawJpeg cannot be decoded.
182
+ def image
183
+ part = parts.first
184
+ return part.is_a?(RawJpeg) ? part.data : part if parts.size == 1
185
+
186
+ images = parts.map { |p| p.is_a?(RawJpeg) ? JPEG.decode(p.data) : p }
187
+ Image.new(images.first.width, images.sum(&:height), images.map(&:data).join)
188
+ rescue Error
189
+ nil
190
+ end
191
+
192
+ # The info Hash for +image+ (see PDF.each_image).
193
+ def info(page, image)
194
+ info = { page: page, name: name }
195
+ if image.is_a?(String)
196
+ info[:width] = parts.first.width
197
+ info[:height] = parts.first.height
198
+ info[:format] = :jpeg
199
+ else
200
+ info[:width] = image.width
201
+ info[:height] = image.height
202
+ end
203
+ info.merge!(Band.geometry(ctm, info[:width], info[:height]))
204
+ end
205
+
206
+ # {pixel_aspect:, dpi:} of a +width+ x +height+ image drawn with
207
+ # +ctm+ (which maps the unit square onto the drawn image).
208
+ def self.geometry(ctm, width, height)
209
+ a, b, c, d = ctm
210
+ drawn_w = ctm && Math.hypot(a, b) # in points (1/72 inch)
211
+ drawn_h = ctm && Math.hypot(c, d)
212
+ return { pixel_aspect: 1.0, dpi: nil } unless drawn_w&.finite? && drawn_h&.finite? &&
213
+ drawn_w > 1e-6 && drawn_h > 1e-6
214
+
215
+ aspect = (drawn_w / width) / (drawn_h / height)
216
+ aspect = 1.0 unless aspect.between?(0.125, 8.0)
217
+ { pixel_aspect: aspect.round(6), dpi: [(width * 72 / drawn_w).round(2), (height * 72 / drawn_h).round(2)] }
218
+ end
219
+
220
+ # Appends +image+ (drawn with +matrix+) if it continues this band;
221
+ # returns true if it did.
222
+ def join(image, matrix)
223
+ return false unless ctm && matrix && upright?(ctm) && upright?(matrix)
224
+
225
+ width = parts.first.width
226
+ height = parts.sum(&:height)
227
+ a, _, _, d, e, f = ctm
228
+ a2, _, _, d2, e2, f2 = matrix
229
+ row = d / height
230
+ return false unless image.width == width && (height + image.height) * width <= MAX_PIXELS
231
+ # Same horizontal placement (within half a pixel) and row height.
232
+ return false unless (a2 - a).abs <= a * 0.01 && (e2 - e).abs <= a / width / 2
233
+ return false unless (d2 / image.height - row).abs <= row * 0.01
234
+
235
+ if (f2 + d2 - f).abs <= row / 2 # directly below
236
+ parts << image
237
+ self.ctm = [a, 0.0, 0.0, d + d2, e, f2]
238
+ elsif (f + d - f2).abs <= row / 2 # directly above
239
+ parts.unshift(image)
240
+ self.ctm = [a, 0.0, 0.0, d + d2, e, f]
241
+ else
242
+ return false
243
+ end
244
+ true
245
+ end
246
+
247
+ private
248
+
249
+ def upright?(m)
250
+ m[1].abs < 1e-6 && m[2].abs < 1e-6 && m[0].positive? && m[3].positive?
251
+ end
252
+ end
253
+
254
+ # Small conversions shared by the parts below.
255
+ module Util
256
+ module_function
257
+
258
+ def int(value)
259
+ case value
260
+ when Integer then value
261
+ when Float then value.finite? ? value.to_i : nil
262
+ end
263
+ end
264
+
265
+ def positive(value)
266
+ v = int(value)
267
+ v if v&.positive?
268
+ end
269
+
270
+ def number(value)
271
+ case value
272
+ when Integer then value.to_f
273
+ when Float then value.finite? ? value : nil
274
+ end
275
+ end
276
+
277
+ # Big-endian unsigned integer of +width+ bytes at +pos+.
278
+ def be_int(data, pos, width)
279
+ v = 0
280
+ i = 0
281
+ while i < width
282
+ v = (v << 8) | data.getbyte(pos + i)
283
+ i += 1
284
+ end
285
+ v
286
+ end
287
+ end
288
+
289
+ # Tokenizer and object parser (ISO 32000-1, 7.2 and 7.3).
290
+ #
291
+ # Regexps never match unbounded runs: Onigmo keeps a backtracking
292
+ # stack entry (about 50 bytes) per character a quantifier matches,
293
+ # gigabytes for the long runs of white space or token characters in
294
+ # a hostile file or stream. White space and tokens are scanned in
295
+ # bounded steps (the rest of an overlong token is skipped), comments
296
+ # with String#index.
297
+ class Parser
298
+ MAX_DEPTH = 100
299
+ # Array elements and dictionary entries one document may parse
300
+ # (about 100 bytes of memory and 2 us each at worst, e.g. empty
301
+ # strings; a scanned page needs about 30).
302
+ MAX_ELEMENTS = 2_000_000
303
+ NOT_SPACE = /[^\x00\t\n\f\r ]/n
304
+ LINE_END = /[\r\n]/n
305
+ SPACE_RUN = /[\x00\t\n\f\r ]{1,1024}/n
306
+ TOKEN_RUN = %r{[^\x00\t\n\f\r ()<>\[\]{}/%]{1,1024}}n
307
+ NAME_RUN = %r{/[^\x00\t\n\f\r ()<>\[\]{}/%]{0,1024}}n
308
+ # Ends a regular token (white space or a delimiter).
309
+ DELIMITER = %r{[\x00\t\n\f\r ()<>\[\]{}/%]}n
310
+ DELIMITERS = "\x00\t\n\f\r ()<>[]{}/%".b.bytes.freeze
311
+ # Byte -> true for white space / for white space and delimiters.
312
+ SPACE_BYTE = Array.new(256) { |b| "\x00\t\n\f\r ".b.bytes.include?(b) }.freeze
313
+ DELIMITER_BYTE = Array.new(256) { |b| DELIMITERS.include?(b) }.freeze
314
+ INTEGER = /\A[+-]?\d+\z/
315
+ REF_TAIL = %r{[\x00\t\n\f\r ]{1,64}(\d{1,10})[\x00\t\n\f\r ]{1,64}R(?![^\x00\t\n\f\r ()<>\[\]{}/%])}n
316
+ OBJ_HEADER = %r{(\d{1,10})[\x00\t\n\f\r ]{1,64}(\d{1,5})[\x00\t\n\f\r ]{0,64}obj(?![^\x00\t\n\f\r ()<>\[\]{}/%])}n
317
+ STREAM = /stream(?:\r\n|\n|\r)?/
318
+ LITERAL_SPECIAL = /[()\\\r]/n
319
+ ESCAPES = { 0x6E => 0x0A, 0x72 => 0x0D, 0x74 => 0x09, 0x62 => 0x08, 0x66 => 0x0C }.freeze
320
+ HEX_PAIR = /#(\h\h)/
321
+
322
+ # Name token (with its slash) -> Symbol, #xx escapes decoded.
323
+ def self.name_symbol(token)
324
+ raw = token.byteslice(1, token.bytesize - 1)
325
+ raw = raw.gsub(HEX_PAIR) { ::Regexp.last_match(1).hex.chr } if raw.include?("#")
326
+ raw.to_sym
327
+ end
328
+
329
+ # Moves scanner +s+ (over +data+) past white space and comments;
330
+ # returns the next byte (nil at the end).
331
+ def self.skip_space(s, data)
332
+ while true
333
+ s.skip(SPACE_RUN)
334
+ b = data.getbyte(s.pos)
335
+ if b == 0x25
336
+ s.pos = data.index(LINE_END, s.pos) || data.bytesize
337
+ elsif b.nil? || !SPACE_BYTE[b]
338
+ return b
339
+ end
340
+ end
341
+ end
342
+
343
+ # The token +pattern+ (TOKEN_RUN or NAME_RUN) matches at the
344
+ # scanner position, or nil; the rest of an overlong token is skipped.
345
+ def self.scan_token(s, data, pattern)
346
+ token = s.scan(pattern) or return nil
347
+ b = data.getbyte(s.pos)
348
+ s.pos = data.index(DELIMITER, s.pos) || data.bytesize if b && !DELIMITER_BYTE[b]
349
+ token
350
+ end
351
+
352
+ # A shared count of the elements left to parse (see MAX_ELEMENTS).
353
+ def self.budget
354
+ [MAX_ELEMENTS]
355
+ end
356
+
357
+ def initialize(data, budget = Parser.budget)
358
+ @data = data
359
+ @s = StringScanner.new(data)
360
+ @budget = budget
361
+ end
362
+
363
+ def pos
364
+ @s.pos
365
+ end
366
+
367
+ def pos=(value)
368
+ @s.pos = value.clamp(0, @data.bytesize)
369
+ end
370
+
371
+ def skip_space
372
+ Parser.skip_space(@s, @data)
373
+ end
374
+
375
+ # The next object: Integer, Float, true, false, nil, Symbol (name),
376
+ # String, Array, Hash (Symbol keys), Ref, or a Keyword.
377
+ def parse(depth = 0)
378
+ s = @s
379
+ data = @data
380
+ case Parser.skip_space(s, data)
381
+ when nil then raise ParseError, "unexpected end of PDF data"
382
+ when 0x2F then Parser.name_symbol(Parser.scan_token(s, data, NAME_RUN))
383
+ when 0x28 then literal_string
384
+ when 0x3C then data.getbyte(s.pos + 1) == 0x3C ? dictionary(depth) : hex_string
385
+ when 0x5B then array(depth)
386
+ else
387
+ token = Parser.scan_token(s, data, TOKEN_RUN)
388
+ unless token
389
+ s.pos += 1
390
+ raise ParseError, "unexpected delimiter in PDF object"
391
+ end
392
+ atom(token)
393
+ end
394
+ end
395
+
396
+ # [num, gen, value, stream data offset or nil] for the indirect
397
+ # object at +offset+, or nil when there is none.
398
+ def indirect(offset)
399
+ return nil if offset.negative? || offset >= @data.bytesize
400
+
401
+ s = @s
402
+ s.pos = offset
403
+ skip_space
404
+ return nil unless s.scan(OBJ_HEADER)
405
+
406
+ num = s[1].to_i
407
+ gen = s[2].to_i
408
+ value = parse
409
+ value = nil if value.is_a?(Keyword) # "endobj" of an empty object
410
+ start = nil
411
+ if value.is_a?(Hash)
412
+ skip_space
413
+ start = s.pos if s.scan(STREAM)
414
+ end
415
+ [num, gen, value, start]
416
+ end
417
+
418
+ private
419
+
420
+ def atom(token)
421
+ b = token.getbyte(0)
422
+ if b <= 0x39 && (b >= 0x30 || b == 0x2B || b == 0x2D || b == 0x2E)
423
+ return 0 if token.bytesize > 32
424
+ return token.to_f.then { |f| f.finite? ? f : 0.0 } unless INTEGER.match?(token)
425
+ return 0 if token.bytesize > 20
426
+
427
+ value = token.to_i
428
+ return Ref.new(value, @s[1].to_i) if b >= 0x30 && @s.scan(REF_TAIL)
429
+
430
+ return value
431
+ end
432
+ case token
433
+ when "true" then true
434
+ when "false" then false
435
+ when "null" then nil
436
+ else Keyword.new(token)
437
+ end
438
+ end
439
+
440
+ def literal_string
441
+ s = @s
442
+ data = @data
443
+ size = data.bytesize
444
+ s.pos += 1
445
+ out = String.new(encoding: Encoding::BINARY)
446
+ depth = 1
447
+ loop do
448
+ pos = s.pos
449
+ stop = data.index(LITERAL_SPECIAL, pos) || size
450
+ out << data.byteslice(pos, stop - pos) if stop > pos
451
+ s.pos = stop
452
+ b = data.getbyte(stop) or break
453
+ s.pos += 1
454
+ case b
455
+ when 0x28
456
+ depth += 1
457
+ out << b
458
+ when 0x29
459
+ depth -= 1
460
+ break if depth.zero?
461
+
462
+ out << b
463
+ when 0x0D
464
+ s.pos += 1 if data.getbyte(s.pos) == 0x0A
465
+ out << 0x0A
466
+ else
467
+ escape(out)
468
+ end
469
+ end
470
+ out
471
+ end
472
+
473
+ def escape(out)
474
+ s = @s
475
+ data = @data
476
+ e = data.getbyte(s.pos) or return
477
+ s.pos += 1
478
+ if (mapped = ESCAPES[e])
479
+ out << mapped
480
+ elsif e >= 0x30 && e <= 0x37
481
+ v = e - 0x30
482
+ 2.times do
483
+ d = data.getbyte(s.pos)
484
+ break unless d && d >= 0x30 && d <= 0x37
485
+
486
+ v = (v << 3) | (d - 0x30)
487
+ s.pos += 1
488
+ end
489
+ out << (v & 0xFF)
490
+ elsif e == 0x0D
491
+ s.pos += 1 if data.getbyte(s.pos) == 0x0A
492
+ elsif e != 0x0A
493
+ out << e
494
+ end
495
+ end
496
+
497
+ def hex_string
498
+ s = @s
499
+ data = @data
500
+ start = s.pos + 1
501
+ stop = data.index(">", start) || data.bytesize
502
+ s.pos = stop < data.bytesize ? stop + 1 : stop
503
+ hex = data.byteslice(start, stop - start).delete("^0-9A-Fa-f")
504
+ hex << "0" if hex.bytesize.odd?
505
+ [hex].pack("H*")
506
+ end
507
+
508
+ # Counts one array element or dictionary entry against the budget.
509
+ def count_element
510
+ return if (@budget[0] -= 1) >= 0
511
+
512
+ raise ParseError, "PDF has too many objects (more than #{MAX_ELEMENTS} array elements and dictionary entries)"
513
+ end
514
+
515
+ def array(depth)
516
+ raise ParseError, "PDF objects nested too deeply" if depth > MAX_DEPTH
517
+
518
+ s = @s
519
+ data = @data
520
+ s.pos += 1
521
+ list = []
522
+ loop do
523
+ b = Parser.skip_space(s, data)
524
+ raise ParseError, "unterminated array" unless b
525
+
526
+ if b == 0x5D
527
+ s.pos += 1
528
+ return list
529
+ end
530
+ value = parse(depth + 1)
531
+ raise ParseError, "unexpected #{value.word} in array" if value.is_a?(Keyword)
532
+
533
+ count_element
534
+ list << value
535
+ end
536
+ end
537
+
538
+ def dictionary(depth)
539
+ raise ParseError, "PDF objects nested too deeply" if depth > MAX_DEPTH
540
+
541
+ s = @s
542
+ data = @data
543
+ s.pos += 2
544
+ dict = {}
545
+ loop do
546
+ b = Parser.skip_space(s, data)
547
+ raise ParseError, "unterminated dictionary" unless b
548
+
549
+ if b == 0x3E
550
+ s.pos += data.getbyte(s.pos + 1) == 0x3E ? 2 : 1
551
+ return dict
552
+ end
553
+ raise ParseError, "dictionary key is not a name" unless b == 0x2F
554
+
555
+ key = Parser.name_symbol(Parser.scan_token(s, data, NAME_RUN))
556
+ value = parse(depth + 1)
557
+ raise ParseError, "unexpected #{value.word} in dictionary" if value.is_a?(Keyword)
558
+
559
+ count_element
560
+ dict[key] = value
561
+ end
562
+ end
563
+ end
564
+
565
+ # Cross-reference data, lazy object loading, decryption and the page
566
+ # tree of one document.
567
+ class Document
568
+ include Util
569
+
570
+ MAX_REF_DEPTH = 32
571
+ MISSING = Object.new.freeze
572
+ # (Regexps match bounded lengths only, see Parser.)
573
+ OBJ_SCAN = %r{(?<!\d)(\d{1,10})[\x00\t\n\f\r ]{1,64}(\d{1,5})[\x00\t\n\f\r ]{1,64}obj(?![^\x00\t\n\f\r ()<>\[\]{}/%])}n
574
+ SUBSECTION = /[\x00\t\n\f\r ]{0,64}(\d{1,10})[\x00\t\n\f\r ]{1,64}(\d{1,10})/n
575
+ XREF_ENTRY = /[\x00\t\n\f\r ]{0,64}(\d{1,10})[\x00\t\n\f\r ]{1,64}(\d{1,5})[\x00\t\n\f\r ]{1,64}([nf])/n
576
+ TRAILER_AHEAD = /[\x00\t\n\f\r ]{0,64}trailer/n
577
+ TRAILER_DICT = /trailer[\x00\t\n\f\r ]{0,64}<</n
578
+ ENDSTREAM = /\A[\x00\t\n\f\r ]*endstream/
579
+ NUMBER = /\d{1,20}/n
580
+ TRAILER_KEYS = %i[Root Encrypt ID Info].freeze
581
+ # Distinct objects a document may register (about 80 bytes each).
582
+ MAX_OBJECTS = 2_000_000
583
+ # Cross-reference entries read in all (tables and streams, possibly
584
+ # the same entries again through crafted /Prev chains).
585
+ MAX_XREF_ENTRIES = 4 * MAX_OBJECTS
586
+ # Decoded bytes of all content streams (pages, forms) and of all
587
+ # object streams, at least (and twice the file size for large
588
+ # files): a stream may be a decompression bomb, and many pages may
589
+ # draw the same one. Pages beyond the content budget list their
590
+ # image resources instead.
591
+ MIN_STREAM_BUDGET = 1 << 26
592
+
593
+ attr_reader :data, :trailer
594
+
595
+ def initialize(data)
596
+ @data = data
597
+ @header = data.byteslice(0, 1024).index("%PDF-") or raise ImageError, "not a PDF document (no %PDF- header)"
598
+ @budget = Parser.budget
599
+ @parser = Parser.new(data, @budget)
600
+ @content_left = @object_stream_left = [MIN_STREAM_BUDGET, 2 * data.bytesize].max
601
+ @xref_left = MAX_XREF_ENTRIES
602
+ @object_pairs_left = MAX_OBJECTS
603
+ @offsets = {}
604
+ @cache = {}
605
+ @loading = {}
606
+ @object_streams = {}
607
+ @rebuilt = false
608
+ @security = nil
609
+ @trailer = read_xref
610
+ rebuild unless @trailer && @trailer[:Root]
611
+ setup_security unless @security
612
+ end
613
+
614
+ # Value of indirect object +num+ (nil when missing or broken).
615
+ def object(num)
616
+ return @cache[num] if @cache.key?(num)
617
+ return nil if @loading[num] || @loading.size > MAX_REF_DEPTH
618
+
619
+ @loading[num] = true
620
+ begin
621
+ value = load_object(num)
622
+ value = load_object(num) if value.equal?(MISSING) && rebuild
623
+ value = nil if value.equal?(MISSING)
624
+ ensure
625
+ @loading.delete(num)
626
+ end
627
+ @cache[num] = value
628
+ end
629
+
630
+ # Follows references to a direct value.
631
+ def resolve(value)
632
+ depth = 0
633
+ while value.is_a?(Ref)
634
+ return nil if (depth += 1) > MAX_REF_DEPTH
635
+
636
+ value = object(value.num)
637
+ end
638
+ value
639
+ end
640
+
641
+ # The resolved value if it is a dictionary (a stream's dictionary
642
+ # does not count), else nil.
643
+ def dict_value(value)
644
+ value = resolve(value)
645
+ value.is_a?(Hash) ? value : nil
646
+ end
647
+
648
+ # [filter names, decode parameter dictionaries] of a stream or image
649
+ # dictionary; abbreviated (inline image) names are expanded and the
650
+ # parameter values resolved.
651
+ def filter_chain(dict)
652
+ filters = resolve(dict[:Filter])
653
+ filters = [filters] unless filters.is_a?(Array)
654
+ filters = filters.filter_map do |f|
655
+ f = resolve(f)
656
+ f.is_a?(Symbol) ? Filters::ABBREVIATIONS.fetch(f, f) : nil
657
+ end
658
+ params = resolve(dict[:DecodeParms] || dict[:DP])
659
+ params = [params] unless params.is_a?(Array)
660
+ params = Array.new(filters.size) do |i|
661
+ p = dict_value(params[i])
662
+ p ? p.transform_values { |v| resolve(v) } : {}
663
+ end
664
+ [filters, params]
665
+ end
666
+
667
+ # Raw stream data, decrypted.
668
+ def stream_bytes(stream)
669
+ raw = stream.raw
670
+ return raw unless @security && stream.num
671
+ return raw if resolve(stream.dict[:Type]) == :XRef || identity_crypt?(stream.dict)
672
+
673
+ @security.decrypt_stream(raw, stream.num, stream.gen)
674
+ end
675
+
676
+ # Stream data with all filters applied (no image codecs).
677
+ def decode_stream(stream, limit = STREAM_LIMIT)
678
+ filters, params = filter_chain(stream.dict)
679
+ Filters.decode(stream_bytes(stream), filters, params, limit)
680
+ end
681
+
682
+ # Decoded content stream, or nil when it cannot be decoded (or the
683
+ # document's CONTENT_BUDGET is spent).
684
+ def content(stream)
685
+ return nil if @content_left <= 0
686
+
687
+ data = decode_stream(stream, @content_left < STREAM_LIMIT ? @content_left : STREAM_LIMIT)
688
+ @content_left -= data.bytesize + 1
689
+ data
690
+ rescue Error, *INTERNAL_ERRORS
691
+ nil
692
+ end
693
+
694
+ # The pages in page-tree order; falls back to all page objects (in
695
+ # file order) and then to all image objects for broken documents.
696
+ def pages
697
+ list = walk_pages
698
+ list = walk_pages if list.empty? && rebuild
699
+ list = scan_pages if list.empty?
700
+ list
701
+ end
702
+
703
+ # ImageSources drawn by +page+ in drawing order.
704
+ def page_images(page)
705
+ return [ImageSource.new(page.orphan.dict, page.orphan, nil, {}, nil, nil)] if page.orphan
706
+
707
+ resources = page.resources
708
+ contents = resolve(page.dict[:Contents])
709
+ streams = (contents.is_a?(Array) ? contents.map { |c| resolve(c) } : [contents]).grep(Stream)
710
+ return [] if streams.empty?
711
+
712
+ parts = streams.filter_map { |stream| content(stream) }
713
+ # Undecodable content: list the page's image resources instead.
714
+ return resource_images(resources) if parts.empty?
715
+
716
+ ContentScanner.new(self).images(parts.join("\n"), resources)
717
+ end
718
+
719
+ private
720
+
721
+ def identity_crypt?(dict)
722
+ filters, params = filter_chain(dict)
723
+ i = filters.index(:Crypt) or return false
724
+ (params[i][:Name] || :Identity) == :Identity
725
+ end
726
+
727
+ def resource_images(resources)
728
+ xobjects = dict_value(dict_value(resources)&.[](:XObject)) or return []
729
+ xobjects.filter_map do |name, ref|
730
+ stream = resolve(ref)
731
+ next unless stream.is_a?(Stream) && resolve(stream.dict[:Subtype]) == :Image
732
+
733
+ ImageSource.new(stream.dict, stream, nil, resources, name.to_s, nil)
734
+ end
735
+ end
736
+
737
+ def walk_pages
738
+ root = dict_value(@trailer && @trailer[:Root]) or return []
739
+ list = []
740
+ visited = {}
741
+ stack = [[root[:Pages], nil]]
742
+ until stack.empty?
743
+ ref, inherited = stack.pop
744
+ node = resolve(ref)
745
+ next unless node.is_a?(Hash)
746
+
747
+ id = ref.is_a?(Ref) ? ref.num : node.object_id
748
+ next if visited[id]
749
+
750
+ visited[id] = true
751
+ resources = node.key?(:Resources) ? node[:Resources] : inherited
752
+ kids = resolve(node[:Kids])
753
+ if kids.is_a?(Array) && node[:Type] != :Page
754
+ kids.reverse_each { |kid| stack << [kid, resources] }
755
+ elsif node[:Type] == :Page || !node.key?(:Kids)
756
+ list << Page.new(node, resources, nil)
757
+ end
758
+ end
759
+ list
760
+ end
761
+
762
+ def scan_pages
763
+ rebuild
764
+ pages = []
765
+ images = []
766
+ each_object do |value|
767
+ if value.is_a?(Hash) && value[:Type] == :Page
768
+ pages << Page.new(value, page_resources(value), nil)
769
+ elsif value.is_a?(Stream) && resolve(value.dict[:Subtype]) == :Image
770
+ images << Page.new(nil, nil, value)
771
+ end
772
+ end
773
+ pages.empty? ? images : pages
774
+ end
775
+
776
+ def page_resources(page)
777
+ node = page
778
+ MAX_REF_DEPTH.times do
779
+ return node[:Resources] if node.key?(:Resources)
780
+
781
+ node = dict_value(node[:Parent]) or return nil
782
+ end
783
+ nil
784
+ end
785
+
786
+ # Yields every object, direct ones in file order, then compressed ones.
787
+ def each_object
788
+ direct, compressed = @offsets.partition { |_num, entry| entry.is_a?(Integer) }
789
+ direct.sort_by! { |_num, offset| offset }
790
+ (direct + compressed).each { |num, _entry| yield object(num) }
791
+ end
792
+
793
+ # --- objects -------------------------------------------------------
794
+
795
+ def load_object(num)
796
+ entry = @offsets[num]
797
+ case entry
798
+ when Integer then load_direct(num, entry)
799
+ when Array then load_compressed(num, entry[0], entry[1])
800
+ else MISSING
801
+ end
802
+ end
803
+
804
+ def load_direct(num, offset)
805
+ positions = [offset]
806
+ positions << offset + @header if @header.positive?
807
+ positions.each do |pos|
808
+ parsed = begin
809
+ @parser.indirect(pos)
810
+ rescue ParseError
811
+ nil
812
+ end
813
+ next unless parsed && parsed[0] == num
814
+
815
+ _, gen, value, start = parsed
816
+ value = build_stream(value, start, num, gen) if start
817
+ value = decrypt_strings(value, num, gen) if @security && num != @encrypt_num
818
+ return value
819
+ end
820
+ MISSING
821
+ end
822
+
823
+ def load_compressed(num, stream_num, index)
824
+ container = object_stream(stream_num) or return MISSING
825
+ by_num, pairs, parser, first = container
826
+ pair = pairs[index]
827
+ offset = pair && pair[0] == num ? pair[1] : by_num[num]
828
+ return MISSING unless offset
829
+
830
+ parser.pos = first + offset
831
+ value = parser.parse
832
+ value.is_a?(Keyword) ? nil : value
833
+ rescue ParseError
834
+ MISSING
835
+ end
836
+
837
+ # [{num => offset}, [[num, offset], ...], parser, first] of object
838
+ # stream +num+, or nil.
839
+ def object_stream(num)
840
+ return @object_streams[num] if @object_streams.key?(num)
841
+
842
+ @object_streams[num] = nil
843
+ stream = object(num)
844
+ return nil unless stream.is_a?(Stream)
845
+
846
+ first = int(resolve(stream.dict[:First])) or return nil
847
+ return nil if @object_stream_left <= 0
848
+
849
+ data = decode_stream(stream, @object_stream_left < STREAM_LIMIT ? @object_stream_left : STREAM_LIMIT)
850
+ @object_stream_left -= data.bytesize + 1
851
+ return nil if first.negative? || first > data.bytesize
852
+
853
+ # At most N pairs, and MAX_OBJECTS pairs in all object streams.
854
+ count = (int(resolve(stream.dict[:N])) || 0).clamp(0, @object_pairs_left)
855
+ numbers = []
856
+ header = StringScanner.new(data.byteslice(0, first))
857
+ numbers << header.matched.to_i while numbers.size < 2 * count && header.skip_until(NUMBER)
858
+ count = numbers.size / 2
859
+ @object_pairs_left -= count
860
+ pairs = Array.new(count) { |i| [numbers[2 * i], numbers[2 * i + 1]] }
861
+ by_num = {}
862
+ pairs.each { |n, offset| by_num[n] ||= offset }
863
+ @object_streams[num] = [by_num, pairs, Parser.new(data, @budget), first]
864
+ rescue Error, *INTERNAL_ERRORS
865
+ nil
866
+ end
867
+
868
+ def build_stream(dict, start, num, gen, resolve_length: true)
869
+ length = dict[:Length]
870
+ length = resolve(length) if resolve_length && length.is_a?(Ref)
871
+ size = @data.bytesize
872
+ if length.is_a?(Integer) && length >= 0 && start + length <= size &&
873
+ (start + length == size || ENDSTREAM.match?(@data.byteslice(start + length, 64)))
874
+ raw = @data.byteslice(start, length)
875
+ else
876
+ stop = @data.index("endstream", start) || size
877
+ stop -= 1 if stop > start && @data.getbyte(stop - 1) == 0x0A
878
+ stop -= 1 if stop > start && @data.getbyte(stop - 1) == 0x0D
879
+ raw = @data.byteslice(start, stop - start)
880
+ end
881
+ Stream.new(dict, raw, num, gen)
882
+ end
883
+
884
+ def decrypt_strings(value, num, gen)
885
+ case value
886
+ when String then @security.decrypt_string(value, num, gen)
887
+ when Array then value.map! { |v| decrypt_strings(v, num, gen) }
888
+ when Hash then value.transform_values! { |v| decrypt_strings(v, num, gen) }
889
+ when Stream
890
+ decrypt_strings(value.dict, num, gen)
891
+ value
892
+ else value
893
+ end
894
+ end
895
+
896
+ # --- cross-reference data -----------------------------------------
897
+
898
+ # Merged trailer of all cross-reference sections (newest wins).
899
+ def read_xref
900
+ offset = startxref
901
+ trailers = []
902
+ seen = {}
903
+ while offset.is_a?(Integer) && !seen[offset] && seen.size < 256
904
+ seen[offset] = true
905
+ trailer = read_section(offset) or break
906
+ trailers << trailer
907
+ stream = trailer[:XRefStm]
908
+ if stream.is_a?(Integer) && !seen[stream]
909
+ seen[stream] = true
910
+ read_section(stream)
911
+ end
912
+ offset = trailer[:Prev]
913
+ end
914
+ trailers.reverse.inject { |older, newer| older.merge(newer) }
915
+ end
916
+
917
+ def startxref
918
+ from = [@data.bytesize - 4096, 0].max
919
+ tail = @data.byteslice(from, @data.bytesize - from)
920
+ i = tail.rindex("startxref") or return nil
921
+ tail.byteslice(i + 9, 40)[/\A[\x00\t\n\f\r ]*(\d{1,12})/, 1]&.to_i
922
+ end
923
+
924
+ # Reads the section at +offset+ (or at +offset+ counted from the
925
+ # header); returns its trailer dictionary or nil.
926
+ def read_section(offset)
927
+ positions = [offset]
928
+ positions << offset + @header if @header.positive?
929
+ positions.each do |pos|
930
+ next unless pos.between?(0, @data.bytesize - 1)
931
+
932
+ scanner = StringScanner.new(@data)
933
+ scanner.pos = pos
934
+ Parser.skip_space(scanner, @data)
935
+ trailer = begin
936
+ if scanner.scan(/xref/)
937
+ read_table(scanner)
938
+ else
939
+ read_xref_stream(scanner.pos)
940
+ end
941
+ rescue ParseError
942
+ nil
943
+ end
944
+ return trailer if trailer
945
+ end
946
+ nil
947
+ end
948
+
949
+ def read_table(s)
950
+ until s.match?(TRAILER_AHEAD)
951
+ break unless s.scan(SUBSECTION)
952
+
953
+ start = s[1].to_i
954
+ count = s[2].to_i
955
+ i = 0
956
+ while i < count && (@xref_left -= 1) >= 0 && s.scan(XREF_ENTRY)
957
+ offset = s[1].to_i
958
+ if s[3] == "n"
959
+ register(start + i, offset) if offset.positive?
960
+ elsif i.zero? && start == 1 && s[2] == "65535"
961
+ start = 0 # a common off-by-one: the table really starts at object 0
962
+ end
963
+ i += 1
964
+ end
965
+ end
966
+ s.skip_until(/trailer/) or return nil
967
+ @parser.pos = s.pos
968
+ trailer = @parser.parse
969
+ trailer.is_a?(Hash) ? trailer : nil
970
+ end
971
+
972
+ def read_xref_stream(pos)
973
+ num, gen, dict, start = @parser.indirect(pos)
974
+ return nil unless start && dict.is_a?(Hash) && (dict[:Type] == :XRef || dict.key?(:W))
975
+
976
+ widths = dict[:W]
977
+ return nil unless widths.is_a?(Array) && widths.size >= 3 &&
978
+ widths.first(3).all? { |w| w.is_a?(Integer) && w.between?(0, 8) }
979
+
980
+ w0, w1, w2 = widths
981
+ row = w0 + w1 + w2
982
+ return dict if row.zero?
983
+
984
+ index = dict[:Index].is_a?(Array) ? dict[:Index] : [0, int(dict[:Size]) || 0]
985
+ rows = 0
986
+ index.each_slice(2) { |_, count| rows += count if count.is_a?(Integer) && count.positive? }
987
+ rows = @xref_left if rows > @xref_left
988
+ return dict if rows <= 0
989
+
990
+ stream = build_stream(dict, start, num, gen, resolve_length: false)
991
+ filters, params = filter_chain(dict)
992
+ data = Filters.decode(stream.raw, filters, params, rows * row)
993
+ @xref_left -= data.bytesize / row
994
+ pos = 0
995
+ index.each_slice(2) do |first, count|
996
+ break unless first.is_a?(Integer) && count.is_a?(Integer) && first >= 0
997
+
998
+ pos = read_xref_rows(data, pos, first, count, w0, w1, w2)
999
+ end
1000
+ dict
1001
+ rescue Error, *INTERNAL_ERRORS
1002
+ nil
1003
+ end
1004
+
1005
+ def read_xref_rows(data, pos, first, count, w0, w1, w2)
1006
+ row = w0 + w1 + w2
1007
+ i = 0
1008
+ while i < count && pos + row <= data.bytesize
1009
+ type = w0.zero? ? 1 : be_int(data, pos, w0)
1010
+ field = be_int(data, pos + w0, w1)
1011
+ if type == 1
1012
+ register(first + i, field) if field.positive?
1013
+ elsif type == 2
1014
+ register(first + i, [field, be_int(data, pos + w0 + w1, w2)])
1015
+ end
1016
+ pos += row
1017
+ i += 1
1018
+ end
1019
+ pos
1020
+ end
1021
+
1022
+ # Records where object +num+ is (the first entry found wins), up to
1023
+ # MAX_OBJECTS objects.
1024
+ def register(num, entry)
1025
+ @offsets[num] ||= entry if @offsets.size < MAX_OBJECTS || @offsets.key?(num)
1026
+ end
1027
+
1028
+ # Rebuilds the cross-reference data by scanning the whole file.
1029
+ # Returns true if it ran (only the first call does).
1030
+ def rebuild
1031
+ return false if @rebuilt
1032
+
1033
+ @rebuilt = true
1034
+ offsets = {}
1035
+ scan_objects { |num, offset| offsets[num] = offset if offsets.size < MAX_OBJECTS || offsets.key?(num) }
1036
+ @offsets = offsets
1037
+ @object_streams.clear
1038
+ @cache.clear
1039
+ scanned = scanned_trailer
1040
+ @trailer = (@trailer || {}).merge(scanned) { |_key, old, _new| old }
1041
+ setup_security if @security.nil? && !@securing && @trailer[:Encrypt]
1042
+ register_object_streams
1043
+ unless dict_value(@trailer[:Root])
1044
+ @trailer[:Root] = dict_value(scanned[:Root]) ? scanned[:Root] : find_catalog
1045
+ end
1046
+ true
1047
+ end
1048
+
1049
+ # Yields [num, offset] of every "N G obj", skipping stream data. The
1050
+ # positions of the next keywords are cached so that the scan stays
1051
+ # linear in the file size.
1052
+ def scan_objects
1053
+ data = @data
1054
+ size = data.bytesize
1055
+ pos = 0
1056
+ stream = endobj = other = -1
1057
+ while (m = OBJ_SCAN.match(data, pos))
1058
+ yield m[1].to_i, m.begin(0)
1059
+ pos = m.end(0)
1060
+ stream = data.index("stream", pos) || size if stream < pos
1061
+ endobj = data.index("endobj", pos) || size if endobj < pos
1062
+ other = data.index("obj", pos) || size if other < pos
1063
+ next if stream == size || endobj < stream || other < stream
1064
+
1065
+ stop = data.index("endstream", stream + 6) or next
1066
+ pos = stop + 9
1067
+ end
1068
+ end
1069
+
1070
+ # Registers the objects of all object streams (direct objects win).
1071
+ def register_object_streams
1072
+ objects_containing("/ObjStm").each do |num|
1073
+ stream = object(num)
1074
+ next unless stream.is_a?(Stream) && resolve(stream.dict[:Type]) == :ObjStm
1075
+
1076
+ container = object_stream(num) or next
1077
+ container[1].each_with_index do |(inner, _offset), index|
1078
+ next if @offsets[inner].is_a?(Integer) || (@offsets.size >= MAX_OBJECTS && !@offsets.key?(inner))
1079
+
1080
+ @offsets[inner] = [num, index]
1081
+ end
1082
+ end
1083
+ end
1084
+
1085
+ # Numbers of the direct objects whose text contains +needle+.
1086
+ def objects_containing(needle)
1087
+ sorted = @offsets.select { |_num, entry| entry.is_a?(Integer) }.sort_by { |_num, offset| offset }
1088
+ starts = sorted.map(&:last)
1089
+ found = []
1090
+ pos = 0
1091
+ while (pos = @data.index(needle, pos))
1092
+ i = starts.bsearch_index { |offset| offset > pos } || starts.size
1093
+ found << sorted[i - 1][0] if i.positive?
1094
+ pos += needle.bytesize
1095
+ end
1096
+ found.uniq
1097
+ end
1098
+
1099
+ # Trailer entries from "trailer" dictionaries and cross-reference
1100
+ # stream dictionaries anywhere in the file (later ones win).
1101
+ def scanned_trailer
1102
+ trailer = {}
1103
+ @data.scan(TRAILER_DICT) do
1104
+ @parser.pos = ::Regexp.last_match.end(0) - 2
1105
+ dict = begin
1106
+ @parser.parse
1107
+ rescue ParseError
1108
+ nil
1109
+ end
1110
+ trailer.merge!(dict.slice(*TRAILER_KEYS)) if dict.is_a?(Hash)
1111
+ end
1112
+ objects_containing("/XRef").each do |num|
1113
+ _, _, dict, = @parser.indirect(@offsets[num])
1114
+ trailer.merge!(dict.slice(*TRAILER_KEYS)) if dict.is_a?(Hash) && dict[:Type] == :XRef
1115
+ rescue ParseError
1116
+ next
1117
+ end
1118
+ trailer
1119
+ end
1120
+
1121
+ def find_catalog
1122
+ candidates = objects_containing("/Catalog") + @offsets.select { |_num, entry| entry.is_a?(Array) }.keys
1123
+ num = candidates.find { |n| dict_value(Ref.new(n, 0))&.[](:Type) == :Catalog }
1124
+ num && Ref.new(num, 0)
1125
+ end
1126
+
1127
+ # --- encryption ------------------------------------------------------
1128
+
1129
+ def setup_security
1130
+ encrypt = @trailer && @trailer[:Encrypt]
1131
+ return unless encrypt
1132
+
1133
+ @securing = true
1134
+ @encrypt_num = encrypt.num if encrypt.is_a?(Ref)
1135
+ dict = dict_value(encrypt) or raise UnsupportedImageError, "encrypted PDF with an invalid /Encrypt dictionary"
1136
+ ids = resolve(@trailer[:ID])
1137
+ id = ids.is_a?(Array) ? resolve(ids[0]) : nil
1138
+ @security = Security.new(dict, id.is_a?(String) ? id : "".b, self)
1139
+ @cache.clear # objects loaded so far were not decrypted
1140
+ @object_streams.clear
1141
+ ensure
1142
+ @securing = false
1143
+ end
1144
+ end
1145
+
1146
+ # Standard security handler with an empty user password (ISO 32000-1
1147
+ # 7.6.3, ISO 32000-2 7.6.4): RC4 40-128 bit, AES-128 and AES-256.
1148
+ class Security
1149
+ include Util
1150
+
1151
+ PADDING = [
1152
+ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
1153
+ 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
1154
+ ].pack("C*").freeze
1155
+ PASSWORD = "password-protected PDF (only documents with an empty user password can be decrypted)"
1156
+
1157
+ def initialize(dict, id, document)
1158
+ begin
1159
+ require "digest"
1160
+ require "openssl"
1161
+ rescue LoadError
1162
+ raise UnsupportedImageError, "encrypted PDF (needs the openssl library)"
1163
+ end
1164
+ @doc = document
1165
+ filter = document.resolve(dict[:Filter])
1166
+ raise UnsupportedImageError, "encrypted PDF (security handler #{filter})" unless filter == :Standard
1167
+
1168
+ @v = int(document.resolve(dict[:V])) || 0
1169
+ @r = int(document.resolve(dict[:R])) || 2
1170
+ case @v
1171
+ when 1, 2 then setup_rc4(dict, id, @v == 1 ? 40 : int(document.resolve(dict[:Length])) || 40)
1172
+ when 4 then setup_crypt_filters(dict, id)
1173
+ when 5 then setup_aes256(dict)
1174
+ else raise UnsupportedImageError, "encrypted PDF (unsupported encryption version #{@v})"
1175
+ end
1176
+ rescue Error
1177
+ raise
1178
+ rescue StandardError => e # OpenSSL errors for malformed keys
1179
+ raise UnsupportedImageError, "encrypted PDF (#{e.class}: #{e.message})"
1180
+ end
1181
+
1182
+ def decrypt_string(data, num, gen)
1183
+ decrypt(@string_method, data, num, gen)
1184
+ end
1185
+
1186
+ def decrypt_stream(data, num, gen)
1187
+ decrypt(@stream_method, data, num, gen)
1188
+ end
1189
+
1190
+ private
1191
+
1192
+ def setup_rc4(dict, id, bits)
1193
+ @key = file_key(dict, id, (bits / 8).clamp(5, 16))
1194
+ @stream_method = @string_method = :rc4
1195
+ end
1196
+
1197
+ def setup_crypt_filters(dict, id)
1198
+ filters = @doc.dict_value(dict[:CF]) || {}
1199
+ methods = %i[StmF StrF].map do |key|
1200
+ name = @doc.resolve(dict[key]) || :Identity
1201
+ next :identity if name == :Identity
1202
+
1203
+ cf = @doc.dict_value(filters[name]) or raise UnsupportedImageError, "encrypted PDF (crypt filter #{name})"
1204
+ case @doc.resolve(cf[:CFM])
1205
+ when :V2 then :rc4
1206
+ when :AESV2 then :aes128
1207
+ when :None, nil then :identity
1208
+ else raise UnsupportedImageError, "encrypted PDF (crypt filter method #{cf[:CFM]})"
1209
+ end
1210
+ end
1211
+ @stream_method, @string_method = methods
1212
+ bits = int(@doc.resolve(dict[:Length])) || 128
1213
+ bits *= 8 if bits <= 16 # some writers give bytes
1214
+ bits = 128 if methods.include?(:aes128)
1215
+ @key = file_key(dict, id, (bits / 8).clamp(5, 16))
1216
+ end
1217
+
1218
+ # Algorithms 2 and 4/5: file key from the empty user password,
1219
+ # checked against /U.
1220
+ def file_key(dict, id, length)
1221
+ o = string(dict[:O])
1222
+ u = string(dict[:U])
1223
+ p = int(@doc.resolve(dict[:P])) || 0
1224
+ md5 = Digest::MD5.new
1225
+ md5 << PADDING << o.byteslice(0, 32) << [p & 0xFFFFFFFF].pack("V") << id
1226
+ md5 << "\xFF\xFF\xFF\xFF".b if @r >= 4 && @doc.resolve(dict[:EncryptMetadata]) == false
1227
+ key = md5.digest
1228
+ length = 5 if @r == 2
1229
+ 50.times { key = Digest::MD5.digest(key.byteslice(0, length)) } if @r >= 3
1230
+ key = key.byteslice(0, length)
1231
+ valid = if @r == 2
1232
+ rc4(key, PADDING) == u.byteslice(0, 32)
1233
+ else
1234
+ x = rc4(key, Digest::MD5.digest(PADDING + id))
1235
+ 1.upto(19) { |i| x = rc4(key.bytes.map { |b| b ^ i }.pack("C*"), x) }
1236
+ x == u.byteslice(0, 16)
1237
+ end
1238
+ raise UnsupportedImageError, PASSWORD unless valid
1239
+
1240
+ key
1241
+ end
1242
+
1243
+ def setup_aes256(dict)
1244
+ u = string(dict[:U])
1245
+ ue = string(dict[:UE])
1246
+ raise UnsupportedImageError, "encrypted PDF (invalid /U or /UE)" if u.bytesize < 48 || ue.bytesize < 32
1247
+
1248
+ empty = "".b
1249
+ check = @r >= 6 ? hash_r6(empty, u.byteslice(32, 8)) : Digest::SHA256.digest(u.byteslice(32, 8))
1250
+ raise UnsupportedImageError, PASSWORD unless check == u.byteslice(0, 32)
1251
+
1252
+ intermediate = @r >= 6 ? hash_r6(empty, u.byteslice(40, 8)) : Digest::SHA256.digest(u.byteslice(40, 8))
1253
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").decrypt
1254
+ cipher.key = intermediate
1255
+ cipher.iv = "\0".b * 16
1256
+ cipher.padding = 0
1257
+ @key = cipher.update(ue.byteslice(0, 32)) + cipher.final
1258
+ filters = @doc.dict_value(dict[:CF]) || {}
1259
+ @stream_method, @string_method = %i[StmF StrF].map do |key|
1260
+ name = @doc.resolve(dict[key]) || :Identity
1261
+ cf = @doc.dict_value(filters[name])
1262
+ name == :Identity || (cf && @doc.resolve(cf[:CFM]) == :None) ? :identity : :aes256
1263
+ end
1264
+ end
1265
+
1266
+ # ISO 32000-2 Algorithm 2.B (empty password, no user key data).
1267
+ def hash_r6(password, salt)
1268
+ k = Digest::SHA256.digest(password + salt)
1269
+ i = 0
1270
+ loop do
1271
+ k1 = (password + k) * 64
1272
+ cipher = OpenSSL::Cipher.new("AES-128-CBC").encrypt
1273
+ cipher.key = k.byteslice(0, 16)
1274
+ cipher.iv = k.byteslice(16, 16)
1275
+ cipher.padding = 0
1276
+ e = cipher.update(k1) + cipher.final
1277
+ k = case e.byteslice(0, 16).sum % 3
1278
+ when 0 then Digest::SHA256.digest(e)
1279
+ when 1 then Digest::SHA384.digest(e)
1280
+ else Digest::SHA512.digest(e)
1281
+ end
1282
+ i += 1
1283
+ break if i >= 64 && e.getbyte(-1) <= i - 32
1284
+ end
1285
+ k.byteslice(0, 32)
1286
+ end
1287
+
1288
+ def string(value)
1289
+ value = @doc.resolve(value)
1290
+ value.is_a?(String) ? value : "".b
1291
+ end
1292
+
1293
+ def decrypt(method, data, num, gen)
1294
+ case method
1295
+ when :rc4 then rc4(object_key(num, gen, false), data)
1296
+ when :aes128 then aes_cbc("AES-128-CBC", object_key(num, gen, true), data)
1297
+ when :aes256 then aes_cbc("AES-256-CBC", @key, data)
1298
+ else data
1299
+ end
1300
+ rescue OpenSSL::Cipher::CipherError => e
1301
+ raise ImageError, "cannot decrypt PDF data (#{e.message})"
1302
+ end
1303
+
1304
+ def object_key(num, gen, aes)
1305
+ material = @key + [num].pack("V").byteslice(0, 3) + [gen].pack("V").byteslice(0, 2)
1306
+ material << "sAlT" if aes
1307
+ Digest::MD5.digest(material).byteslice(0, [@key.bytesize + 5, 16].min)
1308
+ end
1309
+
1310
+ def aes_cbc(name, key, data)
1311
+ return "".b if data.bytesize < 32
1312
+
1313
+ cipher = OpenSSL::Cipher.new(name).decrypt
1314
+ cipher.key = key
1315
+ cipher.iv = data.byteslice(0, 16)
1316
+ cipher.padding = 0
1317
+ out = cipher.update(data.byteslice(16, (data.bytesize - 16) & ~15)) + cipher.final
1318
+ pad = out.getbyte(-1) || 0
1319
+ if pad.between?(1, 16) && out.byteslice(-pad, pad).bytes.all?(pad)
1320
+ out.byteslice(0, out.bytesize - pad)
1321
+ else
1322
+ out
1323
+ end
1324
+ end
1325
+
1326
+ def rc4(key, data)
1327
+ @openssl_rc4 = openssl_rc4? if @openssl_rc4.nil?
1328
+ return RC4.new(key).process(data) unless @openssl_rc4
1329
+
1330
+ cipher = OpenSSL::Cipher.new("RC4").decrypt
1331
+ cipher.key_len = key.bytesize
1332
+ cipher.key = key
1333
+ cipher.update(data) + cipher.final
1334
+ end
1335
+
1336
+ # OpenSSL 3 only offers RC4 with its legacy provider loaded.
1337
+ def openssl_rc4?
1338
+ OpenSSL::Cipher.new("RC4")
1339
+ true
1340
+ rescue OpenSSL::Cipher::CipherError, RuntimeError
1341
+ false
1342
+ end
1343
+
1344
+ # Pure-Ruby RC4 (processed in chunks so that YJIT compiles the loop).
1345
+ class RC4
1346
+ def initialize(key)
1347
+ s = (0..255).to_a
1348
+ j = 0
1349
+ n = key.bytesize
1350
+ 256.times do |i|
1351
+ j = (j + s[i] + key.getbyte(i % n)) & 255
1352
+ s[i], s[j] = s[j], s[i]
1353
+ end
1354
+ @s = s
1355
+ @i = 0
1356
+ @j = 0
1357
+ end
1358
+
1359
+ def process(data)
1360
+ n = data.bytesize
1361
+ out = "\0".b * n
1362
+ pos = 0
1363
+ while pos < n
1364
+ stop = [pos + 4096, n].min
1365
+ chunk(data, out, pos, stop)
1366
+ pos = stop
1367
+ end
1368
+ out
1369
+ end
1370
+
1371
+ private
1372
+
1373
+ def chunk(data, out, k, stop)
1374
+ s = @s
1375
+ i = @i
1376
+ j = @j
1377
+ while k < stop
1378
+ i = (i + 1) & 255
1379
+ si = s[i]
1380
+ j = (j + si) & 255
1381
+ sj = s[j]
1382
+ s[i] = sj
1383
+ s[j] = si
1384
+ out.setbyte(k, data.getbyte(k) ^ s[(si + sj) & 255])
1385
+ k += 1
1386
+ end
1387
+ @i = i
1388
+ @j = j
1389
+ end
1390
+ end
1391
+ end
1392
+
1393
+ # Stream filters (ISO 32000-1 7.4) other than the image codecs.
1394
+ module Filters
1395
+ extend Util
1396
+
1397
+ ABBREVIATIONS = {
1398
+ AHx: :ASCIIHexDecode, A85: :ASCII85Decode, LZW: :LZWDecode, Fl: :FlateDecode,
1399
+ RL: :RunLengthDecode, CCF: :CCITTFaxDecode, DCT: :DCTDecode
1400
+ }.freeze
1401
+ NONZERO = /[^\x00]/n
1402
+ HEX_DIGITS = "^0-9A-Fa-f"
1403
+ WHITESPACE = "\x00\t\n\f\r "
1404
+
1405
+ class << self
1406
+ # Applies +filters+ in order; the output of the last one is cut
1407
+ # at +limit+ bytes. (Intermediate results are encoded data of the
1408
+ # later filters, which never needs much more room than the result.)
1409
+ def decode(data, filters, params, limit)
1410
+ last = filters.size - 1
1411
+ filters.each_with_index do |filter, i|
1412
+ data = apply(data, filter, params[i] || {}, i == last ? limit : 2 * limit + 4096)
1413
+ end
1414
+ data.bytesize > limit ? data.byteslice(0, limit) : data
1415
+ end
1416
+
1417
+ private
1418
+
1419
+ def apply(data, filter, params, limit)
1420
+ case filter
1421
+ when :FlateDecode then unpredict(inflate(data, predicted_limit(params, limit)), params, limit)
1422
+ when :LZWDecode
1423
+ early = int(params[:EarlyChange]) == 0 ? 0 : 1
1424
+ unpredict(QrReaderKernels.lzw_decode(data, predicted_limit(params, limit), early, false), params, limit)
1425
+ when :RunLengthDecode then run_length(data, limit)
1426
+ when :ASCIIHexDecode then ascii_hex(data)
1427
+ when :ASCII85Decode then ascii85(data)
1428
+ when :Crypt then data
1429
+ else raise UnsupportedImageError, "unsupported PDF filter /#{filter}"
1430
+ end
1431
+ end
1432
+
1433
+ # Room for the PNG predictor's tag bytes.
1434
+ def predicted_limit(params, limit)
1435
+ (int(params[:Predictor]) || 1) >= 10 ? limit * 2 + 64 : limit
1436
+ end
1437
+
1438
+ def inflate(data, limit)
1439
+ inflate_with(Zlib::Inflate.new(Zlib::MAX_WBITS + 32), data, limit) ||
1440
+ inflate_with(Zlib::Inflate.new(-Zlib::MAX_WBITS), data, limit) ||
1441
+ raise(ImageError, "corrupt Flate data")
1442
+ end
1443
+
1444
+ # Inflated data (partial for truncated or damaged streams), nil if
1445
+ # nothing could be inflated.
1446
+ def inflate_with(zstream, data, limit)
1447
+ out = String.new(encoding: Encoding::BINARY)
1448
+ full = false
1449
+ begin
1450
+ zstream.inflate(data) do |chunk|
1451
+ out << chunk
1452
+ full = out.bytesize >= limit
1453
+ break if full
1454
+ end
1455
+ rescue Zlib::Error
1456
+ failed = true
1457
+ end
1458
+ # Output of an unfinished stream is not yielded.
1459
+ out << zstream.flush_next_out.to_s unless full
1460
+ failed && out.empty? ? nil : out
1461
+ ensure
1462
+ zstream.reset unless zstream.closed? || zstream.finished?
1463
+ zstream.close unless zstream.closed?
1464
+ end
1465
+
1466
+ def unpredict(data, params, limit)
1467
+ predictor = int(params[:Predictor]) || 1
1468
+ return data if predictor < 2
1469
+
1470
+ colors = (int(params[:Colors]) || 1).clamp(1, 64)
1471
+ bpc = int(params[:BitsPerComponent]) || 8
1472
+ raise ImageError, "invalid predictor BitsPerComponent #{bpc}" unless [1, 2, 4, 8, 16].include?(bpc)
1473
+
1474
+ columns = int(params[:Columns]) || 1
1475
+ columns = 1 if columns < 1
1476
+ row = (columns * colors * bpc + 7) / 8
1477
+ # (Rows are allocated whole: one longer than the output limit
1478
+ # would be a crafted allocation, never an image.)
1479
+ raise ImageError, "invalid predictor Columns #{columns}" if row > limit + 1
1480
+ if predictor == 2
1481
+ tiff_unpredict(data, row, colors, bpc)
1482
+ else
1483
+ png_unpredict(data, row, [(colors * bpc + 7) / 8, 1].max, limit)
1484
+ end
1485
+ end
1486
+
1487
+ def png_unpredict(data, row, bpp, limit)
1488
+ src_row = row + 1
1489
+ rows = [(data.bytesize + row - 1) / src_row, limit / row + 1].min
1490
+ return "".b if rows <= 0
1491
+
1492
+ missing = rows * src_row - data.bytesize
1493
+ data += "\0".b * missing if missing.positive?
1494
+ out = "\0".b * (rows * row)
1495
+ r = 0
1496
+ while r < rows
1497
+ src = r * src_row
1498
+ png_row(data.getbyte(src), data, src + 1, out, r * row, row, bpp, r.positive?)
1499
+ r += 1
1500
+ end
1501
+ out
1502
+ end
1503
+
1504
+ # Reverses one PNG-filtered row (types 0-4) into +out+ at +dst+.
1505
+ def png_row(type, src, so, out, dst, len, bpp, has_up)
1506
+ case type
1507
+ when 1 then bpp == 1 ? png_sub1(src, so, out, dst, len) : png_sub(src, so, out, dst, len, bpp)
1508
+ when 2
1509
+ return out.bytesplice(dst, len, src.byteslice(so, len)) unless has_up
1510
+
1511
+ nz = src.index(NONZERO, so)
1512
+ return out.bytesplice(dst, len, out.byteslice(dst - len, len)) if nz.nil? || nz >= so + len
1513
+
1514
+ png_up(src, so, out, dst, len)
1515
+ when 3
1516
+ i = 0
1517
+ while i < len
1518
+ left = i >= bpp ? out.getbyte(dst + i - bpp) : 0
1519
+ up = has_up ? out.getbyte(dst - len + i) : 0
1520
+ out.setbyte(dst + i, (src.getbyte(so + i) + ((left + up) >> 1)) & 255)
1521
+ i += 1
1522
+ end
1523
+ when 4
1524
+ i = 0
1525
+ while i < len
1526
+ a = i >= bpp ? out.getbyte(dst + i - bpp) : 0
1527
+ b = has_up ? out.getbyte(dst - len + i) : 0
1528
+ c = has_up && i >= bpp ? out.getbyte(dst - len + i - bpp) : 0
1529
+ p = a + b - c
1530
+ pa = (p - a).abs
1531
+ pb = (p - b).abs
1532
+ pc = (p - c).abs
1533
+ pred = if pa <= pb && pa <= pc then a
1534
+ elsif pb <= pc then b
1535
+ else c
1536
+ end
1537
+ out.setbyte(dst + i, (src.getbyte(so + i) + pred) & 255)
1538
+ i += 1
1539
+ end
1540
+ else
1541
+ out.bytesplice(dst, len, src.byteslice(so, len))
1542
+ end
1543
+ end
1544
+
1545
+ # Sub with one byte per pixel: a running sum.
1546
+ def png_sub1(src, so, out, dst, len)
1547
+ left = 0
1548
+ i = so
1549
+ o = dst
1550
+ stop = so + len
1551
+ while i < stop
1552
+ left = (src.getbyte(i) + left) & 255
1553
+ out.setbyte(o, left)
1554
+ i += 1
1555
+ o += 1
1556
+ end
1557
+ end
1558
+
1559
+ def png_sub(src, so, out, dst, len, bpp)
1560
+ out.bytesplice(dst, bpp, src.byteslice(so, bpp))
1561
+ i = bpp
1562
+ while i < len
1563
+ out.setbyte(dst + i, (src.getbyte(so + i) + out.getbyte(dst + i - bpp)) & 255)
1564
+ i += 1
1565
+ end
1566
+ end
1567
+
1568
+ def png_up(src, so, out, dst, len)
1569
+ i = so
1570
+ o = dst
1571
+ u = dst - len
1572
+ stop = so + len
1573
+ while i < stop
1574
+ out.setbyte(o, (src.getbyte(i) + out.getbyte(u)) & 255)
1575
+ i += 1
1576
+ o += 1
1577
+ u += 1
1578
+ end
1579
+ end
1580
+
1581
+ # TIFF predictor 2: horizontal differencing per component.
1582
+ def tiff_unpredict(data, row, colors, bpc)
1583
+ out = data.dup
1584
+ rows = out.bytesize / row
1585
+ r = 0
1586
+ while r < rows
1587
+ case bpc
1588
+ when 8 then tiff_row8(out, r * row, row, colors)
1589
+ when 16 then tiff_row16(out, r * row, row, colors)
1590
+ else tiff_row_bits(out, r * row, row, colors, bpc)
1591
+ end
1592
+ r += 1
1593
+ end
1594
+ out
1595
+ end
1596
+
1597
+ def tiff_row8(out, off, len, colors)
1598
+ i = colors
1599
+ while i < len
1600
+ out.setbyte(off + i, (out.getbyte(off + i) + out.getbyte(off + i - colors)) & 255)
1601
+ i += 1
1602
+ end
1603
+ end
1604
+
1605
+ def tiff_row16(out, off, len, colors)
1606
+ step = 2 * colors
1607
+ i = step
1608
+ while i + 1 < len
1609
+ v = ((out.getbyte(off + i) << 8) | out.getbyte(off + i + 1)) +
1610
+ ((out.getbyte(off + i - step) << 8) | out.getbyte(off + i - step + 1))
1611
+ out.setbyte(off + i, (v >> 8) & 255)
1612
+ out.setbyte(off + i + 1, v & 255)
1613
+ i += 2
1614
+ end
1615
+ end
1616
+
1617
+ def tiff_row_bits(out, off, len, colors, bpc)
1618
+ mask = (1 << bpc) - 1
1619
+ samples = len * 8 / bpc
1620
+ i = colors
1621
+ while i < samples
1622
+ v = (sample(out, off, i, bpc) + sample(out, off, i - colors, bpc)) & mask
1623
+ bit = i * bpc
1624
+ shift = 8 - bpc - (bit & 7)
1625
+ byte = off + (bit >> 3)
1626
+ out.setbyte(byte, (out.getbyte(byte) & ~(mask << shift)) | (v << shift))
1627
+ i += 1
1628
+ end
1629
+ end
1630
+
1631
+ def sample(data, off, i, bpc)
1632
+ bit = i * bpc
1633
+ (data.getbyte(off + (bit >> 3)) >> (8 - bpc - (bit & 7))) & ((1 << bpc) - 1)
1634
+ end
1635
+
1636
+ def run_length(data, limit)
1637
+ out = String.new(encoding: Encoding::BINARY)
1638
+ pos = 0
1639
+ n = data.bytesize
1640
+ while pos < n && out.bytesize < limit
1641
+ len = data.getbyte(pos)
1642
+ pos += 1
1643
+ if len < 128
1644
+ out << data.byteslice(pos, len + 1)
1645
+ pos += len + 1
1646
+ elsif len > 128
1647
+ break if pos >= n
1648
+
1649
+ out << (data.byteslice(pos, 1) * (257 - len))
1650
+ pos += 1
1651
+ else
1652
+ break
1653
+ end
1654
+ end
1655
+ out
1656
+ end
1657
+
1658
+ def ascii_hex(data)
1659
+ stop = data.index(">")
1660
+ data = data.byteslice(0, stop) if stop
1661
+ hex = data.delete(HEX_DIGITS)
1662
+ hex << "0" if hex.bytesize.odd?
1663
+ [hex].pack("H*")
1664
+ end
1665
+
1666
+ def ascii85(data)
1667
+ stop = data.index("~>")
1668
+ data = data.byteslice(0, stop) if stop
1669
+ data = data.delete(WHITESPACE)
1670
+ data = data.byteslice(2, data.bytesize - 2) if data.start_with?("<~")
1671
+ data = data.gsub("z", "!!!!!") if data.include?("z")
1672
+ bad = data.index(/[^!-u]/)
1673
+ data = data.byteslice(0, bad) if bad
1674
+ full = data.bytesize / 5
1675
+ words = []
1676
+ pos = 0
1677
+ while pos < full
1678
+ stop = [pos + 1024, full].min
1679
+ ascii85_groups(data, pos, stop, words)
1680
+ pos = stop
1681
+ end
1682
+ out = words.pack("N*")
1683
+ rest = data.bytesize - full * 5
1684
+ if rest > 1
1685
+ tail = []
1686
+ ascii85_groups(data.byteslice(full * 5, rest) + ("u" * (5 - rest)), 0, 1, tail)
1687
+ out << tail.pack("N").byteslice(0, rest - 1)
1688
+ end
1689
+ out
1690
+ end
1691
+
1692
+ def ascii85_groups(data, group, stop, words)
1693
+ while group < stop
1694
+ o = group * 5
1695
+ v = data.getbyte(o) - 33
1696
+ v = v * 85 + data.getbyte(o + 1) - 33
1697
+ v = v * 85 + data.getbyte(o + 2) - 33
1698
+ v = v * 85 + data.getbyte(o + 3) - 33
1699
+ v = v * 85 + data.getbyte(o + 4) - 33
1700
+ words << (v & 0xFFFFFFFF)
1701
+ group += 1
1702
+ end
1703
+ end
1704
+ end
1705
+ end
1706
+
1707
+ # Colour spaces reduced to what luminance needs.
1708
+ class ColorSpace
1709
+ attr_reader :kind, :components
1710
+
1711
+ def initialize(kind, components, palette: nil, none: false)
1712
+ @kind = kind
1713
+ @components = components
1714
+ @palette = palette
1715
+ @none = none
1716
+ end
1717
+
1718
+ GRAY = new(:gray, 1)
1719
+ RGB = new(:rgb, 3)
1720
+ CMYK = new(:cmyk, 4)
1721
+ LAB = new(:lab, 3)
1722
+ MASK = new(:gray, 1)
1723
+
1724
+ class << self
1725
+ # Colour space of an image's /ColorSpace value (names are looked
1726
+ # up in the resources); nil when the value is missing.
1727
+ def build(doc, spec, resources, depth = 0)
1728
+ spec = doc.resolve(spec)
1729
+ return nil if spec.nil?
1730
+ raise UnsupportedImageError, "colour spaces nested too deeply" if depth > 8
1731
+
1732
+ family = spec.is_a?(Array) ? doc.resolve(spec[0]) : spec
1733
+ case family
1734
+ when :DeviceGray, :CalGray, :G then GRAY
1735
+ when :DeviceRGB, :CalRGB, :RGB then RGB
1736
+ when :DeviceCMYK, :CMYK then CMYK
1737
+ when :Lab then LAB
1738
+ when :ICCBased then icc(doc, spec, resources, depth)
1739
+ when :Indexed, :I then indexed(doc, spec, resources, depth)
1740
+ when :Separation then new(:separation, 1, none: spec.is_a?(Array) && doc.resolve(spec[1]) == :None)
1741
+ when :DeviceN then device_n(doc, spec)
1742
+ when :Pattern then raise UnsupportedImageError, "image with a pattern colour space"
1743
+ when Symbol
1744
+ named = doc.dict_value(doc.dict_value(resources)&.[](:ColorSpace))&.[](family)
1745
+ raise UnsupportedImageError, "unknown colour space /#{family}" if named.nil?
1746
+
1747
+ build(doc, named, resources, depth + 1)
1748
+ else raise UnsupportedImageError, "invalid colour space"
1749
+ end
1750
+ end
1751
+
1752
+ # Luminance of CMYK ink amounts (0..255), with the formula of the
1753
+ # JPEG decoder (which sees Adobe's inverted CMYK).
1754
+ def cmyk_luma(c, m, y, k)
1755
+ kk = 255 - k
1756
+ Image.luma(((255 - c) * kk + 127) / 255, ((255 - m) * kk + 127) / 255, ((255 - y) * kk + 127) / 255)
1757
+ end
1758
+
1759
+ private
1760
+
1761
+ def icc(doc, spec, resources, depth)
1762
+ stream = spec.is_a?(Array) ? doc.resolve(spec[1]) : nil
1763
+ dict = stream.is_a?(Stream) ? stream.dict : {}
1764
+ case doc.resolve(dict[:N])
1765
+ when 1 then GRAY
1766
+ when 3 then RGB
1767
+ when 4 then CMYK
1768
+ else
1769
+ alternate = dict[:Alternate] or raise UnsupportedImageError, "ICCBased colour space without /N"
1770
+ build(doc, alternate, resources, depth + 1)
1771
+ end
1772
+ end
1773
+
1774
+ def indexed(doc, spec, resources, depth)
1775
+ raise UnsupportedImageError, "invalid Indexed colour space" unless spec.is_a?(Array) && spec.size >= 4
1776
+
1777
+ base = build(doc, spec[1], resources, depth + 1)
1778
+ raise UnsupportedImageError, "invalid Indexed base colour space" if base.nil? || base.kind == :indexed
1779
+
1780
+ hival = Util.int(doc.resolve(spec[2]))&.clamp(0, 255) || 0
1781
+ lookup = doc.resolve(spec[3])
1782
+ lookup = doc.decode_stream(lookup, (hival + 1) * base.components) if lookup.is_a?(Stream)
1783
+ raise UnsupportedImageError, "invalid Indexed lookup table" unless lookup.is_a?(String)
1784
+
1785
+ n = base.components
1786
+ palette = Array.new(hival + 1) { |i| base.luma_at(lookup, i * n) }
1787
+ new(:indexed, 1, palette: palette)
1788
+ end
1789
+
1790
+ def device_n(doc, spec)
1791
+ names = spec.is_a?(Array) ? doc.resolve(spec[1]) : nil
1792
+ unless names.is_a?(Array) && names.size.between?(1, 32)
1793
+ raise UnsupportedImageError, "invalid DeviceN colour space"
1794
+ end
1795
+
1796
+ new(:device_n, names.size, none: names.all? { |n| doc.resolve(n) == :None })
1797
+ end
1798
+ end
1799
+
1800
+ # [min, max] decode range per component.
1801
+ def decode_ranges(decode, bpc)
1802
+ if decode.is_a?(Array) && decode.size >= 2 * @components
1803
+ ranges = decode.first(2 * @components).map { |v| Util.number(v) }
1804
+ return ranges.each_slice(2).to_a unless ranges.include?(nil)
1805
+ end
1806
+ case @kind
1807
+ when :indexed then [[0.0, ((1 << bpc) - 1).to_f]]
1808
+ when :lab then [[0.0, 100.0], [-100.0, 100.0], [-100.0, 100.0]]
1809
+ else [[0.0, 1.0]] * @components
1810
+ end
1811
+ end
1812
+
1813
+ # Per component: sample value (the high byte for 16 bits) ->
1814
+ # component value 0..255 (or palette index).
1815
+ def component_luts(bpc, decode)
1816
+ maxv = ((1 << bpc) - 1).to_f
1817
+ size = bpc == 16 ? 256 : (1 << bpc)
1818
+ step = bpc == 16 ? 257 : 1
1819
+ decode_ranges(decode, bpc).each_with_index.map do |(dmin, dmax), c|
1820
+ Array.new(size) { |s| normalize(c, dmin + (s * step) * (dmax - dmin) / maxv) }
1821
+ end
1822
+ end
1823
+
1824
+ # Single-component spaces: sample value -> luminance.
1825
+ def luma_lut(bpc, decode)
1826
+ component_luts(bpc, decode).first.map { |v| luma1(v) }
1827
+ end
1828
+
1829
+ # True if the decode array inverts every component.
1830
+ def inverted?(decode, bpc = 8)
1831
+ decode_ranges(decode, bpc).all? { |dmin, dmax| dmin > dmax }
1832
+ end
1833
+
1834
+ # Luminance of the 8-bit components at +offset+ of a lookup table.
1835
+ def luma_at(bytes, offset)
1836
+ c = Array.new(@components) { |i| bytes.getbyte(offset + i) || 0 }
1837
+ case @kind
1838
+ when :gray, :lab then c[0]
1839
+ when :rgb then Image.luma(c[0], c[1], c[2])
1840
+ when :cmyk then ColorSpace.cmyk_luma(c[0], c[1], c[2], c[3])
1841
+ else @none ? 255 : 255 - c.max
1842
+ end
1843
+ end
1844
+
1845
+ private
1846
+
1847
+ def normalize(component, value)
1848
+ case @kind
1849
+ when :indexed then value.round.clamp(0, @palette.size - 1)
1850
+ when :lab then component.zero? ? (value * 2.55).round.clamp(0, 255) : 0
1851
+ else (value * 255).round.clamp(0, 255)
1852
+ end
1853
+ end
1854
+
1855
+ def luma1(value)
1856
+ case @kind
1857
+ when :indexed then @palette[value]
1858
+ when :separation, :device_n then @none ? 255 : 255 - value
1859
+ else value
1860
+ end
1861
+ end
1862
+ end
1863
+
1864
+ # Per-row pixel loops (YJIT compiles methods that are called often,
1865
+ # never a loop in a method entered once).
1866
+ module Pixels
1867
+ # Byte -> its four 2-bit samples, as Integers.
1868
+ TWO_BIT = Array.new(256) { |b| [b >> 6, (b >> 4) & 3, (b >> 2) & 3, b & 3] }.freeze
1869
+
1870
+ module_function
1871
+
1872
+ def rgb(src, off, n, out)
1873
+ i = off
1874
+ stop = off + 3 * n
1875
+ while i < stop
1876
+ out << ((306 * src.getbyte(i) + 601 * src.getbyte(i + 1) + 117 * src.getbyte(i + 2) + 0x200) >> 10)
1877
+ i += 3
1878
+ end
1879
+ end
1880
+
1881
+ def cmyk(src, off, n, out)
1882
+ i = off
1883
+ stop = off + 4 * n
1884
+ while i < stop
1885
+ kk = 255 - src.getbyte(i + 3)
1886
+ r = ((255 - src.getbyte(i)) * kk + 127) / 255
1887
+ g = ((255 - src.getbyte(i + 1)) * kk + 127) / 255
1888
+ b = ((255 - src.getbyte(i + 2)) * kk + 127) / 255
1889
+ out << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
1890
+ i += 4
1891
+ end
1892
+ end
1893
+
1894
+ # Every +stride+-th byte (Lab: L*).
1895
+ def first(src, off, n, out, stride)
1896
+ i = off
1897
+ stop = off + stride * n
1898
+ while i < stop
1899
+ out << src.getbyte(i)
1900
+ i += stride
1901
+ end
1902
+ end
1903
+
1904
+ # DeviceN: the darkest tint wins.
1905
+ def device_n(src, off, n, out, stride)
1906
+ i = off
1907
+ stop = off + stride * n
1908
+ while i < stop
1909
+ max = 0
1910
+ j = i
1911
+ e = i + stride
1912
+ while j < e
1913
+ v = src.getbyte(j)
1914
+ max = v if v > max
1915
+ j += 1
1916
+ end
1917
+ out << (255 - max)
1918
+ i += stride
1919
+ end
1920
+ end
1921
+
1922
+ def high_bytes(src, off, n, out)
1923
+ i = off
1924
+ stop = off + 2 * n
1925
+ while i < stop
1926
+ out << src.getbyte(i)
1927
+ i += 2
1928
+ end
1929
+ end
1930
+
1931
+ def two_bit(src, off, nbytes, out, lut)
1932
+ i = off
1933
+ stop = off + nbytes
1934
+ while i < stop
1935
+ b = src.getbyte(i)
1936
+ out << lut[b >> 6] << lut[(b >> 4) & 3] << lut[(b >> 2) & 3] << lut[b & 3]
1937
+ i += 1
1938
+ end
1939
+ end
1940
+
1941
+ # Colour conversion with a separate lookup table per component.
1942
+ def mapped(src, off, n, out, kind, luts)
1943
+ ncomp = luts.size
1944
+ i = off
1945
+ stop = off + ncomp * n
1946
+ c = Array.new(ncomp, 0)
1947
+ while i < stop
1948
+ k = 0
1949
+ while k < ncomp
1950
+ c[k] = luts[k][src.getbyte(i + k)]
1951
+ k += 1
1952
+ end
1953
+ out << case kind
1954
+ when :rgb then Image.luma(c[0], c[1], c[2])
1955
+ when :cmyk then ColorSpace.cmyk_luma(c[0], c[1], c[2], c[3])
1956
+ when :lab then c[0]
1957
+ else 255 - c.max
1958
+ end
1959
+ i += ncomp
1960
+ end
1961
+ end
1962
+ end
1963
+
1964
+ # Runs content streams and collects the images they draw.
1965
+ class ContentScanner
1966
+ MAX_FORM_DEPTH = 12
1967
+ MAX_OPERANDS = 32
1968
+ MAX_STACK = 256
1969
+ IDENTITY = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0].freeze
1970
+ INLINE_KEYS = {
1971
+ BPC: :BitsPerComponent, CS: :ColorSpace, D: :Decode, DP: :DecodeParms, F: :Filter,
1972
+ H: :Height, IM: :ImageMask, I: :Interpolate, W: :Width, L: :Length
1973
+ }.freeze
1974
+ EI = %r{EI(?![^\x00\t\n\f\r ()<>\[\]{}/%])}
1975
+ EI_AFTER_SPACE = %r{[\x00\t\n\f\r ]EI(?![^\x00\t\n\f\r ()<>\[\]{}/%])}
1976
+ TEXTUAL = /\A[\t\n\r\x20-\x7E]*\z/
1977
+ STRING_SPECIAL = /[()\\]/
1978
+
1979
+ def initialize(doc)
1980
+ @doc = doc
1981
+ @images = []
1982
+ @seen = {}
1983
+ @forms = {}
1984
+ end
1985
+
1986
+ def images(content, resources)
1987
+ scan(content, resources, IDENTITY, 0)
1988
+ @images
1989
+ end
1990
+
1991
+ private
1992
+
1993
+ def scan(data, resources, ctm, depth)
1994
+ s = StringScanner.new(data)
1995
+ operands = []
1996
+ stack = []
1997
+ space_run = Parser::SPACE_RUN
1998
+ space_byte = Parser::SPACE_BYTE
1999
+ token_run = Parser::TOKEN_RUN
2000
+ delimiter_byte = Parser::DELIMITER_BYTE
2001
+ while true
2002
+ # (Parser.skip_space, with its common case inlined.)
2003
+ s.skip(space_run)
2004
+ b = data.getbyte(s.pos) or break
2005
+ b = Parser.skip_space(s, data) or break if b == 0x25 || space_byte[b]
2006
+ case b
2007
+ when 0x2F then operand(operands, Parser.scan_token(s, data, Parser::NAME_RUN))
2008
+ when 0x28
2009
+ skip_string(s, data)
2010
+ operand(operands, nil)
2011
+ when 0x3C
2012
+ if data.getbyte(s.pos + 1) == 0x3C
2013
+ s.pos += 2
2014
+ else
2015
+ s.skip_until(/>/) || s.terminate
2016
+ operand(operands, nil)
2017
+ end
2018
+ when 0x3E, 0x5B, 0x5D, 0x7B, 0x7D, 0x29, 0x25 then s.pos += 1
2019
+ else
2020
+ # (Parser.scan_token inlined.)
2021
+ token = s.scan(token_run)
2022
+ n = data.getbyte(s.pos)
2023
+ s.pos = data.index(Parser::DELIMITER, s.pos) || data.bytesize if n && !delimiter_byte[n]
2024
+ if b <= 0x39 && (b >= 0x30 || b == 0x2B || b == 0x2D || b == 0x2E)
2025
+ operands << token
2026
+ operands.shift if operands.size > MAX_OPERANDS
2027
+ next
2028
+ end
2029
+ case token
2030
+ when "cm" then ctm = multiply(operands.last(6).map(&:to_f), ctm) if operands.size >= 6
2031
+ when "q" then stack << ctm if stack.size < MAX_STACK
2032
+ when "Q" then ctm = stack.pop || ctm
2033
+ when "Do" then draw(operands.last, resources, ctm, depth)
2034
+ when "BI" then inline_image(s, data, resources, ctm)
2035
+ end
2036
+ operands.clear
2037
+ end
2038
+ end
2039
+ end
2040
+
2041
+ # Keeps the last MAX_OPERANDS operands (operators take at most 6).
2042
+ def operand(operands, value)
2043
+ operands << value
2044
+ operands.shift if operands.size > MAX_OPERANDS
2045
+ end
2046
+
2047
+ def skip_string(s, data)
2048
+ s.pos += 1
2049
+ depth = 1
2050
+ while depth.positive?
2051
+ unless s.skip_until(STRING_SPECIAL)
2052
+ s.terminate
2053
+ return
2054
+ end
2055
+ case data.getbyte(s.pos - 1)
2056
+ when 0x5C then s.pos += 1 unless s.eos?
2057
+ when 0x28 then depth += 1
2058
+ else depth -= 1
2059
+ end
2060
+ end
2061
+ end
2062
+
2063
+ # m x ctm (both [a b c d e f]).
2064
+ def multiply(m, n)
2065
+ a, b, c, d, e, f = m
2066
+ na, nb, nc, nd, ne, nf = n
2067
+ [a * na + b * nc, a * nb + b * nd, c * na + d * nc, c * nb + d * nd,
2068
+ e * na + f * nc + ne, e * nb + f * nd + nf]
2069
+ end
2070
+
2071
+ def draw(token, resources, ctm, depth)
2072
+ return unless token.is_a?(String)
2073
+
2074
+ name = Parser.name_symbol(token)
2075
+ xobjects = @doc.dict_value(@doc.dict_value(resources)&.[](:XObject)) or return
2076
+ ref = xobjects[name]
2077
+ stream = @doc.resolve(ref)
2078
+ return unless stream.is_a?(Stream)
2079
+
2080
+ key = ref.is_a?(Ref) ? ref : stream.object_id
2081
+ dict = stream.dict
2082
+ subtype = @doc.resolve(dict[:Subtype])
2083
+ if subtype == :Image || (subtype.nil? && dict.key?(:Width))
2084
+ return if @seen[key]
2085
+
2086
+ @seen[key] = true
2087
+ @images << ImageSource.new(dict, stream, nil, resources, name.to_s, ctm)
2088
+ elsif subtype == :Form || subtype.nil?
2089
+ return if depth >= MAX_FORM_DEPTH || @forms[key]
2090
+
2091
+ @forms[key] = true
2092
+ content = @doc.content(stream) or return
2093
+ matrix = @doc.resolve(dict[:Matrix])
2094
+ matrix = matrix.map { |v| Util.number(@doc.resolve(v)) || 0.0 } if matrix.is_a?(Array) && matrix.size == 6
2095
+ ctm = multiply(matrix, ctm) if matrix.is_a?(Array) && matrix.size == 6
2096
+ scan(content, dict.key?(:Resources) ? dict[:Resources] : resources, ctm, depth + 1)
2097
+ end
2098
+ end
2099
+
2100
+ def inline_image(s, data, resources, ctm)
2101
+ parser = Parser.new(data)
2102
+ parser.pos = s.pos
2103
+ dict = {}
2104
+ loop do
2105
+ key = parser.parse
2106
+ break if key.is_a?(Keyword) && key.word == "ID"
2107
+ raise ParseError, "invalid inline image" unless key.is_a?(Symbol) && dict.size < 64
2108
+
2109
+ value = parser.parse
2110
+ raise ParseError, "invalid inline image" if value.is_a?(Keyword)
2111
+
2112
+ dict[INLINE_KEYS.fetch(key, key)] = value
2113
+ end
2114
+ start = parser.pos
2115
+ start += 1 if start < data.bytesize && " \t\n\f\r\x00".include?(data.byteslice(start, 1))
2116
+ stop, after = extent(data, start, dict, resources)
2117
+ s.pos = after
2118
+ @images << ImageSource.new(dict, nil, data.byteslice(start, stop - start), resources, nil, ctm)
2119
+ rescue ParseError
2120
+ s.skip_until(EI) || s.terminate
2121
+ end
2122
+
2123
+ # [end of the image data, position after EI].
2124
+ def extent(data, start, dict, resources)
2125
+ length = Util.int(@doc.resolve(dict[:Length]))
2126
+ if length && length >= 0 && (after = ei_at(data, start + length))
2127
+ return [start + length, after]
2128
+ end
2129
+
2130
+ filters, = @doc.filter_chain(dict)
2131
+ if filters.empty?
2132
+ size = raw_size(dict, resources)
2133
+ return [start + size, after] if size && (after = ei_at(data, start + size))
2134
+ else
2135
+ marker = { ASCIIHexDecode: ">", ASCII85Decode: "~>" }[filters.first]
2136
+ if marker && (stop = data.index(marker, start)) && (after = ei_at(data, stop + marker.bytesize))
2137
+ return [stop + marker.bytesize, after]
2138
+ end
2139
+ end
2140
+ search(data, start)
2141
+ end
2142
+
2143
+ def raw_size(dict, resources)
2144
+ width = Util.positive(@doc.resolve(dict[:Width])) or return nil
2145
+ height = Util.positive(@doc.resolve(dict[:Height])) or return nil
2146
+ mask = @doc.resolve(dict[:ImageMask]) == true
2147
+ bpc = mask ? 1 : Util.positive(@doc.resolve(dict[:BitsPerComponent])) || 8
2148
+ components = mask ? 1 : ColorSpace.build(@doc, dict[:ColorSpace], resources)&.components || 1
2149
+ (width * components * bpc + 7) / 8 * height
2150
+ rescue Error
2151
+ nil
2152
+ end
2153
+
2154
+ # Position after an "EI" at +pos+ (after optional white space).
2155
+ def ei_at(data, pos)
2156
+ return nil if pos > data.bytesize
2157
+
2158
+ pos = data.index(Parser::NOT_SPACE, pos) or return nil
2159
+ return nil unless data.getbyte(pos) == 0x45 && data.getbyte(pos + 1) == 0x49 # EI
2160
+ return nil unless (after = data.getbyte(pos + 2)).nil? || Parser::DELIMITERS.include?(after)
2161
+
2162
+ pos + 2
2163
+ end
2164
+
2165
+ # First " EI" followed by something that looks like content stream
2166
+ # operators (binary image data rarely does).
2167
+ def search(data, start)
2168
+ pos = start
2169
+ while (i = data.index(EI_AFTER_SPACE, pos))
2170
+ after = i + 3
2171
+ return [i, after] if TEXTUAL.match?(data.byteslice(after, 48))
2172
+
2173
+ pos = i + 1
2174
+ end
2175
+ [data.bytesize, data.bytesize]
2176
+ end
2177
+ end
2178
+
2179
+ # Decodes one image (XObject or inline) to a luminance Image.
2180
+ class ImageDecoder
2181
+ include Util
2182
+
2183
+ CODECS = %i[DCTDecode CCITTFaxDecode JBIG2Decode JPXDecode].freeze
2184
+ ALL_BYTES = Image::TR_ESCAPE.call((0..255).to_a)
2185
+ IDENTITY = (0..255).to_a.freeze
2186
+ SOF_MARKERS = [0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF].freeze
2187
+
2188
+ def initialize(doc, source, raw_jpeg = false)
2189
+ @doc = doc
2190
+ @source = source
2191
+ @dict = source.dict
2192
+ @raw_jpeg = raw_jpeg
2193
+ end
2194
+
2195
+ def decode
2196
+ width = positive(value(:Width))
2197
+ height = positive(value(:Height))
2198
+ raise ImageError, "image without a valid /Width and /Height" unless width && height
2199
+ if width * height > MAX_PIXELS
2200
+ raise UnsupportedImageError, "PDF image too large (#{width}x#{height}, limit #{MAX_PIXELS} pixels)"
2201
+ end
2202
+
2203
+ filters, params = @doc.filter_chain(@dict)
2204
+ codec = CODECS.include?(filters.last) ? filters.pop : nil
2205
+ codec_params = codec ? params.pop : nil
2206
+ mask = value(:ImageMask) == true
2207
+ space = mask ? ColorSpace::MASK : ColorSpace.build(@doc, @dict[:ColorSpace], @source.resources)
2208
+ decode = value(:Decode)
2209
+ decode = decode.map { |v| @doc.resolve(v) } if decode.is_a?(Array)
2210
+ data = @source.stream ? @doc.stream_bytes(@source.stream) : @source.data
2211
+
2212
+ case codec
2213
+ when nil
2214
+ bpc = mask ? 1 : int(value(:BitsPerComponent)) || 8
2215
+ raw_image(data, filters, params, width, height, space || ColorSpace::GRAY, bpc, decode)
2216
+ when :DCTDecode
2217
+ dct_image(Filters.decode(data, filters, params, SAMPLE_LIMIT), width, height, space, decode)
2218
+ when :CCITTFaxDecode
2219
+ data = Filters.decode(data, filters, params, SAMPLE_LIMIT)
2220
+ ccitt_image(data, codec_params, width, height, one_component(space), decode)
2221
+ when :JBIG2Decode
2222
+ data = Filters.decode(data, filters, params, SAMPLE_LIMIT)
2223
+ jbig2_image(data, codec_params, width, height, one_component(space), decode)
2224
+ else
2225
+ raise UnsupportedImageError, "JPEG 2000 (JPXDecode) images are not supported"
2226
+ end
2227
+ end
2228
+
2229
+ private
2230
+
2231
+ def value(key)
2232
+ @doc.resolve(@dict[key])
2233
+ end
2234
+
2235
+ def one_component(space)
2236
+ space && space.components == 1 ? space : ColorSpace::GRAY
2237
+ end
2238
+
2239
+ def raw_image(data, filters, params, width, height, space, bpc, decode)
2240
+ raise UnsupportedImageError, "unsupported BitsPerComponent #{bpc}" unless [1, 2, 4, 8, 16].include?(bpc)
2241
+
2242
+ ncomp = space.components
2243
+ stride = (width * ncomp * bpc + 7) / 8
2244
+ # Packed and unpacked (one byte per sample) buffers are both bounded.
2245
+ if stride * height > SAMPLE_LIMIT || width * ncomp * height > SAMPLE_LIMIT
2246
+ raise UnsupportedImageError, "PDF image too large"
2247
+ end
2248
+
2249
+ raw = Filters.decode(data, filters, params, stride * height)
2250
+ raise ImageError, "image has no data" if raw.empty?
2251
+
2252
+ if ncomp == 1
2253
+ pixels, rows = expand(raw, width, height, bpc, space.luma_lut(bpc, decode))
2254
+ else
2255
+ luts = space.component_luts(bpc, decode)
2256
+ uniform = luts.uniq.size == 1 || space.kind == :lab
2257
+ # Uniform: samples are mapped while unpacking; otherwise they
2258
+ # stay raw values and each component is mapped while converting.
2259
+ lut = uniform ? luts.first : Array.new(luts.first.size) { |s| s }
2260
+ samples, rows = expand(raw, width * ncomp, height, bpc, lut)
2261
+ pixels = convert(samples, width, rows, space, uniform ? nil : luts)
2262
+ end
2263
+ pixels << ("\xFF".b * (width * (height - rows))) if rows < height
2264
+ Image.new(width, height, pixels)
2265
+ end
2266
+
2267
+ # One byte per sample, mapped through +lut+ (indexed by the sample
2268
+ # value, or its high byte for 16 bits), for the rows present in
2269
+ # +raw+ (a partial last row is zero-padded). Returns [samples, rows].
2270
+ def expand(raw, samples_per_row, height, bpc, lut)
2271
+ stride = (samples_per_row * bpc + 7) / 8
2272
+ rows = [(raw.bytesize + stride - 1) / stride, height].min
2273
+ need = rows * stride
2274
+ raw = raw.byteslice(0, need) if raw.bytesize > need
2275
+ raw += "\0".b * (need - raw.bytesize) if raw.bytesize < need
2276
+ out = case bpc
2277
+ when 8 then translate(raw, lut)
2278
+ when 16
2279
+ buf = String.new(capacity: samples_per_row * rows, encoding: Encoding::BINARY)
2280
+ rows.times { |r| Pixels.high_bytes(raw, r * stride, samples_per_row, buf) }
2281
+ translate(buf, lut)
2282
+ when 2
2283
+ buf = String.new(capacity: (stride * 4) * rows, encoding: Encoding::BINARY)
2284
+ rows.times { |r| Pixels.two_bit(raw, r * stride, stride, buf, lut) }
2285
+ strip_padding(buf, stride * 4, samples_per_row, rows)
2286
+ else unpack(raw, samples_per_row, stride, rows, bpc, lut)
2287
+ end
2288
+ [out, rows]
2289
+ end
2290
+
2291
+ # 1- and 4-bit samples via unpack ("B"/"H") and String#tr.
2292
+ def unpack(raw, samples_per_row, stride, rows, bpc, lut)
2293
+ format = bpc == 1 ? "B" : "H"
2294
+ chars = bpc == 1 ? "01" : "0123456789abcdef"
2295
+ if (samples_per_row * bpc) % 8 == 0
2296
+ out = raw.unpack1("#{format}*")
2297
+ else
2298
+ out = String.new(capacity: samples_per_row * rows)
2299
+ directive = "#{format}#{samples_per_row}"
2300
+ rows.times { |r| out << raw.unpack1(directive, offset: r * stride) }
2301
+ end
2302
+ out.force_encoding(Encoding::BINARY)
2303
+ out.tr!(chars, Image::TR_ESCAPE.call(lut))
2304
+ out
2305
+ end
2306
+
2307
+ def strip_padding(buf, have, want, rows)
2308
+ return buf if have == want
2309
+
2310
+ out = String.new(capacity: want * rows, encoding: Encoding::BINARY)
2311
+ rows.times { |r| out << buf.byteslice(r * have, want) }
2312
+ out
2313
+ end
2314
+
2315
+ def translate(bytes, lut)
2316
+ return bytes if lut == IDENTITY
2317
+
2318
+ bytes.tr(ALL_BYTES, Image::TR_ESCAPE.call(lut))
2319
+ end
2320
+
2321
+ def convert(samples, width, rows, space, luts)
2322
+ out = String.new(capacity: width * rows, encoding: Encoding::BINARY)
2323
+ ncomp = space.components
2324
+ row = width * ncomp
2325
+ kind = space.kind
2326
+ rows.times do |r|
2327
+ off = r * row
2328
+ if luts then Pixels.mapped(samples, off, width, out, kind, luts)
2329
+ elsif kind == :rgb then Pixels.rgb(samples, off, width, out)
2330
+ elsif kind == :cmyk then Pixels.cmyk(samples, off, width, out)
2331
+ elsif kind == :lab then Pixels.first(samples, off, width, out, ncomp)
2332
+ else Pixels.device_n(samples, off, width, out, ncomp)
2333
+ end
2334
+ end
2335
+ out
2336
+ end
2337
+
2338
+ def dct_image(jpeg, width, height, space, decode)
2339
+ start = jpeg.index("\xFF\xD8".b) or raise ImageError, "DCTDecode data is not a JPEG"
2340
+ jpeg = jpeg.byteslice(start, jpeg.bytesize - start) if start.positive?
2341
+ # A frame much larger than the image dictionary says is corrupt
2342
+ # (and would be expensive to decode).
2343
+ jw, jh = JPEG.dimensions(jpeg)
2344
+ if jw * jh > 4 * width * height + 4096
2345
+ raise ImageError, "JPEG size #{jw}x#{jh} does not match the image (#{width}x#{height})"
2346
+ end
2347
+
2348
+ # Plain images (luminance = JPEG.decode) go undecoded with raw_jpeg.
2349
+ components = jpeg_components(jpeg) || space&.components || 1
2350
+ case components
2351
+ when 1
2352
+ lut = one_component(space).luma_lut(8, decode)
2353
+ return RawJpeg.new(jpeg, jw, jh) if @raw_jpeg && lut == IDENTITY
2354
+
2355
+ image = JPEG.decode(jpeg)
2356
+ lut == IDENTITY ? image : Image.new(image.width, image.height, translate(image.data, lut))
2357
+ when 4
2358
+ # JPEG.decode reads CMYK the Adobe way (stored inverted), which
2359
+ # PDFs declare with /Decode [1 0 1 0 1 0 1 0].
2360
+ cmyk = space&.components == 4 ? space : ColorSpace::CMYK
2361
+ raise UnsupportedImageError, "CMYK JPEG image without an inverting /Decode array" unless cmyk.inverted?(decode)
2362
+
2363
+ @raw_jpeg ? RawJpeg.new(jpeg, jw, jh) : JPEG.decode(jpeg)
2364
+ else
2365
+ rgb = space&.components == 3 ? space : ColorSpace::RGB
2366
+ invert = rgb.kind != :lab && rgb.inverted?(decode)
2367
+ return RawJpeg.new(jpeg, jw, jh) if @raw_jpeg && !invert && components == 3
2368
+
2369
+ image = JPEG.decode(jpeg)
2370
+ invert ? image.invert : image
2371
+ end
2372
+ end
2373
+
2374
+ # Number of components in the JPEG frame header.
2375
+ def jpeg_components(jpeg)
2376
+ pos = 2
2377
+ while pos + 10 <= jpeg.bytesize
2378
+ return nil unless jpeg.getbyte(pos) == 0xFF
2379
+
2380
+ marker = jpeg.getbyte(pos + 1)
2381
+ if marker == 0xFF || marker == 0x01 || marker.between?(0xD0, 0xD7)
2382
+ pos += marker == 0xFF ? 1 : 2
2383
+ next
2384
+ end
2385
+ return jpeg.getbyte(pos + 9) if SOF_MARKERS.include?(marker)
2386
+
2387
+ pos += 2 + ((jpeg.getbyte(pos + 2) << 8) | jpeg.getbyte(pos + 3))
2388
+ end
2389
+ nil
2390
+ end
2391
+
2392
+ def ccitt_image(data, params, width, height, space, decode)
2393
+ params ||= {}
2394
+ rows = positive(params[:Rows])
2395
+ image = CCITT.decode(
2396
+ data,
2397
+ columns: positive(params[:Columns]) || width,
2398
+ rows: rows ? [rows, height].min : height,
2399
+ k: int(params[:K]) || 0,
2400
+ black_is_1: params[:BlackIs1] == true,
2401
+ encoded_byte_align: params[:EncodedByteAlign] == true,
2402
+ end_of_line: params[:EndOfLine] == true
2403
+ )
2404
+ bilevel(image, space.luma_lut(1, decode), width, height)
2405
+ end
2406
+
2407
+ def jbig2_image(data, params, width, height, space, decode)
2408
+ globals = params && params[:JBIG2Globals]
2409
+ globals = @doc.decode_stream(globals) if globals.is_a?(Stream)
2410
+ image = JBIG2.decode(data, globals: globals.is_a?(String) ? globals : nil,
2411
+ max_pixels: 4 * width * height + 65_536)
2412
+ bilevel(image, space.luma_lut(1, decode), width, height)
2413
+ end
2414
+
2415
+ # A 0/255 bilevel image (0 = sample value 0) mapped through +lut+
2416
+ # and fitted to the declared size (missing parts white).
2417
+ def bilevel(image, lut, width, height)
2418
+ pixels = image.data
2419
+ if lut != [0, 255]
2420
+ pixels = pixels.tr("\x00\xFF".b, Image::TR_ESCAPE.call(lut))
2421
+ end
2422
+ return Image.new(width, height, pixels) if image.width == width && image.height == height
2423
+
2424
+ out = String.new(capacity: width * height, encoding: Encoding::BINARY)
2425
+ w = [image.width, width].min
2426
+ pad = "\xFF".b * (width - w)
2427
+ [image.height, height].min.times do |y|
2428
+ out << pixels.byteslice(y * image.width, w) << pad
2429
+ end
2430
+ out << ("\xFF".b * (width * height - out.bytesize))
2431
+ Image.new(width, height, out)
2432
+ end
2433
+ end
2434
+
2435
+ # JBIG2 decoder for PDF's embedded streams (ITU-T T.88): page
2436
+ # information, immediate generic regions (MQ arithmetic coding with
2437
+ # templates 0-3, adaptive pixels and typical prediction, or MMR) and
2438
+ # end-of-stripe segments, composed onto the page with any combination
2439
+ # operator. Text, halftone and refinement regions (symbol coding) raise
2440
+ # UnsupportedImageError: an image is decoded completely or not at all.
2441
+ module JBIG2
2442
+ # MQ coder probability states: [Qe, next MPS, next LPS, switch].
2443
+ QE = [
2444
+ [0x5601, 1, 1, 1], [0x3401, 2, 6, 0], [0x1801, 3, 9, 0], [0x0AC1, 4, 12, 0],
2445
+ [0x0521, 5, 29, 0], [0x0221, 38, 33, 0], [0x5601, 7, 6, 1], [0x5401, 8, 14, 0],
2446
+ [0x4801, 9, 14, 0], [0x3801, 10, 14, 0], [0x3001, 11, 17, 0], [0x2401, 12, 18, 0],
2447
+ [0x1C01, 13, 20, 0], [0x1601, 29, 21, 0], [0x5601, 15, 14, 1], [0x5401, 16, 14, 0],
2448
+ [0x5101, 17, 15, 0], [0x4801, 18, 16, 0], [0x3801, 19, 17, 0], [0x3401, 20, 18, 0],
2449
+ [0x3001, 21, 19, 0], [0x2801, 22, 19, 0], [0x2401, 23, 20, 0], [0x2201, 24, 21, 0],
2450
+ [0x1C01, 25, 22, 0], [0x1801, 26, 23, 0], [0x1601, 27, 24, 0], [0x1401, 28, 25, 0],
2451
+ [0x1201, 29, 26, 0], [0x1101, 30, 27, 0], [0x0AC1, 31, 28, 0], [0x09C1, 32, 29, 0],
2452
+ [0x08A1, 33, 30, 0], [0x0521, 34, 31, 0], [0x0441, 35, 32, 0], [0x02A1, 36, 33, 0],
2453
+ [0x0221, 37, 34, 0], [0x0141, 38, 35, 0], [0x0111, 39, 36, 0], [0x0085, 40, 37, 0],
2454
+ [0x0049, 41, 38, 0], [0x0025, 42, 39, 0], [0x0015, 43, 40, 0], [0x0009, 44, 41, 0],
2455
+ [0x0005, 45, 42, 0], [0x0001, 45, 43, 0], [0x5601, 46, 46, 0]
2456
+ ].freeze
2457
+ # Per state: Qe, and the packed context (state << 1 | mps) after an
2458
+ # MPS or LPS decision, for mps 0 and 1.
2459
+ QE_VALUE = QE.map(&:first).freeze
2460
+ NEXT_MPS = QE.each_with_index.flat_map { |(_, nmps, _, _), _i| [nmps << 1, (nmps << 1) | 1] }.freeze
2461
+ NEXT_LPS = QE.flat_map do |_, _, nlps, switch|
2462
+ [(nlps << 1) | switch, (nlps << 1) | (1 - switch)]
2463
+ end.freeze
2464
+
2465
+ # Generic region templates (T.88 6.2.5.3) as sliding windows over
2466
+ # the rows y-2, y-1 and y: [row y-2 rightmost dx, its width, row
2467
+ # y-1 rightmost dx, its width, row y width], with the nominal
2468
+ # adaptive pixel positions inside the windows; the context bit of
2469
+ # each adaptive pixel; its nominal position; the typical prediction
2470
+ # context.
2471
+ TEMPLATES = [
2472
+ { windows: [2, 5, 3, 7, 4], at_bits: [4, 10, 11, 15], at: [[3, -1], [-3, -1], [2, -2], [-2, -2]],
2473
+ sltp: 0x9B25 },
2474
+ { windows: [2, 4, 3, 6, 3], at_bits: [3], at: [[3, -1]], sltp: 0x0795 },
2475
+ { windows: [1, 3, 2, 5, 2], at_bits: [2], at: [[2, -1]], sltp: 0x00E5 },
2476
+ { windows: [0, 0, 2, 6, 4], at_bits: [4], at: [[2, -1]], sltp: 0x0195 }
2477
+ ].freeze
2478
+ PAD = 4 # zero pixels left and right of each bitmap row
2479
+ BITS = "\x00\x01".b.freeze # bitmap: 1 = black
2480
+ LUMA = "\xFF\x00".b.freeze
2481
+
2482
+ UNSUPPORTED = {
2483
+ 4 => "text region", 6 => "text region", 7 => "text region", 20 => "halftone region",
2484
+ 22 => "halftone region", 23 => "halftone region", 40 => "refinement region",
2485
+ 42 => "refinement region", 43 => "refinement region"
2486
+ }.freeze
2487
+
2488
+ # Decodes an embedded JBIG2 stream (+globals+: the JBIG2Globals
2489
+ # data) to an Image (black 0, white 255). Pages and regions larger
2490
+ # than +max_pixels+ are rejected.
2491
+ def self.decode(data, globals: nil, max_pixels: MAX_PIXELS)
2492
+ Page.new(max_pixels).decode(globals, data)
2493
+ end
2494
+
2495
+ # Segment parsing and page composition.
2496
+ class Page
2497
+ include Util
2498
+
2499
+ def initialize(max_pixels)
2500
+ @max_pixels = max_pixels
2501
+ end
2502
+
2503
+ def decode(globals, data)
2504
+ each_segment(globals) { |type, _body| raise_unsupported(type) } if globals
2505
+ each_segment(data) do |type, body|
2506
+ case type
2507
+ when 48 then page_information(body)
2508
+ when 38, 39 then generic_region(body)
2509
+ when 50 then end_of_stripe(body)
2510
+ when 49, 51 then break
2511
+ else raise_unsupported(type)
2512
+ end
2513
+ end
2514
+ raise ImageError, "JBIG2 data without a page" unless @page
2515
+
2516
+ height = @height || @rows
2517
+ raise ImageError, "empty JBIG2 page" unless height.positive?
2518
+
2519
+ bitmap = @page.byteslice(0, @width * height)
2520
+ Image.new(@width, height, bitmap.tr(BITS, LUMA))
2521
+ end
2522
+
2523
+ private
2524
+
2525
+ def raise_unsupported(type)
2526
+ what = UNSUPPORTED[type] or return
2527
+ raise UnsupportedImageError, "JBIG2 images with symbol coding (#{what}) are not supported"
2528
+ end
2529
+
2530
+ # Yields [type, data] of each segment (T.88 7.2).
2531
+ def each_segment(data)
2532
+ pos = 0
2533
+ size = data.bytesize
2534
+ while pos + 11 <= size
2535
+ number = be_int(data, pos, 4)
2536
+ flags = data.getbyte(pos + 4)
2537
+ type = flags & 0x3F
2538
+ pos += 5
2539
+ count = data.getbyte(pos) >> 5
2540
+ if count == 7
2541
+ return if pos + 4 > size
2542
+
2543
+ count = be_int(data, pos, 4) & 0x1FFFFFFF
2544
+ pos += 4 + (count + 8) / 8
2545
+ else
2546
+ pos += 1
2547
+ end
2548
+ pos += count * (number <= 256 ? 1 : number <= 65_536 ? 2 : 4)
2549
+ pos += flags & 0x40 == 0 ? 1 : 4
2550
+ return if pos + 4 > size
2551
+
2552
+ length = be_int(data, pos, 4)
2553
+ pos += 4
2554
+ length = unknown_length(data, pos) if length == 0xFFFFFFFF
2555
+ length = size - pos if pos + length > size
2556
+ yield type, data.byteslice(pos, length)
2557
+ pos += length
2558
+ end
2559
+ end
2560
+
2561
+ # Data length of an immediate generic region of unknown size:
2562
+ # up to its end marker and row count (T.88 7.2.7).
2563
+ def unknown_length(data, pos)
2564
+ return data.bytesize - pos if pos + 18 > data.bytesize
2565
+
2566
+ mmr = data.getbyte(pos + 17) & 1 == 1
2567
+ marker = mmr ? "\x00\x00".b : "\xFF\xAC".b
2568
+ stop = data.index(marker, pos + 18) or return data.bytesize - pos
2569
+ stop + 6 - pos
2570
+ end
2571
+
2572
+ def page_information(body)
2573
+ raise ImageError, "short JBIG2 page information" if body.bytesize < 19
2574
+
2575
+ width = be_int(body, 0, 4)
2576
+ height = be_int(body, 4, 4)
2577
+ flags = body.getbyte(16)
2578
+ @default = flags[2]
2579
+ @operator = (flags >> 3) & 3
2580
+ @override = flags[6] == 1
2581
+ @height = height == 0xFFFFFFFF ? nil : height
2582
+ @width = width
2583
+ rows = @height || 0 # striped pages of unknown height grow
2584
+ raise ImageError, "invalid JBIG2 page size #{width}x#{height}" unless width.positive?
2585
+ raise UnsupportedImageError, "JBIG2 page too large" if width * [rows, 1].max > @max_pixels
2586
+
2587
+ @rows = rows
2588
+ @page = @default.chr.b * (width * rows)
2589
+ end
2590
+
2591
+ def end_of_stripe(body)
2592
+ return unless @page && @height.nil? && body.bytesize >= 4
2593
+
2594
+ grow(be_int(body, 0, 4) + 1)
2595
+ end
2596
+
2597
+ # Pages of unknown height grow with their stripes.
2598
+ def grow(rows)
2599
+ return if rows <= @rows
2600
+ raise UnsupportedImageError, "JBIG2 page too large" if @width * rows > @max_pixels
2601
+
2602
+ @page << (@default.chr.b * (@width * (rows - @rows)))
2603
+ @rows = rows
2604
+ end
2605
+
2606
+ def generic_region(body)
2607
+ raise ImageError, "JBIG2 region before page information" unless @page
2608
+ raise ImageError, "short JBIG2 generic region" if body.bytesize < 18
2609
+
2610
+ width, height, x, y = body.unpack("N4")
2611
+ x -= 1 << 32 if x >= 1 << 31
2612
+ y -= 1 << 32 if y >= 1 << 31
2613
+ operator = @override ? body.getbyte(16) & 7 : @operator
2614
+ flags = body.getbyte(17)
2615
+ raise UnsupportedImageError, "JBIG2 extended templates are not supported" if flags & 0x10 != 0
2616
+
2617
+ height = be_int(body, body.bytesize - 4, 4) if height == 0xFFFFFFFF && body.bytesize >= 22
2618
+ unless width.positive? && height.positive? && width * height <= @max_pixels
2619
+ raise ImageError, "invalid JBIG2 region size #{width}x#{height}"
2620
+ end
2621
+
2622
+ bitmap = if flags & 1 == 1
2623
+ mmr(body.byteslice(18, body.bytesize - 18), width, height)
2624
+ else
2625
+ template = (flags >> 1) & 3
2626
+ at_count = template.zero? ? 4 : 1
2627
+ at = body.byteslice(18, 2 * at_count).unpack("c*").each_slice(2).to_a
2628
+ raise ImageError, "short JBIG2 generic region" if at.size < at_count
2629
+
2630
+ data = body.byteslice(18 + 2 * at_count, body.bytesize)
2631
+ GenericRegion.new(data, width, height, template, at, flags[3] == 1).decode
2632
+ end
2633
+ grow(y + height) if @height.nil?
2634
+ compose(bitmap, width, height, x, y, operator)
2635
+ end
2636
+
2637
+ # MMR-coded generic region: T.6 with 1 = black.
2638
+ def mmr(data, width, height)
2639
+ image = CCITT.decode(data, columns: width, rows: height, k: -1)
2640
+ bits = image.data.tr(LUMA, BITS)
2641
+ bits << ("\0".b * (width * (height - image.height))) if image.height < height
2642
+ bits
2643
+ end
2644
+
2645
+ def compose(bitmap, width, height, x, y, operator)
2646
+ page = @page
2647
+ pw = @width
2648
+ x0 = [x, 0].max
2649
+ x1 = [x + width, pw].min
2650
+ n = x1 - x0
2651
+ return if n <= 0
2652
+
2653
+ height.times do |ry|
2654
+ py = y + ry
2655
+ next if py.negative?
2656
+ break if py >= @rows
2657
+
2658
+ src = ry * width + (x0 - x)
2659
+ dst = py * pw + x0
2660
+ piece = bitmap.byteslice(src, n)
2661
+ if operator == 4 || (operator.zero? && !page.byteslice(dst, n).include?("\x01"))
2662
+ page.bytesplice(dst, n, piece)
2663
+ elsif operator != 0 || piece.include?("\x01")
2664
+ combine(page, dst, piece, n, operator)
2665
+ end
2666
+ end
2667
+ end
2668
+
2669
+ def combine(page, dst, piece, n, operator)
2670
+ i = 0
2671
+ while i < n
2672
+ a = page.getbyte(dst + i)
2673
+ b = piece.getbyte(i)
2674
+ v = case operator
2675
+ when 0 then a | b
2676
+ when 1 then a & b
2677
+ when 2 then a ^ b
2678
+ else 1 ^ a ^ b
2679
+ end
2680
+ page.setbyte(dst + i, v)
2681
+ i += 1
2682
+ end
2683
+ end
2684
+ end
2685
+
2686
+ # Arithmetic-coded generic region decoding (T.88 6.2.5.7) with the
2687
+ # MQ decoder of Annex E. The bitmap has PAD zero pixels left and
2688
+ # right of each row and two zero rows on top, so that the context
2689
+ # windows never need bounds checks. Past the end of its data the
2690
+ # decoder reads 1 bits; a valid stream needs only a few of them, so
2691
+ # decoding stops (the rest stays white) after FEED_LIMIT.
2692
+ class GenericRegion
2693
+ FEED_LIMIT = 64
2694
+
2695
+ def initialize(data, width, height, template, at, typical)
2696
+ @data = data
2697
+ @size = data.bytesize
2698
+ @width = width
2699
+ @height = height
2700
+ @template = TEMPLATES[template]
2701
+ @at = at
2702
+ @typical = typical
2703
+ @contexts = Array.new(1 << 16, 0)
2704
+ nominal = @template[:at]
2705
+ @patches = at.each_with_index.filter_map do |(dx, dy), i|
2706
+ next if [dx, dy] == nominal[i]
2707
+ raise ImageError, "invalid JBIG2 adaptive pixel" if dy.positive? || (dy.zero? && dx >= 0)
2708
+
2709
+ [dx, dy, @template[:at_bits][i]]
2710
+ end
2711
+ @stride = width + 2 * PAD
2712
+ init_decoder
2713
+ end
2714
+
2715
+ def decode
2716
+ stride = @stride
2717
+ buf = "\0".b * (stride * (@height + 2))
2718
+ ltp = 0
2719
+ sltp = @template[:sltp]
2720
+ @height.times do |y|
2721
+ break if @fed > FEED_LIMIT
2722
+
2723
+ row = (y + 2) * stride + PAD
2724
+ if @typical
2725
+ ltp ^= decode_bit(sltp)
2726
+ if ltp == 1
2727
+ buf.bytesplice(row - PAD, stride, buf.byteslice(row - PAD - stride, stride))
2728
+ next
2729
+ end
2730
+ end
2731
+ decode_row(buf, row, y)
2732
+ end
2733
+ out = String.new(capacity: @width * @height, encoding: Encoding::BINARY)
2734
+ @height.times { |y| out << buf.byteslice((y + 2) * stride + PAD, @width) }
2735
+ out
2736
+ end
2737
+
2738
+ private
2739
+
2740
+ def init_decoder
2741
+ @fed = 0
2742
+ @bp = 0
2743
+ @c = (@data.getbyte(0) || 0xFF) << 16
2744
+ byte_in
2745
+ @c = (@c << 7) & 0xFFFFFFFF
2746
+ @ct -= 7
2747
+ @a = 0x8000
2748
+ end
2749
+
2750
+ def byte_in
2751
+ data = @data
2752
+ bp = @bp
2753
+ if data.getbyte(bp) == 0xFF
2754
+ b1 = data.getbyte(bp + 1)
2755
+ if b1.nil? || b1 > 0x8F
2756
+ @c += 0xFF00
2757
+ @ct = 8
2758
+ @fed += 1
2759
+ else
2760
+ @bp = bp + 1
2761
+ @c += b1 << 9
2762
+ @ct = 7
2763
+ end
2764
+ else
2765
+ @bp = bp += 1
2766
+ if bp < @size
2767
+ @c += data.getbyte(bp) << 8
2768
+ else
2769
+ @c += 0xFF00
2770
+ @fed += 1
2771
+ end
2772
+ @ct = 8
2773
+ end
2774
+ end
2775
+
2776
+ # One decision with context +cx+ (used for typical prediction).
2777
+ def decode_bit(cx)
2778
+ contexts = @contexts
2779
+ state = contexts[cx]
2780
+ qe = QE_VALUE[state >> 1]
2781
+ mps = state & 1
2782
+ a = @a - qe
2783
+ if (@c >> 16) < qe
2784
+ if a < qe
2785
+ bit = mps
2786
+ contexts[cx] = NEXT_MPS[state]
2787
+ else
2788
+ bit = 1 - mps
2789
+ contexts[cx] = NEXT_LPS[state]
2790
+ end
2791
+ a = qe
2792
+ else
2793
+ @c -= qe << 16
2794
+ if a & 0x8000 != 0
2795
+ @a = a
2796
+ return mps
2797
+ end
2798
+ if a < qe
2799
+ bit = 1 - mps
2800
+ contexts[cx] = NEXT_LPS[state]
2801
+ else
2802
+ bit = mps
2803
+ contexts[cx] = NEXT_MPS[state]
2804
+ end
2805
+ end
2806
+ loop do
2807
+ byte_in if @ct.zero?
2808
+ a <<= 1
2809
+ @c = (@c << 1) & 0xFFFFFFFF
2810
+ @ct -= 1
2811
+ break if a & 0x8000 != 0
2812
+ end
2813
+ @a = a
2814
+ bit
2815
+ end
2816
+
2817
+ # Decodes one row at +row+ (the offset of pixel 0) of +buf+. The
2818
+ # context is built from sliding windows over the rows y-2, y-1
2819
+ # and y; adaptive pixels off their nominal positions are patched
2820
+ # in. The MQ decoder is inlined (this loop runs per pixel).
2821
+ def decode_row(buf, row, y)
2822
+ hi2, n2, hi1, n1, n0 = @template[:windows]
2823
+ m0 = (1 << n0) - 1
2824
+ s1 = n0
2825
+ s2 = n0 + n1
2826
+ r2 = row - 2 * @stride + hi2 + 1 # next pixel to enter each window
2827
+ r1 = row - @stride + hi1 + 1
2828
+ # Rows y-2 and y-1 share one window: shifting moves row y-1's
2829
+ # oldest pixel onto row y-2's newest bit, which the mask clears.
2830
+ mask = ((((1 << n2) - 1) << s2) | (((1 << n1) - 1) << s1)) & ~(1 << s1) & ~(1 << s2)
2831
+ above = 0
2832
+ (hi2 - n2 + 1).upto(hi2) { |dx| above |= buf.getbyte(r2 - hi2 - 1 + dx) << (s2 + hi2 - dx) }
2833
+ (hi1 - n1 + 1).upto(hi1) { |dx| above |= buf.getbyte(r1 - hi1 - 1 + dx) << (s1 + hi1 - dx) }
2834
+ row2 = n2.positive?
2835
+ w0 = 0
2836
+ patches = @patches.empty? ? nil : @patches
2837
+ contexts = @contexts
2838
+ qe_value = QE_VALUE
2839
+ next_mps = NEXT_MPS
2840
+ next_lps = NEXT_LPS
2841
+ data = @data
2842
+ size = @size
2843
+ a = @a
2844
+ c = @c
2845
+ ct = @ct
2846
+ bp = @bp
2847
+ fed = @fed
2848
+ width = @width
2849
+ x = 0
2850
+ while x < width
2851
+ cx = above | w0
2852
+ cx = patch(cx, patches, buf, x, y) if patches
2853
+ state = contexts[cx]
2854
+ qe = qe_value[state >> 1]
2855
+ bit = state & 1
2856
+ a -= qe
2857
+ if (c >> 16) < qe
2858
+ if a < qe
2859
+ contexts[cx] = next_mps[state]
2860
+ else
2861
+ bit = 1 - bit
2862
+ contexts[cx] = next_lps[state]
2863
+ end
2864
+ a = qe
2865
+ else
2866
+ c -= qe << 16
2867
+ if a & 0x8000 == 0
2868
+ if a < qe
2869
+ bit = 1 - bit
2870
+ contexts[cx] = next_lps[state]
2871
+ else
2872
+ contexts[cx] = next_mps[state]
2873
+ end
2874
+ end
2875
+ end
2876
+ while a & 0x8000 == 0
2877
+ if ct.zero?
2878
+ if data.getbyte(bp) == 0xFF
2879
+ b1 = data.getbyte(bp + 1)
2880
+ if b1.nil? || b1 > 0x8F
2881
+ c += 0xFF00
2882
+ ct = 8
2883
+ fed += 1
2884
+ else
2885
+ bp += 1
2886
+ c += b1 << 9
2887
+ ct = 7
2888
+ end
2889
+ else
2890
+ bp += 1
2891
+ if bp < size
2892
+ c += data.getbyte(bp) << 8
2893
+ else
2894
+ c += 0xFF00
2895
+ fed += 1
2896
+ end
2897
+ ct = 8
2898
+ end
2899
+ end
2900
+ a <<= 1
2901
+ c = (c << 1) & 0xFFFFFFFF
2902
+ ct -= 1
2903
+ end
2904
+ buf.setbyte(row + x, 1) if bit == 1
2905
+ w0 = ((w0 << 1) | bit) & m0
2906
+ above = ((above << 1) & mask) | (buf.getbyte(r1 + x) << s1)
2907
+ above |= buf.getbyte(r2 + x) << s2 if row2
2908
+ x += 1
2909
+ end
2910
+ @a = a
2911
+ @c = c
2912
+ @ct = ct
2913
+ @bp = bp
2914
+ @fed = fed
2915
+ end
2916
+
2917
+ # Replaces the nominal adaptive pixels in +cx+ by the real ones.
2918
+ def patch(cx, patches, buf, x, y)
2919
+ patches.each do |dx, dy, bit|
2920
+ px = x + dx
2921
+ py = y + dy
2922
+ v = px >= 0 && px < @width && py >= 0 ? buf.getbyte((py + 2) * @stride + PAD + px) : 0
2923
+ cx = (cx & ~(1 << bit)) | (v << bit)
2924
+ end
2925
+ cx
2926
+ end
2927
+ end
2928
+ end
2929
+
2930
+ private_constant :ParseError, :Ref, :Keyword, :Stream, :Page, :ImageSource, :Band, :STREAM_LIMIT, :SAMPLE_LIMIT,
2931
+ :INTERNAL_ERRORS, :Util, :Parser, :Document, :Security, :Filters, :ColorSpace, :Pixels,
2932
+ :ContentScanner, :ImageDecoder, :JBIG2
2933
+ end
2934
+ end
2935
+ end