forked from yeqown/go-qrcode
-
Notifications
You must be signed in to change notification settings - Fork 0
/
encoder.go
350 lines (309 loc) · 7.67 KB
/
encoder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
// Package qrcode ...
// encoder.go working for data encoding
package qrcode
import (
"fmt"
"log"
"github.com/yeqown/reedsolomon/binary"
)
// encMode ...
type encMode uint
const (
// a qrbool of EncModeAuto will trigger a detection of the letter set from the input data,
EncModeAuto = 0
// EncModeNone mode ...
EncModeNone encMode = 1 << iota
// EncModeNumeric mode ...
EncModeNumeric
// EncModeAlphanumeric mode ...
EncModeAlphanumeric
// EncModeByte mode ...
EncModeByte
// EncModeJP mode ...
EncModeJP
)
var (
paddingByte1, _ = binary.NewFromBinaryString("11101100")
paddingByte2, _ = binary.NewFromBinaryString("00010001")
)
// getEncModeName ...
func getEncModeName(mode encMode) string {
switch mode {
case EncModeNone:
return "none"
case EncModeNumeric:
return "numeric"
case EncModeAlphanumeric:
return "alphanumeric"
case EncModeByte:
return "byte"
case EncModeJP:
return "japan"
default:
return "unknown"
}
}
// getEncodeModeIndicator ...
func getEncodeModeIndicator(mode encMode) *binary.Binary {
switch mode {
case EncModeNumeric:
return binary.New(false, false, false, true)
case EncModeAlphanumeric:
return binary.New(false, false, true, false)
case EncModeByte:
return binary.New(false, true, false, false)
case EncModeJP:
return binary.New(true, false, false, false)
default:
panic("no indicator")
}
}
// encoder ... data to bit stream ...
type encoder struct {
// self init
dst *binary.Binary
data []byte // raw input data
// initial params
mode encMode // encode mode
ecLv ecLevel // error correction level
// self load
version version // QR version ref
}
func newEncoder(m encMode, ec ecLevel, v version) *encoder {
return &encoder{
dst: nil,
data: nil,
mode: m,
ecLv: ec,
version: v,
}
}
// Encode ...
// 1. encode raw data into bitset
// 2. append _defaultPadding data
//
func (e *encoder) Encode(byts []byte) (*binary.Binary, error) {
e.dst = binary.New()
e.data = byts
// append mode indicator symbol
indicator := getEncodeModeIndicator(e.mode)
e.dst.Append(indicator)
// append chars length counter bits symbol
e.dst.AppendUint32(uint32(len(byts)), e.charCountBits())
// encode data with specified mode
switch e.mode {
case EncModeNumeric:
e.encodeNumeric()
case EncModeAlphanumeric:
e.encodeAlphanumeric()
case EncModeByte:
e.encodeByte()
case EncModeJP:
panic("this has not been finished")
}
// fill and _defaultPadding bits
e.breakUpInto8bit()
return e.dst, nil
}
// 0001b mode indicator
func (e *encoder) encodeNumeric() {
if e.dst == nil {
log.Println("e.dst is nil")
return
}
for i := 0; i < len(e.data); i += 3 {
charsRemaining := len(e.data) - i
var value uint32
bitsUsed := 1
for j := 0; j < charsRemaining && j < 3; j++ {
value *= 10
value += uint32(e.data[i+j] - 0x30)
bitsUsed += 3
}
e.dst.AppendUint32(value, bitsUsed)
}
}
// 0010b mode indicator
func (e *encoder) encodeAlphanumeric() {
if e.dst == nil {
log.Println("e.dst is nil")
return
}
for i := 0; i < len(e.data); i += 2 {
charsRemaining := len(e.data) - i
var value uint32
for j := 0; j < charsRemaining && j < 2; j++ {
value *= 45
value += encodeAlphanumericCharacter(e.data[i+j])
}
bitsUsed := 6
if charsRemaining > 1 {
bitsUsed = 11
}
e.dst.AppendUint32(value, bitsUsed)
}
}
// 0100b mode indicator
func (e *encoder) encodeByte() {
if e.dst == nil {
log.Println("e.dst is nil")
return
}
for _, b := range e.data {
_ = e.dst.AppendByte(b, 8)
}
}
// Break Up into 8-bit Codewords and Add Pad Bytes if Necessary
func (e *encoder) breakUpInto8bit() {
// fill ending code (max 4bit)
// depends on max capacity of current version and EC level
maxCap := e.version.NumTotalCodewords() * 8
if less := maxCap - e.dst.Len(); less < 0 {
err := fmt.Errorf(
"wrong version(%d) cap(%d bits) and could not contain all bits: %d bits",
e.version.Ver, maxCap, e.dst.Len(),
)
panic(err)
} else if less < 4 {
e.dst.AppendNumBools(less, false)
} else {
e.dst.AppendNumBools(4, false)
}
// append `0` to be 8 times bits length
if mod := e.dst.Len() % 8; mod != 0 {
e.dst.AppendNumBools(8-mod, false)
}
// _defaultPadding bytes
// _defaultPadding byte 11101100 00010001
if n := maxCap - e.dst.Len(); n > 0 {
debugLogf("maxCap: %d, len: %d, less: %d", maxCap, e.dst.Len(), n)
for i := 1; i <= (n / 8); i++ {
if i%2 == 1 {
e.dst.Append(paddingByte1)
} else {
e.dst.Append(paddingByte2)
}
}
}
}
// 字符计数指示符位长字典
var charCountMap = map[string]int{
"9_numeric": 10,
"9_alphanumeric": 9,
"9_byte": 8,
"9_japan": 8,
"26_numeric": 12,
"26_alphanumeric": 11,
"26_byte": 16,
"26_japan": 10,
"40_numeric": 14,
"40_alphanumeric": 13,
"40_byte": 16,
"40_japan": 12,
}
// charCountBits
func (e *encoder) charCountBits() int {
var lv int
if v := e.version.Ver; v <= 9 {
lv = 9
} else if v <= 26 {
lv = 26
} else {
lv = 40
}
pos := fmt.Sprintf("%d_%s", lv, getEncModeName(e.mode))
return charCountMap[pos]
}
// v must be a QR Code defined alphanumeric character: 0-9, A-Z, SP, $%*+-./ or
// :. The characters are mapped to values in the range 0-44 respectively.
func encodeAlphanumericCharacter(v byte) uint32 {
c := uint32(v)
switch {
case c >= '0' && c <= '9':
// 0-9 encoded as 0-9.
return c - '0'
case c >= 'A' && c <= 'Z':
// A-Z encoded as 10-35.
return c - 'A' + 10
case c == ' ':
return 36
case c == '$':
return 37
case c == '%':
return 38
case c == '*':
return 39
case c == '+':
return 40
case c == '-':
return 41
case c == '.':
return 42
case c == '/':
return 43
case c == ':':
return 44
default:
log.Panicf("encodeAlphanumericCharacter() with non alphanumeric char %c", v)
}
return 0
}
// analyzeEncFunc returns true is current byte matched in current mode,
// otherwise means you should use a bigger character set to check.
type analyzeEncFunc func(byte) bool
// analyzeEncodeModeFromRaw try to detect letter set of input data,
// so that encoder can determine which mode should be use.
// reference: https://en.wikipedia.org/wiki/QR_code
//
// case1: only numbers, use EncModeNumeric.
// case2: could not use EncModeNumeric, but you can find all of them in character mapping, use EncModeAlphanumeric.
// case3: could not use EncModeAlphanumeric, but you can find all of them in ISO-8859-1 character set, use EncModeByte.
// case4: could not use EncModeByte, use EncModeJP, no more choice.
//
func analyzeEncodeModeFromRaw(raw []byte) encMode {
analyzeFnMapping := map[encMode]analyzeEncFunc{
EncModeNumeric: analyzeNum,
EncModeAlphanumeric: analyzeAlphaNum,
EncModeByte: nil,
EncModeJP: nil,
}
var (
f analyzeEncFunc
mode = EncModeNumeric
)
// loop to check each character in raw data,
// from low mode to higher while current mode could bearing the input data.
for _, byt := range raw {
reAnalyze:
if f = analyzeFnMapping[mode]; f == nil {
break
}
// issue#28 @borislavone reports this bug.
// FIXED(@yeqown): next encMode analyzeVersionAuto func did not check the previous byte,
// add goto statement to reanalyze previous byte which can't be analyzed in last encMode.
if !f(byt) {
mode <<= 1
goto reAnalyze
}
}
return mode
}
// analyzeNum is byt in num encMode
func analyzeNum(byt byte) bool {
return byt >= '0' && byt <= '9'
}
// analyzeAlphaNum is byt in alpha number
func analyzeAlphaNum(byt byte) bool {
if (byt >= '0' && byt <= '9') || (byt >= 'A' && byt <= 'Z') {
return true
}
switch byt {
case ' ', '$', '%', '*', '+', '-', '.', '/', ':':
return true
}
return false
}
//// analyzeByte is byt in bytes.
//func analyzeByte(byt byte) qrbool {
// return false
//}