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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +14 -0
- data/README.md +222 -0
- data/exe/qr_reader +66 -0
- data/lib/qr_reader/binarizer.rb +170 -0
- data/lib/qr_reader/bit_matrix.rb +120 -0
- data/lib/qr_reader/decoder/bitstream_parser.rb +307 -0
- data/lib/qr_reader/decoder/character_set.rb +163 -0
- data/lib/qr_reader/decoder/codewords.rb +164 -0
- data/lib/qr_reader/decoder/data_mask.rb +57 -0
- data/lib/qr_reader/decoder/format_reader.rb +204 -0
- data/lib/qr_reader/decoder/function_patterns.rb +62 -0
- data/lib/qr_reader/decoder/galois_field.rb +69 -0
- data/lib/qr_reader/decoder/reed_solomon.rb +312 -0
- data/lib/qr_reader/decoder.rb +241 -0
- data/lib/qr_reader/detector/alignment_grid.rb +127 -0
- data/lib/qr_reader/detector/alignment_pattern_finder.rb +191 -0
- data/lib/qr_reader/detector/candidate/dimension.rb +237 -0
- data/lib/qr_reader/detector/candidate/strokes.rb +126 -0
- data/lib/qr_reader/detector/candidate.rb +436 -0
- data/lib/qr_reader/detector/finder_pattern.rb +63 -0
- data/lib/qr_reader/detector/finder_pattern_finder.rb +199 -0
- data/lib/qr_reader/detector/geometry.rb +118 -0
- data/lib/qr_reader/detector/grid_sampler.rb +350 -0
- data/lib/qr_reader/detector/pattern_sets.rb +235 -0
- data/lib/qr_reader/detector/perspective_transform.rb +117 -0
- data/lib/qr_reader/detector/pure_detector.rb +197 -0
- data/lib/qr_reader/detector.rb +215 -0
- data/lib/qr_reader/errors.rb +14 -0
- data/lib/qr_reader/format_information.rb +79 -0
- data/lib/qr_reader/formats/bmp.rb +434 -0
- data/lib/qr_reader/formats/ccitt.rb +81 -0
- data/lib/qr_reader/formats/gif.rb +272 -0
- data/lib/qr_reader/formats/jpeg.rb +152 -0
- data/lib/qr_reader/formats/pdf.rb +2935 -0
- data/lib/qr_reader/formats/png.rb +311 -0
- data/lib/qr_reader/formats/pnm.rb +286 -0
- data/lib/qr_reader/formats/tiff.rb +1345 -0
- data/lib/qr_reader/formats.rb +279 -0
- data/lib/qr_reader/image.rb +231 -0
- data/lib/qr_reader/kernels/binarizer.rb +475 -0
- data/lib/qr_reader/kernels/ccitt.rb +554 -0
- data/lib/qr_reader/kernels/detector.rb +587 -0
- data/lib/qr_reader/kernels/image.rb +65 -0
- data/lib/qr_reader/kernels/jpeg.rb +3421 -0
- data/lib/qr_reader/kernels/lzw.rb +173 -0
- data/lib/qr_reader/kernels/pixels.rb +116 -0
- data/lib/qr_reader/kernels/png.rb +1138 -0
- data/lib/qr_reader/kernels.rb +14 -0
- data/lib/qr_reader/native_kernels.rb +130 -0
- data/lib/qr_reader/reader.rb +348 -0
- data/lib/qr_reader/result.rb +49 -0
- data/lib/qr_reader/symbol_version.rb +173 -0
- data/lib/qr_reader/version.rb +5 -0
- data/lib/qr_reader.rb +77 -0
- metadata +103 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# Raised by BitstreamParser for data that violates the bit stream syntax.
|
|
6
|
+
# Decoder.decode turns it into a nil result.
|
|
7
|
+
class FormatError < Error; end
|
|
8
|
+
|
|
9
|
+
# Decoded data of one symbol, before it is wrapped into a Result (same
|
|
10
|
+
# meaning as the Result attributes of the same names).
|
|
11
|
+
Content = Data.define(:text, :raw_bytes, :segments, :eci, :fnc1, :structured_append, :symbology_identifier)
|
|
12
|
+
|
|
13
|
+
# Parses the data codewords of a symbol (ISO/IEC 18004 §7.4): a sequence
|
|
14
|
+
# of segments, each a 4-bit mode indicator followed by mode-specific data,
|
|
15
|
+
# ended by the terminator 0000 or by running out of bits (fewer than 4 left).
|
|
16
|
+
#
|
|
17
|
+
# BitstreamParser.parse("\x40\x24\x86\x90\xec".b, 1).text # => "Hi" (byte mode, 2 chars)
|
|
18
|
+
class BitstreamParser
|
|
19
|
+
TERMINATOR = 0x0
|
|
20
|
+
NUMERIC = 0x1
|
|
21
|
+
ALPHANUMERIC = 0x2
|
|
22
|
+
STRUCTURED_APPEND = 0x3
|
|
23
|
+
BYTE = 0x4
|
|
24
|
+
FNC1_FIRST = 0x5
|
|
25
|
+
ECI = 0x7
|
|
26
|
+
KANJI = 0x8
|
|
27
|
+
FNC1_SECOND = 0x9
|
|
28
|
+
HANZI = 0xD
|
|
29
|
+
|
|
30
|
+
# Character count indicator lengths for versions 1-9, 10-26 and 27-40.
|
|
31
|
+
COUNT_BITS = {
|
|
32
|
+
NUMERIC => [10, 12, 14], ALPHANUMERIC => [9, 11, 13], BYTE => [8, 16, 16],
|
|
33
|
+
KANJI => [8, 10, 12], HANZI => [8, 10, 12]
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
ALPHANUMERIC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".b.freeze
|
|
37
|
+
# Decoded text for every valid 10-bit numeric group ("000".."999").
|
|
38
|
+
DIGIT_TRIPLES = ("000".."999").map(&:freeze).freeze
|
|
39
|
+
GB2312_SUBSET = 1
|
|
40
|
+
GROUP_SEPARATOR = "\x1D".b.freeze
|
|
41
|
+
ASCII = Encoding::US_ASCII
|
|
42
|
+
|
|
43
|
+
# Parses +data+ (binary String of data codewords) of a symbol of version
|
|
44
|
+
# +version+ (1..40). Returns a Content; raises FormatError.
|
|
45
|
+
def self.parse(data, version)
|
|
46
|
+
new(data, version).parse
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def initialize(data, version)
|
|
50
|
+
raise ArgumentError, "version must be 1..40" unless version.is_a?(Integer) && version.between?(1, 40)
|
|
51
|
+
|
|
52
|
+
@data = data.b + "\0\0" # padding so a 24-bit window never reads past the end
|
|
53
|
+
@limit = data.bytesize * 8
|
|
54
|
+
@pos = 0
|
|
55
|
+
@size_class = if version <= 9 then 0
|
|
56
|
+
elsif version <= 26 then 1
|
|
57
|
+
else 2
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def parse
|
|
62
|
+
segments = [] # [mode, bytes, eci designator or nil]
|
|
63
|
+
eci = nil
|
|
64
|
+
first_eci = nil
|
|
65
|
+
fnc1 = nil
|
|
66
|
+
structured_append = nil
|
|
67
|
+
while @limit - @pos >= 4
|
|
68
|
+
mode = read_bits(4)
|
|
69
|
+
case mode
|
|
70
|
+
when TERMINATOR
|
|
71
|
+
break
|
|
72
|
+
when NUMERIC
|
|
73
|
+
segments << [:numeric, numeric(read_count(mode)), eci]
|
|
74
|
+
when ALPHANUMERIC
|
|
75
|
+
segments << [:alphanumeric, alphanumeric(read_count(mode), fnc1), eci]
|
|
76
|
+
when BYTE
|
|
77
|
+
segments << [:byte, byte(read_count(mode)), eci]
|
|
78
|
+
when KANJI
|
|
79
|
+
segments << [:kanji, kanji(read_count(mode)), eci]
|
|
80
|
+
when HANZI
|
|
81
|
+
subset = read_bits(4)
|
|
82
|
+
count = read_count(mode)
|
|
83
|
+
raise FormatError, "unsupported Hanzi subset #{subset}" unless subset == GB2312_SUBSET
|
|
84
|
+
|
|
85
|
+
segments << [:hanzi, hanzi(count), eci]
|
|
86
|
+
when ECI
|
|
87
|
+
eci = read_eci
|
|
88
|
+
first_eci ||= eci
|
|
89
|
+
when FNC1_FIRST
|
|
90
|
+
fnc1 = :first
|
|
91
|
+
when FNC1_SECOND
|
|
92
|
+
fnc1 = [:second, application_indicator]
|
|
93
|
+
when STRUCTURED_APPEND
|
|
94
|
+
sequence = read_bits(8)
|
|
95
|
+
structured_append = StructuredAppend.new(index: sequence >> 4, count: (sequence & 0x0F) + 1,
|
|
96
|
+
parity: read_bits(8))
|
|
97
|
+
else
|
|
98
|
+
raise FormatError, format("invalid mode indicator %04b", mode)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
build_content(segments, first_eci, fnc1, structured_append)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def available
|
|
107
|
+
@limit - @pos
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Next +count+ (<= 17) bits as an Integer, most significant first.
|
|
111
|
+
def read_bits(count)
|
|
112
|
+
pos = @pos
|
|
113
|
+
raise FormatError, "bit stream ends early" if pos + count > @limit
|
|
114
|
+
|
|
115
|
+
data = @data
|
|
116
|
+
index = pos >> 3
|
|
117
|
+
window = (data.getbyte(index) << 16) | (data.getbyte(index + 1) << 8) | data.getbyte(index + 2)
|
|
118
|
+
@pos = pos + count
|
|
119
|
+
(window >> (24 - (pos & 7) - count)) & ((1 << count) - 1)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def read_count(mode)
|
|
123
|
+
read_bits(COUNT_BITS[mode][@size_class])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def require_bits(count)
|
|
127
|
+
raise FormatError, "segment longer than the remaining data" if count > available
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# 3 digits per 10 bits; a remainder of 2 digits takes 7 bits, 1 digit 4 bits.
|
|
131
|
+
def numeric(count)
|
|
132
|
+
require_bits(count / 3 * 10 + [0, 4, 7][count % 3])
|
|
133
|
+
out = "".b
|
|
134
|
+
while count >= 3
|
|
135
|
+
value = read_bits(10)
|
|
136
|
+
raise FormatError, "invalid numeric group #{value}" if value >= 1000
|
|
137
|
+
|
|
138
|
+
out << DIGIT_TRIPLES[value]
|
|
139
|
+
count -= 3
|
|
140
|
+
end
|
|
141
|
+
if count == 2
|
|
142
|
+
value = read_bits(7)
|
|
143
|
+
raise FormatError, "invalid numeric group #{value}" if value >= 100
|
|
144
|
+
|
|
145
|
+
out << DIGIT_TRIPLES[value].byteslice(1, 2)
|
|
146
|
+
elsif count == 1
|
|
147
|
+
value = read_bits(4)
|
|
148
|
+
raise FormatError, "invalid numeric digit #{value}" if value >= 10
|
|
149
|
+
|
|
150
|
+
out << DIGIT_TRIPLES[value].byteslice(2, 1)
|
|
151
|
+
end
|
|
152
|
+
out
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# 2 characters per 11 bits, a final odd character in 6 bits. With FNC1
|
|
156
|
+
# in effect, "%%" stands for "%" and a lone "%" for the GS separator.
|
|
157
|
+
def alphanumeric(count, fnc1)
|
|
158
|
+
require_bits(count / 2 * 11 + (count.odd? ? 6 : 0))
|
|
159
|
+
out = "".b
|
|
160
|
+
while count >= 2
|
|
161
|
+
value = read_bits(11)
|
|
162
|
+
raise FormatError, "invalid alphanumeric pair #{value}" if value >= 2025
|
|
163
|
+
|
|
164
|
+
out << ALPHANUMERIC_CHARS.getbyte(value / 45) << ALPHANUMERIC_CHARS.getbyte(value % 45)
|
|
165
|
+
count -= 2
|
|
166
|
+
end
|
|
167
|
+
if count == 1
|
|
168
|
+
value = read_bits(6)
|
|
169
|
+
raise FormatError, "invalid alphanumeric character #{value}" if value >= 45
|
|
170
|
+
|
|
171
|
+
out << ALPHANUMERIC_CHARS.getbyte(value)
|
|
172
|
+
end
|
|
173
|
+
out = out.gsub(/%%?/n) { |m| m.bytesize == 2 ? "%" : GROUP_SEPARATOR } if fnc1 && out.include?("%")
|
|
174
|
+
out
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def byte(count)
|
|
178
|
+
require_bits(count * 8)
|
|
179
|
+
start = @pos
|
|
180
|
+
shift = start & 7
|
|
181
|
+
@pos = start + count * 8
|
|
182
|
+
return @data.byteslice(start >> 3, count) if shift.zero?
|
|
183
|
+
|
|
184
|
+
# Unaligned: re-pack through a "0101..." string, at C speed.
|
|
185
|
+
[@data.byteslice(start >> 3, count + 1).unpack1("B*").byteslice(shift, count * 8)].pack("B*")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# 13 bits per character: the Shift_JIS code minus 0x8140 or 0xC140,
|
|
189
|
+
# high byte times 0xC0 plus low byte.
|
|
190
|
+
def kanji(count)
|
|
191
|
+
require_bits(count * 13)
|
|
192
|
+
out = "".b
|
|
193
|
+
while count.positive?
|
|
194
|
+
value = read_bits(13)
|
|
195
|
+
code = ((value / 0xC0) << 8) | (value % 0xC0)
|
|
196
|
+
code += code < 0x1F00 ? 0x8140 : 0xC140
|
|
197
|
+
out << (code >> 8) << (code & 0xFF)
|
|
198
|
+
count -= 1
|
|
199
|
+
end
|
|
200
|
+
out
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# 13 bits per character: the GB 2312 code minus 0xA1A1 or 0xA6A1,
|
|
204
|
+
# high byte times 0x60 plus low byte.
|
|
205
|
+
def hanzi(count)
|
|
206
|
+
require_bits(count * 13)
|
|
207
|
+
out = "".b
|
|
208
|
+
while count.positive?
|
|
209
|
+
value = read_bits(13)
|
|
210
|
+
code = ((value / 0x60) << 8) | (value % 0x60)
|
|
211
|
+
code += code < 0x0A00 ? 0xA1A1 : 0xA6A1
|
|
212
|
+
out << ((code >> 8) & 0xFF) << (code & 0xFF)
|
|
213
|
+
count -= 1
|
|
214
|
+
end
|
|
215
|
+
out
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# ECI designator in 1, 2 or 3 bytes (0xxxxxxx, 10xxxxxx +8, 110xxxxx +16).
|
|
219
|
+
def read_eci
|
|
220
|
+
first = read_bits(8)
|
|
221
|
+
value = if (first & 0x80).zero?
|
|
222
|
+
first
|
|
223
|
+
elsif (first & 0xC0) == 0x80
|
|
224
|
+
((first & 0x3F) << 8) | read_bits(8)
|
|
225
|
+
elsif (first & 0xE0) == 0xC0
|
|
226
|
+
((first & 0x1F) << 16) | read_bits(16)
|
|
227
|
+
else
|
|
228
|
+
raise FormatError, "invalid ECI designator"
|
|
229
|
+
end
|
|
230
|
+
raise FormatError, "ECI designator #{value} out of range" if value > 999_999
|
|
231
|
+
|
|
232
|
+
value
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# FNC1 in second position is followed by an application indicator:
|
|
236
|
+
# two digits (00-99) or one letter (encoded as its ASCII value + 100).
|
|
237
|
+
def application_indicator
|
|
238
|
+
value = read_bits(8)
|
|
239
|
+
return format("%02d", value) if value < 100
|
|
240
|
+
return (value - 100).chr if value.between?(165, 190) || value.between?(197, 222)
|
|
241
|
+
|
|
242
|
+
raise FormatError, "invalid FNC1 application indicator #{value}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def build_content(segments, first_eci, fnc1, structured_append)
|
|
246
|
+
guessed = guess_encoding(segments) unless first_eci
|
|
247
|
+
resolved = segments.map do |mode, bytes, eci|
|
|
248
|
+
[mode, bytes, eci, segment_encoding(mode, eci, first_eci, guessed)]
|
|
249
|
+
end
|
|
250
|
+
text = +""
|
|
251
|
+
bytes = "".b
|
|
252
|
+
segment_list = []
|
|
253
|
+
run_start = 0
|
|
254
|
+
resolved.each_with_index do |(mode, data, eci, encoding), i|
|
|
255
|
+
bytes << data
|
|
256
|
+
segment_list << Segment.new(mode: mode, text: CharacterSet.to_utf8(data, encoding), eci: eci)
|
|
257
|
+
next_encoding = resolved[i + 1]&.last
|
|
258
|
+
next if next_encoding == encoding
|
|
259
|
+
|
|
260
|
+
# Convert runs of segments in the same encoding together, so a
|
|
261
|
+
# character split across segments survives.
|
|
262
|
+
text << if run_start == i
|
|
263
|
+
segment_list[i].text
|
|
264
|
+
else
|
|
265
|
+
CharacterSet.to_utf8(resolved[run_start..i].map { |s| s[1] }.join, encoding)
|
|
266
|
+
end
|
|
267
|
+
run_start = i + 1
|
|
268
|
+
end
|
|
269
|
+
Content.new(text: text, raw_bytes: bytes, segments: segment_list.freeze, eci: first_eci, fnc1: fnc1,
|
|
270
|
+
structured_append: structured_append,
|
|
271
|
+
symbology_identifier: "]Q#{symbology_modifier(first_eci, fnc1)}")
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# ISO/IEC 18004 Table F.1: 1 plain, 3 FNC1 first, 5 FNC1 second;
|
|
275
|
+
# one more when an ECI is present.
|
|
276
|
+
def symbology_modifier(eci, fnc1)
|
|
277
|
+
base = if fnc1 == :first then 3
|
|
278
|
+
elsif fnc1 then 5
|
|
279
|
+
else 1
|
|
280
|
+
end
|
|
281
|
+
eci ? base + 1 : base
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Without any ECI, all byte segments share one guessed encoding.
|
|
285
|
+
def guess_encoding(segments)
|
|
286
|
+
data = segments.filter_map { |mode, bytes, _| bytes if mode == :byte }
|
|
287
|
+
data.empty? ? nil : CharacterSet.guess(data.join)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def segment_encoding(mode, eci, first_eci, guessed)
|
|
291
|
+
case mode
|
|
292
|
+
when :numeric, :alphanumeric then ASCII
|
|
293
|
+
when :kanji then CharacterSet::SHIFT_JIS
|
|
294
|
+
when :hanzi then CharacterSet::GB18030
|
|
295
|
+
else
|
|
296
|
+
if eci
|
|
297
|
+
CharacterSet.for_eci(eci) || CharacterSet::ISO_8859_1
|
|
298
|
+
elsif first_eci
|
|
299
|
+
CharacterSet::ISO_8859_1 # default ECI 000003 before the first ECI
|
|
300
|
+
else
|
|
301
|
+
guessed
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# Character sets: ECI designators to Ruby encodings, the encoding guess
|
|
6
|
+
# for byte segments without ECI, and conversion to valid UTF-8.
|
|
7
|
+
module CharacterSet
|
|
8
|
+
UTF_8 = Encoding::UTF_8
|
|
9
|
+
BINARY = Encoding::BINARY
|
|
10
|
+
ISO_8859_1 = Encoding::ISO_8859_1
|
|
11
|
+
SHIFT_JIS = Encoding::Shift_JIS
|
|
12
|
+
# Superset of Shift_JIS (NEC/IBM extensions); used when strict Shift_JIS
|
|
13
|
+
# cannot represent a character.
|
|
14
|
+
WINDOWS_31J = Encoding::Windows_31J
|
|
15
|
+
# Superset of GB 2312 (EUC-CN), used for Hanzi mode.
|
|
16
|
+
GB18030 = Encoding::GB18030
|
|
17
|
+
|
|
18
|
+
# ECI designator => Encoding (ISO/IEC 18004 / AIM ECI assignments as used
|
|
19
|
+
# by ZXing). 899 is "binary": the bytes are shown as ISO-8859-1.
|
|
20
|
+
ECI_ENCODINGS = {
|
|
21
|
+
0 => Encoding::IBM437, 2 => Encoding::IBM437,
|
|
22
|
+
1 => ISO_8859_1, 3 => ISO_8859_1,
|
|
23
|
+
4 => Encoding::ISO_8859_2, 5 => Encoding::ISO_8859_3, 6 => Encoding::ISO_8859_4,
|
|
24
|
+
7 => Encoding::ISO_8859_5, 8 => Encoding::ISO_8859_6, 9 => Encoding::ISO_8859_7,
|
|
25
|
+
10 => Encoding::ISO_8859_8, 11 => Encoding::ISO_8859_9, 12 => Encoding::ISO_8859_10,
|
|
26
|
+
13 => Encoding::ISO_8859_11, 15 => Encoding::ISO_8859_13, 16 => Encoding::ISO_8859_14,
|
|
27
|
+
17 => Encoding::ISO_8859_15, 18 => Encoding::ISO_8859_16,
|
|
28
|
+
20 => SHIFT_JIS,
|
|
29
|
+
21 => Encoding::Windows_1250, 22 => Encoding::Windows_1251,
|
|
30
|
+
23 => Encoding::Windows_1252, 24 => Encoding::Windows_1256,
|
|
31
|
+
25 => Encoding::UTF_16BE, 26 => UTF_8,
|
|
32
|
+
27 => Encoding::US_ASCII, 170 => Encoding::US_ASCII,
|
|
33
|
+
28 => Encoding::Big5, 29 => GB18030, 30 => Encoding::EUC_KR,
|
|
34
|
+
899 => BINARY
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
# Decodes one character that Shift_JIS cannot map, via Windows-31J.
|
|
38
|
+
WINDOWS_31J_FALLBACK = lambda do |char|
|
|
39
|
+
char.dup.force_encoding(WINDOWS_31J).encode(UTF_8, invalid: :replace, undef: :replace)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# GB 18030-2000 private-use code points (from the 2-byte codes A6D9..A6DF,
|
|
43
|
+
# A6EC, A6ED, A6F3 and A8BC) and their GB 18030-2005 replacements.
|
|
44
|
+
GB18030_PUA = "\uE78D\uE78E\uE78F\uE790\uE791\uE792\uE793\uE794\uE795\uE796\uE7C7"
|
|
45
|
+
GB18030_2005 = "\uFE10\uFE12\uFE11\uFE13\uFE14\uFE15\uFE16\uFE17\uFE18\uFE19\u1E3F"
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
# Encoding for an ECI designator, or nil if it names no known character set.
|
|
50
|
+
def for_eci(designator)
|
|
51
|
+
ECI_ENCODINGS[designator]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Guesses the encoding of byte-mode data without ECI, following ZXing's
|
|
55
|
+
# StringUtils.guessCharset: UTF-16 with BOM; UTF-8 if the bytes are valid
|
|
56
|
+
# UTF-8 (strictly) containing multibyte characters; Shift_JIS if the bytes
|
|
57
|
+
# look like it (runs of 3+ double-byte or half-width katakana characters,
|
|
58
|
+
# or many Latin-1 symbols); else ISO-8859-1. Like zxing-cpp, control
|
|
59
|
+
# characters other than CR/LF mark the data as binary, not Shift_JIS,
|
|
60
|
+
# so that it decodes losslessly as ISO-8859-1.
|
|
61
|
+
def guess(bytes)
|
|
62
|
+
return ISO_8859_1 if bytes.ascii_only?
|
|
63
|
+
|
|
64
|
+
if bytes.bytesize > 2
|
|
65
|
+
b0 = bytes.getbyte(0)
|
|
66
|
+
b1 = bytes.getbyte(1)
|
|
67
|
+
return Encoding::UTF_16BE if b0 == 0xFE && b1 == 0xFF
|
|
68
|
+
return Encoding::UTF_16LE if b0 == 0xFF && b1 == 0xFE
|
|
69
|
+
end
|
|
70
|
+
return UTF_8 if bytes.dup.force_encoding(UTF_8).valid_encoding?
|
|
71
|
+
|
|
72
|
+
guess_latin1_or_shift_jis(bytes)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Converts +bytes+ in +encoding+ to a valid UTF-8 String (invalid or
|
|
76
|
+
# unmappable sequences become U+FFFD).
|
|
77
|
+
def to_utf8(bytes, encoding)
|
|
78
|
+
return bytes.dup.force_encoding(UTF_8) if bytes.ascii_only? && encoding.ascii_compatible?
|
|
79
|
+
|
|
80
|
+
case encoding
|
|
81
|
+
when UTF_8
|
|
82
|
+
bytes.dup.force_encoding(UTF_8).scrub
|
|
83
|
+
when BINARY
|
|
84
|
+
bytes.encode(UTF_8, ISO_8859_1)
|
|
85
|
+
when SHIFT_JIS
|
|
86
|
+
# Characters Shift_JIS lacks (NEC/IBM extensions) are taken from
|
|
87
|
+
# Windows-31J one by one, so the rest keep their JIS X 0208 mapping.
|
|
88
|
+
# Ruby maps 0x815C to U+2014 EM DASH; ZXing and zxing-cpp (like
|
|
89
|
+
# Unicode's SHIFTJIS.TXT and Windows-31J) to U+2015 HORIZONTAL BAR.
|
|
90
|
+
bytes.encode(UTF_8, SHIFT_JIS, invalid: :replace, fallback: WINDOWS_31J_FALLBACK).tr("\u2014", "\u2015")
|
|
91
|
+
when GB18030
|
|
92
|
+
# Ruby uses the GB 18030-2000 private-use mappings for 11 GBK codes
|
|
93
|
+
# that GB 18030-2005 (and zxing-cpp) map to standard characters.
|
|
94
|
+
bytes.encode(UTF_8, GB18030, invalid: :replace, undef: :replace).tr(GB18030_PUA, GB18030_2005)
|
|
95
|
+
when Encoding::UTF_16BE, Encoding::UTF_16LE
|
|
96
|
+
text = bytes.encode(UTF_8, encoding, invalid: :replace, undef: :replace)
|
|
97
|
+
text.delete_prefix("\uFEFF")
|
|
98
|
+
else
|
|
99
|
+
bytes.encode(UTF_8, encoding, invalid: :replace, undef: :replace)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ZXing's Latin-1 vs. Shift_JIS heuristics (bytes known not to be UTF-8).
|
|
104
|
+
def guess_latin1_or_shift_jis(bytes)
|
|
105
|
+
can_be_latin1 = true
|
|
106
|
+
can_be_sjis = true
|
|
107
|
+
sjis_bytes_left = 0
|
|
108
|
+
katakana_chars = 0
|
|
109
|
+
katakana_run = 0
|
|
110
|
+
double_byte_run = 0
|
|
111
|
+
max_katakana_run = 0
|
|
112
|
+
max_double_byte_run = 0
|
|
113
|
+
latin1_high_other = 0
|
|
114
|
+
bytes.each_byte do |v|
|
|
115
|
+
if can_be_latin1
|
|
116
|
+
if v > 0x7F && v < 0xA0
|
|
117
|
+
can_be_latin1 = false
|
|
118
|
+
elsif v > 0x9F && (v < 0xC0 || v == 0xD7 || v == 0xF7)
|
|
119
|
+
latin1_high_other += 1
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
if can_be_sjis
|
|
123
|
+
if sjis_bytes_left.positive?
|
|
124
|
+
if v < 0x40 || v == 0x7F || v > 0xFC
|
|
125
|
+
can_be_sjis = false
|
|
126
|
+
else
|
|
127
|
+
sjis_bytes_left -= 1
|
|
128
|
+
end
|
|
129
|
+
elsif v == 0x80 || v == 0xA0 || v > 0xEF || (v < 0x20 && v != 0x0A && v != 0x0D)
|
|
130
|
+
can_be_sjis = false
|
|
131
|
+
elsif v > 0xA0 && v < 0xE0
|
|
132
|
+
katakana_chars += 1
|
|
133
|
+
double_byte_run = 0
|
|
134
|
+
katakana_run += 1
|
|
135
|
+
max_katakana_run = katakana_run if katakana_run > max_katakana_run
|
|
136
|
+
elsif v > 0x7F
|
|
137
|
+
sjis_bytes_left += 1
|
|
138
|
+
katakana_run = 0
|
|
139
|
+
double_byte_run += 1
|
|
140
|
+
max_double_byte_run = double_byte_run if double_byte_run > max_double_byte_run
|
|
141
|
+
else
|
|
142
|
+
katakana_run = 0
|
|
143
|
+
double_byte_run = 0
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
break unless can_be_latin1 || can_be_sjis
|
|
147
|
+
end
|
|
148
|
+
can_be_sjis = false if sjis_bytes_left.positive?
|
|
149
|
+
|
|
150
|
+
return SHIFT_JIS if can_be_sjis && (max_katakana_run >= 3 || max_double_byte_run >= 3)
|
|
151
|
+
|
|
152
|
+
if can_be_latin1 && can_be_sjis
|
|
153
|
+
return SHIFT_JIS if (max_katakana_run == 2 && katakana_chars == 2) || latin1_high_other * 10 >= bytes.bytesize
|
|
154
|
+
|
|
155
|
+
return ISO_8859_1
|
|
156
|
+
end
|
|
157
|
+
return SHIFT_JIS if can_be_sjis && !can_be_latin1
|
|
158
|
+
|
|
159
|
+
ISO_8859_1
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# Codeword placement (ISO/IEC 18004 §7.7.3) and block structure.
|
|
6
|
+
#
|
|
7
|
+
# Everything that depends only on the version is computed once and cached
|
|
8
|
+
# in a Layout: the module indices of all data bits in zig-zag placement
|
|
9
|
+
# order, the data mask as one XOR value per codeword, and per EC level
|
|
10
|
+
# the raw codeword indices of every RS block. Reading a block is then a
|
|
11
|
+
# tight loop of getbyte calls on the module grid.
|
|
12
|
+
module Codewords
|
|
13
|
+
# Precomputed tables for one symbol version.
|
|
14
|
+
class Layout
|
|
15
|
+
# +order+: module indices of the data bits in placement order, 8 per
|
|
16
|
+
# codeword. +mirrored_order+: the same for a symbol stored transposed
|
|
17
|
+
# (mirrored), where module (x, y) lives at x * n + y.
|
|
18
|
+
attr_reader :version, :dimension, :order, :mirrored_order
|
|
19
|
+
|
|
20
|
+
def initialize(version)
|
|
21
|
+
@version = version
|
|
22
|
+
@dimension = version.dimension
|
|
23
|
+
@order, @mirrored_order = build_orders
|
|
24
|
+
@mask_codewords = Array.new(8)
|
|
25
|
+
@blocks = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# One Integer per codeword: the bits that data mask +mask+ flips,
|
|
29
|
+
# read from the mask's module grid like the data itself.
|
|
30
|
+
def mask_codewords(mask)
|
|
31
|
+
@mask_codewords[mask] ||= begin
|
|
32
|
+
count = @order.size / 8
|
|
33
|
+
Codewords.read_block(DataMask.grid(mask, @dimension), @order, Array.new(count, 0),
|
|
34
|
+
Array.new(count) { |k| k }).freeze
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# RS blocks for EC level +level+ in block order: Array of
|
|
39
|
+
# [data_codeword_count, raw_codeword_indices] (data then EC codewords).
|
|
40
|
+
# Undoes the interleaving of §7.6: data codewords are taken from every
|
|
41
|
+
# block in turn (shorter blocks run out first), then EC codewords.
|
|
42
|
+
def blocks(level)
|
|
43
|
+
@blocks[level] ||= build_blocks(@version.ec_blocks(level))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Undoes the interleaving of §7.6 (plain loops: runs once per level).
|
|
49
|
+
def build_blocks(ec_blocks)
|
|
50
|
+
data_counts = ec_blocks.data_codewords_per_block
|
|
51
|
+
count = data_counts.size
|
|
52
|
+
indices = Array.new(count) { [] }
|
|
53
|
+
raw = 0
|
|
54
|
+
longest = data_counts.max
|
|
55
|
+
i = 0
|
|
56
|
+
while i < longest
|
|
57
|
+
b = 0
|
|
58
|
+
while b < count
|
|
59
|
+
if i < data_counts[b]
|
|
60
|
+
indices[b] << raw
|
|
61
|
+
raw += 1
|
|
62
|
+
end
|
|
63
|
+
b += 1
|
|
64
|
+
end
|
|
65
|
+
i += 1
|
|
66
|
+
end
|
|
67
|
+
i = 0
|
|
68
|
+
while i < ec_blocks.ec_codewords_per_block
|
|
69
|
+
b = 0
|
|
70
|
+
while b < count
|
|
71
|
+
indices[b] << raw
|
|
72
|
+
raw += 1
|
|
73
|
+
b += 1
|
|
74
|
+
end
|
|
75
|
+
i += 1
|
|
76
|
+
end
|
|
77
|
+
blocks = []
|
|
78
|
+
b = 0
|
|
79
|
+
while b < count
|
|
80
|
+
blocks << [data_counts[b], indices[b].freeze].freeze
|
|
81
|
+
b += 1
|
|
82
|
+
end
|
|
83
|
+
blocks.freeze
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Zig-zag over column pairs from the right, alternately upwards and
|
|
87
|
+
# downwards, skipping the vertical timing column and function modules
|
|
88
|
+
# (same traversal as ZXing's BitMatrixParser#readCodewords). Returns
|
|
89
|
+
# the normal and the mirrored order; remainder bits are dropped.
|
|
90
|
+
def build_orders
|
|
91
|
+
n = @dimension
|
|
92
|
+
function = FunctionPatterns.for_version(@version.number)
|
|
93
|
+
order = []
|
|
94
|
+
mirrored = []
|
|
95
|
+
upward = true
|
|
96
|
+
x = n - 1
|
|
97
|
+
while x.positive?
|
|
98
|
+
x -= 1 if x == 6
|
|
99
|
+
count = 0
|
|
100
|
+
while count < n
|
|
101
|
+
y = upward ? n - 1 - count : count
|
|
102
|
+
index = y * n + x
|
|
103
|
+
if function.getbyte(index).zero?
|
|
104
|
+
order << index
|
|
105
|
+
mirrored << x * n + y
|
|
106
|
+
end
|
|
107
|
+
if function.getbyte(index - 1).zero?
|
|
108
|
+
order << index - 1
|
|
109
|
+
mirrored << (x - 1) * n + y
|
|
110
|
+
end
|
|
111
|
+
count += 1
|
|
112
|
+
end
|
|
113
|
+
upward = !upward
|
|
114
|
+
x -= 2
|
|
115
|
+
end
|
|
116
|
+
limit = @version.total_codewords * 8
|
|
117
|
+
[order.first(limit).freeze, mirrored.first(limit).freeze]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@layouts = Array.new(41)
|
|
122
|
+
|
|
123
|
+
class << self
|
|
124
|
+
# Layout for version +number+ (1..40), built on first use.
|
|
125
|
+
def layout(number)
|
|
126
|
+
@layouts[number] ||= Layout.new(SymbolVersion[number])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Reads and unmasks the codewords with the given raw indices from
|
|
130
|
+
# +bits+ (a symbol's module String with only 0/1 bytes).
|
|
131
|
+
def read_block(bits, order, mask_codewords, indices)
|
|
132
|
+
count = indices.size
|
|
133
|
+
out = Array.new(count)
|
|
134
|
+
k = 0
|
|
135
|
+
while k < count
|
|
136
|
+
raw = indices[k]
|
|
137
|
+
i = raw << 3
|
|
138
|
+
out[k] = ((bits.getbyte(order[i]) << 7) | (bits.getbyte(order[i + 1]) << 6) |
|
|
139
|
+
(bits.getbyte(order[i + 2]) << 5) | (bits.getbyte(order[i + 3]) << 4) |
|
|
140
|
+
(bits.getbyte(order[i + 4]) << 3) | (bits.getbyte(order[i + 5]) << 2) |
|
|
141
|
+
(bits.getbyte(order[i + 6]) << 1) | bits.getbyte(order[i + 7])) ^ mask_codewords[raw]
|
|
142
|
+
k += 1
|
|
143
|
+
end
|
|
144
|
+
out
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# All codewords of a symbol in transmission (interleaved) order,
|
|
148
|
+
# unmasked. Mainly for tests and diagnostics; the decoder reads block
|
|
149
|
+
# by block so that garbage fails after the first block.
|
|
150
|
+
def read(bit_matrix, version, mask, mirrored: false)
|
|
151
|
+
layout = layout(version)
|
|
152
|
+
order = mirrored ? layout.mirrored_order : layout.order
|
|
153
|
+
mask_codewords = layout.mask_codewords(mask)
|
|
154
|
+
read_block(bit_matrix.bits, order, mask_codewords, (0...mask_codewords.size).to_a)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Splits interleaved codewords into RS blocks (Arrays of Integers).
|
|
158
|
+
def deinterleave(codewords, version, level)
|
|
159
|
+
layout(version).blocks(level).map { |_, indices| codewords.values_at(*indices) }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# The eight data mask patterns of ISO/IEC 18004 Table 10. Only used to
|
|
6
|
+
# precompute per-version tables, so clarity beats speed in masked?.
|
|
7
|
+
module DataMask
|
|
8
|
+
# Every pattern repeats after 12 rows and 12 columns (lcm of the 2-, 3-,
|
|
9
|
+
# 4- and 6-periodic conditions).
|
|
10
|
+
PERIOD = 12
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# True if mask pattern +mask+ (0..7) inverts the module in column +x+,
|
|
15
|
+
# row +y+ (ISO's j and i).
|
|
16
|
+
def masked?(mask, x, y)
|
|
17
|
+
case mask
|
|
18
|
+
when 0 then (y + x).even?
|
|
19
|
+
when 1 then y.even?
|
|
20
|
+
when 2 then (x % 3).zero?
|
|
21
|
+
when 3 then ((y + x) % 3).zero?
|
|
22
|
+
when 4 then ((y / 2) + (x / 3)).even?
|
|
23
|
+
when 5 then ((y * x) % 6).zero? # (ij mod 2) + (ij mod 3) == 0
|
|
24
|
+
when 6 then ((y * x) % 6) < 3 # ((ij mod 2) + (ij mod 3)) mod 2 == 0
|
|
25
|
+
when 7 then (((y + x) % 2) + ((y * x) % 3)).even?
|
|
26
|
+
else raise ArgumentError, "mask must be 0..7, got #{mask.inspect}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The whole pattern for an n x n symbol as a binary String of n * n
|
|
31
|
+
# bytes, row-major, "\x01" where the mask inverts the module. Built
|
|
32
|
+
# from the 12 distinct rows, so it costs O(n) String operations.
|
|
33
|
+
def grid(mask, n)
|
|
34
|
+
repeat = n / PERIOD + 1
|
|
35
|
+
rows = Array.new(PERIOD)
|
|
36
|
+
y = 0
|
|
37
|
+
while y < PERIOD
|
|
38
|
+
unit = "\0".b * PERIOD
|
|
39
|
+
x = 0
|
|
40
|
+
while x < PERIOD
|
|
41
|
+
unit.setbyte(x, 1) if masked?(mask, x, y)
|
|
42
|
+
x += 1
|
|
43
|
+
end
|
|
44
|
+
rows[y] = (unit * repeat).byteslice(0, n)
|
|
45
|
+
y += 1
|
|
46
|
+
end
|
|
47
|
+
out = "".b
|
|
48
|
+
y = 0
|
|
49
|
+
while y < n
|
|
50
|
+
out << rows[y % PERIOD]
|
|
51
|
+
y += 1
|
|
52
|
+
end
|
|
53
|
+
out
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|