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,554 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+
5
+ # CCITT fax kernel: Group 3 (ITU-T T.4, 1-D Modified Huffman and 2-D
6
+ # Modified READ) and Group 4 (T.6, MMR) decoding for TIFF, PDF and JBIG2's
7
+ # MMR regions. Kernel files are plain Ruby in the subset that Spinel
8
+ # compiles (see DESIGN.md, "Kernels").
9
+ #
10
+ # Codes are looked up in tables indexed by the next 13 (runs) or 7 (2-D
11
+ # modes) bits, read from adjacent 16-bit words of the data; changing
12
+ # elements live in Integer arrays; rows are built from cached runs and
13
+ # rows equal to the row above are appended as they are.
14
+ module QrReaderKernels
15
+ # Terminating codes for run lengths 0..63.
16
+ CCITT_WHITE_TERMINATING = %w[
17
+ 00110101 000111 0111 1000 1011 1100 1110 1111 10011 10100 00111 01000 001000 000011 110100 110101
18
+ 101010 101011 0100111 0001100 0001000 0010111 0000011 0000100 0101000 0101011 0010011 0100100
19
+ 0011000 00000010 00000011 00011010 00011011 00010010 00010011 00010100 00010101 00010110 00010111
20
+ 00101000 00101001 00101010 00101011 00101100 00101101 00000100 00000101 00001010 00001011 01010010
21
+ 01010011 01010100 01010101 00100100 00100101 01011000 01011001 01011010 01011011 01001010 01001011
22
+ 00110010 00110011 00110100
23
+ ].freeze
24
+ CCITT_BLACK_TERMINATING = %w[
25
+ 0000110111 010 11 10 011 0011 0010 00011 000101 000100 0000100 0000101 0000111 00000100 00000111
26
+ 000011000 0000010111 0000011000 0000001000 00001100111 00001101000 00001101100 00000110111
27
+ 00000101000 00000010111 00000011000 000011001010 000011001011 000011001100 000011001101
28
+ 000001101000 000001101001 000001101010 000001101011 000011010010 000011010011 000011010100
29
+ 000011010101 000011010110 000011010111 000001101100 000001101101 000011011010 000011011011
30
+ 000001010100 000001010101 000001010110 000001010111 000001100100 000001100101 000001010010
31
+ 000001010011 000000100100 000000110111 000000111000 000000100111 000000101000 000001011000
32
+ 000001011001 000000101011 000000101100 000001011010 000001100110 000001100111
33
+ ].freeze
34
+ # Make-up codes for run lengths 64, 128, ..., 1728.
35
+ CCITT_WHITE_MAKEUP = %w[
36
+ 11011 10010 010111 0110111 00110110 00110111 01100100 01100101 01101000 01100111 011001100
37
+ 011001101 011010010 011010011 011010100 011010101 011010110 011010111 011011000 011011001
38
+ 011011010 011011011 010011000 010011001 010011010 011000 010011011
39
+ ].freeze
40
+ CCITT_BLACK_MAKEUP = %w[
41
+ 0000001111 000011001000 000011001001 000001011011 000000110011 000000110100 000000110101
42
+ 0000001101100 0000001101101 0000001001010 0000001001011 0000001001100 0000001001101
43
+ 0000001110010 0000001110011 0000001110100 0000001110101 0000001110110 0000001110111
44
+ 0000001010010 0000001010011 0000001010100 0000001010101 0000001011010 0000001011011
45
+ 0000001100100 0000001100101
46
+ ].freeze
47
+ # Extended make-up codes shared by both colours: 1792, 1856, ..., 2560.
48
+ CCITT_EXTENDED_MAKEUP = %w[
49
+ 00000001000 00000001100 00000001101 000000010010 000000010011 000000010100 000000010101
50
+ 000000010110 000000010111 000000011100 000000011101 000000011110 000000011111
51
+ ].freeze
52
+ # 2-D mode codes: vertical VL3..VR3 (0..6; a1 = b1 + mode - 3), pass (7),
53
+ # horizontal (8), extension (9).
54
+ CCITT_MODE_CODES = %w[0000010 000010 010 1 011 000011 0000011 0001 001 0000001].freeze
55
+
56
+ CCITT_PASS = 7
57
+ CCITT_HORIZONTAL = 8
58
+ # Seven zero bits (an EOL inside a row, or garbage).
59
+ CCITT_ZEROS = 10
60
+
61
+ # Row starts (CcittDecoder#start_row).
62
+ CCITT_ONE_D = 0
63
+ CCITT_TWO_D = 1
64
+ CCITT_DONE = 2
65
+
66
+ # Runs shorter than this come from cached Strings.
67
+ CCITT_CACHED_RUNS = 64
68
+
69
+ # Value of a binary digit String ("0110" -> 6).
70
+ def self.ccitt_code_value(code)
71
+ v = 0
72
+ i = 0
73
+ n = code.bytesize
74
+ while i < n
75
+ v = (v << 1) | (code.getbyte(i) - 48)
76
+ i += 1
77
+ end
78
+ v
79
+ end
80
+
81
+ # Writes (value << 4 | code length) into +table+ (indexed by +bits+ bits)
82
+ # at every index that starts with +code+.
83
+ def self.ccitt_add_code(table, bits, code, value)
84
+ len = code.bytesize
85
+ i = ccitt_code_value(code) << (bits - len)
86
+ stop = i + (1 << (bits - len))
87
+ while i < stop
88
+ table[i] = (value << 4) | len
89
+ i += 1
90
+ end
91
+ end
92
+
93
+ # Lookup table for the next 13 bits: (run << 4 | code length), or -1
94
+ # where no code matches (EOL, garbage).
95
+ def self.ccitt_run_table(terminating, makeup)
96
+ table = Array.new(8192, -1)
97
+ i = 0
98
+ while i < terminating.size
99
+ ccitt_add_code(table, 13, terminating[i], i)
100
+ i += 1
101
+ end
102
+ i = 0
103
+ while i < makeup.size
104
+ ccitt_add_code(table, 13, makeup[i], 64 * (i + 1))
105
+ i += 1
106
+ end
107
+ i = 0
108
+ while i < CCITT_EXTENDED_MAKEUP.size
109
+ ccitt_add_code(table, 13, CCITT_EXTENDED_MAKEUP[i], 1792 + 64 * i)
110
+ i += 1
111
+ end
112
+ table
113
+ end
114
+
115
+ # Lookup table for the next 7 bits: (mode << 4 | code length).
116
+ def self.ccitt_mode_table
117
+ table = Array.new(128, CCITT_ZEROS << 4)
118
+ i = 0
119
+ while i < CCITT_MODE_CODES.size
120
+ ccitt_add_code(table, 7, CCITT_MODE_CODES[i], i)
121
+ i += 1
122
+ end
123
+ table
124
+ end
125
+
126
+ # Strings of 0..CCITT_CACHED_RUNS-1 copies of +byte+.
127
+ def self.ccitt_runs(byte)
128
+ runs = []
129
+ run = String.new(capacity: CCITT_CACHED_RUNS, encoding: Encoding::BINARY)
130
+ i = 0
131
+ while i < CCITT_CACHED_RUNS
132
+ runs << run.dup
133
+ run << byte
134
+ i += 1
135
+ end
136
+ runs
137
+ end
138
+
139
+ CCITT_WHITE_RUNS = ccitt_run_table(CCITT_WHITE_TERMINATING, CCITT_WHITE_MAKEUP)
140
+ CCITT_BLACK_RUNS = ccitt_run_table(CCITT_BLACK_TERMINATING, CCITT_BLACK_MAKEUP)
141
+ CCITT_MODES = ccitt_mode_table
142
+ CCITT_RUNS_FF = ccitt_runs(255)
143
+ CCITT_RUNS_00 = ccitt_runs(0)
144
+
145
+ # Decodes a CCITT stream to +columns+-byte rows (coded white 255, black
146
+ # 0; swapped when +black_is_1+). +k+ < 0: Group 4; 0: Group 3 1-D; > 0:
147
+ # Group 3 2-D. +rows+ > 0: exactly that many rows (white after the end
148
+ # of the data); 0: all rows up to EOFB/RTC or the end of the data, at
149
+ # most +max_rows+ (more raise UnsupportedImageError). +byte_align+: rows
150
+ # start on byte boundaries; +end_of_line+: EOLs precede the rows (only
151
+ # decides whether an unaligned EOL may stand before a byte-aligned row).
152
+ #
153
+ # Damaged rows of EOL-delimited (Group 3) data are replaced by the row
154
+ # above and decoding resumes at the next EOL. Without EOLs an error ends
155
+ # the image there. Raises ImageError when not even the first row decodes.
156
+ # spinel-sample: QrReaderKernels.ccitt_decode("\x26\xA0\x00\x10\x01".b, 8, 0, -1, false, false, false, 100)
157
+ def self.ccitt_decode(data, columns, rows, k, byte_align, end_of_line, black_is_1, max_rows)
158
+ decoder = CcittDecoder.new(data, columns, k, byte_align, end_of_line)
159
+ out = decoder.decode(rows, max_rows, black_is_1)
160
+ raise QrReader::ImageError, "corrupt CCITT data (first row undecodable)" if decoder.failed_at_start
161
+ raise QrReader::ImageError, "CCITT data contains no rows" if out.bytesize == 0
162
+
163
+ out
164
+ end
165
+
166
+ # Decodes one CCITT stream.
167
+ class CcittDecoder
168
+ attr_reader :failed_at_start
169
+
170
+ def initialize(data, columns, k, byte_align, end_of_line)
171
+ @data = data
172
+ @nbits = data.bytesize * 8
173
+ @win = words(data)
174
+ @columns = columns
175
+ @k = k
176
+ @byte_align = byte_align
177
+ @end_of_line = end_of_line
178
+ @pos = 0
179
+ @eols = false
180
+ @failed_at_start = false
181
+ @last = ""
182
+ end
183
+
184
+ # The rows (see QrReaderKernels.ccitt_decode).
185
+ def decode(rows, max_rows, inverted)
186
+ cols = @columns
187
+ bg = filled(inverted ? 0 : 255, cols)
188
+ fg = filled(inverted ? 255 : 0, cols)
189
+ bg_runs = inverted ? CCITT_RUNS_00 : CCITT_RUNS_FF
190
+ fg_runs = inverted ? CCITT_RUNS_FF : CCITT_RUNS_00
191
+ out = String.new(capacity: cols * (rows > 0 ? rows : 64), encoding: Encoding::BINARY)
192
+ limit = rows > 0 ? rows : max_rows
193
+ ref = [cols, cols, cols, cols]
194
+ cur = []
195
+ count = 0
196
+ while count < limit
197
+ mode = start_row(count == 0)
198
+ break if mode == CCITT_DONE
199
+
200
+ ok = mode == CCITT_TWO_D ? decode_2d_row(ref, cur) : decode_1d_row(cur)
201
+ stop = false
202
+ if ok
203
+ push_sentinels(cur, cols)
204
+ elsif @eols
205
+ # Damaged row: repeat the row above, resume at the next EOL.
206
+ copy(ref, cur)
207
+ @pos = find_eol(@pos)
208
+ else
209
+ # No EOLs to resynchronise at: keep the row's start and stop.
210
+ @failed_at_start = true if count == 0
211
+ cur.pop if cur.size.odd?
212
+ push_sentinels(cur, cols)
213
+ stop = true
214
+ end
215
+ out << row_for(cur, ref, bg, fg, bg_runs, fg_runs)
216
+ count += 1
217
+ break if stop
218
+
219
+ swap = ref
220
+ ref = cur
221
+ cur = swap
222
+ cur.clear
223
+ end
224
+ if rows > 0
225
+ while count < rows
226
+ out << bg
227
+ count += 1
228
+ end
229
+ elsif count == limit && start_row(false) != CCITT_DONE
230
+ raise QrReader::UnsupportedImageError, "CCITT image too large (more than #{limit} rows)"
231
+ end
232
+ out
233
+ end
234
+
235
+ private
236
+
237
+ # The data as big-endian 16-bit words plus zero padding: any 13 bits
238
+ # lie within two adjacent words, any 25 bits within three.
239
+ def words(data)
240
+ n = data.bytesize
241
+ count = (n + 1) / 2 + 8
242
+ win = Array.new(count, 0)
243
+ i = 0
244
+ while i < n
245
+ i = words_part(data, win, i, n)
246
+ end
247
+ win
248
+ end
249
+
250
+ # Fills words from byte +i+ on (a part, so that YJIT compiles the loop).
251
+ def words_part(data, win, i, n)
252
+ stop = i + 8192
253
+ stop = n - 1 if stop > n - 1
254
+ while i < stop
255
+ win[i >> 1] = (data.getbyte(i) << 8) | data.getbyte(i + 1)
256
+ i += 2
257
+ end
258
+ if i == n - 1
259
+ win[i >> 1] = data.getbyte(i) << 8
260
+ i += 1
261
+ end
262
+ i
263
+ end
264
+
265
+ def filled(byte, count)
266
+ s = String.new(capacity: count, encoding: Encoding::BINARY)
267
+ i = 0
268
+ while i < count
269
+ s << byte
270
+ i += 1
271
+ end
272
+ s
273
+ end
274
+
275
+ def push_sentinels(cur, cols)
276
+ cur << cols
277
+ cur << cols
278
+ cur << cols
279
+ cur << cols
280
+ end
281
+
282
+ def copy(from, to)
283
+ to.clear
284
+ i = 0
285
+ n = from.size
286
+ while i < n
287
+ to << from[i]
288
+ i += 1
289
+ end
290
+ end
291
+
292
+ def peek(pos, bits)
293
+ w = @win
294
+ k = pos >> 4
295
+ (((w[k] << 32) | (w[k + 1] << 16) | w[k + 2]) >> (48 - bits - (pos & 15))) & ((1 << bits) - 1)
296
+ end
297
+
298
+ # Handles byte alignment, fill bits, EOL codes, EOFB/RTC and the
299
+ # 1-D/2-D tag bit in front of a row. Returns CCITT_ONE_D, CCITT_TWO_D
300
+ # or CCITT_DONE. Group 3 data may start with several EOLs.
301
+ def start_row(first)
302
+ pos = @pos
303
+ return CCITT_DONE if pos >= @nbits
304
+
305
+ if @byte_align && !@end_of_line
306
+ # EOFB/RTC may follow the last row unaligned; two EOLs in a row
307
+ # cannot be mistaken for fill bits and a row.
308
+ return CCITT_DONE if peek(pos, 24) == 0b000000000001_000000000001
309
+
310
+ pos = (pos + 7) & ~7
311
+ end
312
+ eol = false
313
+ # 11 zero bits: an EOL (maybe after fill bits), EOFB/RTC or padding.
314
+ while peek(pos, 12) <= 1
315
+ pos = skip_eol(pos)
316
+ return CCITT_DONE if pos < 0
317
+ # A second EOL right away is EOFB (Group 4) or RTC (Group 3).
318
+ return CCITT_DONE if eol && !(first && @k >= 0)
319
+
320
+ eol = true
321
+ @eols = true
322
+ # In 2-D Group 3 data each EOL of an RTC carries a tag bit.
323
+ pos += 1 if @k > 0 && peek(pos + 1, 12) <= 1
324
+ end
325
+ pos = (pos + 7) & ~7 if @byte_align && !eol
326
+ two_d = @k < 0
327
+ if @k > 0
328
+ two_d = peek(pos, 1) == 0
329
+ pos += 1
330
+ end
331
+ @pos = pos
332
+ two_d ? CCITT_TWO_D : CCITT_ONE_D
333
+ end
334
+
335
+ # Skips zero bits and the 1 bit that ends an EOL. Returns the position
336
+ # after it, or -1 when only zeros remain.
337
+ def skip_eol(pos)
338
+ while pos < @nbits
339
+ bits = peek(pos, 25)
340
+ return pos + 26 - bit_length(bits) if bits != 0
341
+
342
+ # 25 zero bits: continue at the next nonzero byte.
343
+ i = (pos >> 3) + 1
344
+ n = @data.bytesize
345
+ i += 1 while i < n && @data.getbyte(i) == 0
346
+ return -1 if i >= n
347
+
348
+ pos = i * 8
349
+ end
350
+ -1
351
+ end
352
+
353
+ # Bit position of the next EOL code (or of the fill bits before it)
354
+ # at or after +pos+; the end of the data when there is none.
355
+ def find_eol(pos)
356
+ nbits = @nbits
357
+ while pos < nbits
358
+ bits = peek(pos, 12)
359
+ return pos if bits <= 1
360
+
361
+ # No EOL can start before the first 1 bit of this window.
362
+ pos += 13 - bit_length(bits)
363
+ end
364
+ nbits
365
+ end
366
+
367
+ def bit_length(v)
368
+ n = 0
369
+ while v > 0
370
+ v >>= 1
371
+ n += 1
372
+ end
373
+ n
374
+ end
375
+
376
+ # Decodes a Modified Huffman (1-D) row into changing elements. Returns
377
+ # false on a coding error.
378
+ def decode_1d_row(cur)
379
+ pos = @pos
380
+ win = @win
381
+ cols = @columns
382
+ white = CCITT_WHITE_RUNS
383
+ black = CCITT_BLACK_RUNS
384
+ a = 0
385
+ while true
386
+ run = 0
387
+ while true
388
+ k = pos >> 4
389
+ e = white[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
390
+ if e < 0
391
+ @pos = pos
392
+ return false
393
+ end
394
+ pos += e & 15
395
+ e >>= 4
396
+ run += e
397
+ break if e < 64
398
+ end
399
+ a += run
400
+ a = cols if a > cols
401
+ cur << a
402
+ break if a >= cols
403
+
404
+ run = 0
405
+ while true
406
+ k = pos >> 4
407
+ e = black[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
408
+ if e < 0
409
+ @pos = pos
410
+ return false
411
+ end
412
+ pos += e & 15
413
+ e >>= 4
414
+ run += e
415
+ break if e < 64
416
+ end
417
+ a += run
418
+ a = cols if a > cols
419
+ cur << a
420
+ break if a >= cols
421
+ end
422
+ @pos = pos
423
+ true
424
+ end
425
+
426
+ # Decodes a 2-D (READ) row against the reference row's changing
427
+ # elements +ref+ (terminated by sentinels equal to the width). Returns
428
+ # false on a coding error.
429
+ def decode_2d_row(ref, cur)
430
+ pos = @pos
431
+ win = @win
432
+ cols = @columns
433
+ modes = CCITT_MODES
434
+ a0 = -1
435
+ color = 0 # of the run starting at a0; changing element parity
436
+ bi = 0 # index of b1 in ref
437
+ while a0 < cols
438
+ k = pos >> 4
439
+ m = modes[(((win[k] << 16) | win[k + 1]) >> (25 - (pos & 15))) & 0x7F]
440
+ pos += m & 15
441
+ m >>= 4
442
+ if m < CCITT_PASS # vertical: a1 = b1 + (m - 3)
443
+ a1 = ref[bi] + m - 3
444
+ if a1 < a0 || a1 < 0
445
+ @pos = pos
446
+ return false
447
+ end
448
+ a1 = cols if a1 > cols
449
+ cur << a1
450
+ a0 = a1
451
+ color ^= 1
452
+ # b1 for the other colour: next element on the right, or (after
453
+ # VL) possibly the previous one.
454
+ bi += m >= 3 || bi == 0 ? 1 : -1
455
+ bi += 2 while (b = ref[bi]) <= a0 && b < cols
456
+ elsif m == CCITT_PASS
457
+ a0 = ref[bi + 1]
458
+ bi += 2
459
+ bi += 2 while (b = ref[bi]) <= a0 && b < cols
460
+ elsif m == CCITT_HORIZONTAL
461
+ first = color == 0 ? CCITT_WHITE_RUNS : CCITT_BLACK_RUNS
462
+ run1 = 0
463
+ while true
464
+ k = pos >> 4
465
+ e = first[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
466
+ if e < 0
467
+ @pos = pos
468
+ return false
469
+ end
470
+ pos += e & 15
471
+ e >>= 4
472
+ run1 += e
473
+ break if e < 64
474
+ end
475
+ second = color == 0 ? CCITT_BLACK_RUNS : CCITT_WHITE_RUNS
476
+ run2 = 0
477
+ while true
478
+ k = pos >> 4
479
+ e = second[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
480
+ if e < 0
481
+ @pos = pos
482
+ return false
483
+ end
484
+ pos += e & 15
485
+ e >>= 4
486
+ run2 += e
487
+ break if e < 64
488
+ end
489
+ a1 = (a0 < 0 ? 0 : a0) + run1
490
+ a1 = cols if a1 > cols
491
+ a0 = a1 + run2
492
+ a0 = cols if a0 > cols
493
+ cur << a1
494
+ cur << a0
495
+ bi += 2 while (b = ref[bi]) <= a0 && b < cols
496
+ else # extension (uncompressed mode) or an EOL inside the row
497
+ @pos = pos
498
+ return false
499
+ end
500
+ end
501
+ @pos = pos
502
+ true
503
+ end
504
+
505
+ # The row described by the changing elements +cur+ (+ref+: those of
506
+ # the row above, last painted as @last).
507
+ def row_for(cur, ref, bg, fg, bg_runs, fg_runs)
508
+ cols = @columns
509
+ return bg if cur[0] >= cols
510
+ return @last if same(cur, ref)
511
+
512
+ row = String.new(capacity: cols, encoding: Encoding::BINARY)
513
+ a = 0 # start of the next background run
514
+ i = 0
515
+ while (b = cur[i]) < cols
516
+ n = b - a
517
+ if n < CCITT_CACHED_RUNS
518
+ row << bg_runs[n]
519
+ else
520
+ row << bg.byteslice(0, n)
521
+ end
522
+ a = cur[i + 1]
523
+ n = a - b
524
+ if n < CCITT_CACHED_RUNS
525
+ row << fg_runs[n]
526
+ else
527
+ row << fg.byteslice(0, n)
528
+ end
529
+ i += 2
530
+ end
531
+ n = cols - a
532
+ if n < CCITT_CACHED_RUNS
533
+ row << bg_runs[n]
534
+ elsif n > 0
535
+ row << bg.byteslice(0, n)
536
+ end
537
+ @last = row
538
+ row
539
+ end
540
+
541
+ def same(cur, ref)
542
+ n = cur.size
543
+ return false unless n == ref.size
544
+
545
+ i = 0
546
+ while i < n
547
+ return false unless cur[i] == ref[i]
548
+
549
+ i += 1
550
+ end
551
+ true
552
+ end
553
+ end
554
+ end