bare-script 3.8.14 → 3.8.16
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.
- package/lib/include/qrcode.bare +825 -0
- package/lib/library.js +69 -1
- package/package.json +1 -1
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
# Licensed under the MIT License
|
|
2
|
+
# https://github.com/craigahobbs/bare-script/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# $function: qrcodeDraw
|
|
6
|
+
# $group: qrcode.bare
|
|
7
|
+
# $doc: Draw a QR code at the specified position and size
|
|
8
|
+
# $arg message: The QR code message or the QR code matrix
|
|
9
|
+
# $arg x: The X-coordinate, in pixels, of the left-side of the QR code
|
|
10
|
+
# $arg y: The Y-coordinate, in pixels, of the top of the QR code
|
|
11
|
+
# $arg size: The size of the QR code, in pixels
|
|
12
|
+
# $arg level: Optional (default is 'low'). The error correction level: 'low', 'medium', 'quartile', or 'high'.
|
|
13
|
+
function qrcodeDraw(message, x, y, size, level):
|
|
14
|
+
# Get the QR code pixel matrix
|
|
15
|
+
matrix = if(systemType(message) == 'string', qrcodeMatrix(message, level), message)
|
|
16
|
+
qrcodeSize = arrayLength(matrix)
|
|
17
|
+
|
|
18
|
+
# Draw the white background
|
|
19
|
+
drawStyle('none', 0, 'white')
|
|
20
|
+
drawRect(x, y, size, size)
|
|
21
|
+
|
|
22
|
+
# Draw the black pixels
|
|
23
|
+
# Note: The standard recommendation is a 4-module quiet zone on all sides.
|
|
24
|
+
drawStyle('none', 0, 'black')
|
|
25
|
+
ixRow = 0
|
|
26
|
+
pixelSize = size / (qrcodeSize + 8)
|
|
27
|
+
borderSize = pixelSize * 4
|
|
28
|
+
precision = 6
|
|
29
|
+
pixelSizeRound = mathRound(pixelSize, precision)
|
|
30
|
+
while ixRow < qrcodeSize:
|
|
31
|
+
ixCol = 0
|
|
32
|
+
while ixCol < qrcodeSize:
|
|
33
|
+
pixelValue = arrayGet(arrayGet(matrix, ixRow), ixCol)
|
|
34
|
+
if pixelValue:
|
|
35
|
+
drawPathRect( \
|
|
36
|
+
mathRound(borderSize + x + ixCol * pixelSize, precision), \
|
|
37
|
+
mathRound(borderSize + y + ixRow * pixelSize, precision), \
|
|
38
|
+
pixelSizeRound, \
|
|
39
|
+
pixelSizeRound \
|
|
40
|
+
)
|
|
41
|
+
endif
|
|
42
|
+
ixCol = ixCol + 1
|
|
43
|
+
endwhile
|
|
44
|
+
ixRow = ixRow + 1
|
|
45
|
+
endwhile
|
|
46
|
+
endfunction
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# $function: qrcodeMatrix
|
|
50
|
+
# $group: qrcode.bare
|
|
51
|
+
# $doc: Generate a QR code pixel matrix
|
|
52
|
+
# $arg message: The QR code message
|
|
53
|
+
# $arg level: Optional (default is 'low'). The error correction level: 'low', 'medium', 'quartile', or 'high'.
|
|
54
|
+
# $return: The QR code pixel matrix
|
|
55
|
+
function qrcodeMatrix(message, level):
|
|
56
|
+
level = if(level != null, level, 'low')
|
|
57
|
+
|
|
58
|
+
# Find the QR code model
|
|
59
|
+
for qrcode in qrcodeVersions:
|
|
60
|
+
size = objectGet(qrcode, 'size')
|
|
61
|
+
alignmentPatterns = objectGet(qrcode, 'alignmentPatterns')
|
|
62
|
+
ecLevel = objectGet(qrcode, level)
|
|
63
|
+
totalCodewords = objectGet(ecLevel, 'totalCodewords')
|
|
64
|
+
|
|
65
|
+
# Create the pixel matrix
|
|
66
|
+
matrix = []
|
|
67
|
+
ixRow = 0
|
|
68
|
+
while ixRow < size:
|
|
69
|
+
arrayPush(matrix, arrayNewSize(size, null))
|
|
70
|
+
ixRow = ixRow + 1
|
|
71
|
+
endwhile
|
|
72
|
+
|
|
73
|
+
# Position squares
|
|
74
|
+
qrcodeMatrixPositionSquare(matrix, 3, 3)
|
|
75
|
+
qrcodeMatrixPositionSquare(matrix, 3, size - 4)
|
|
76
|
+
qrcodeMatrixPositionSquare(matrix, size - 4, 3)
|
|
77
|
+
|
|
78
|
+
# Alignment patterns
|
|
79
|
+
if alignmentPatterns:
|
|
80
|
+
ixLast = arrayLength(alignmentPatterns) - 1
|
|
81
|
+
for alignX, ixX in alignmentPatterns:
|
|
82
|
+
for alignY, ixY in alignmentPatterns:
|
|
83
|
+
if !(ixX == 0 && ixY == 0) && !(ixX == 0 && ixY == ixLast) && !(ixX == ixLast && ixY == 0):
|
|
84
|
+
qrcodeMatrixAlignmentPattern(matrix, alignX, alignY)
|
|
85
|
+
endif
|
|
86
|
+
endfor
|
|
87
|
+
endfor
|
|
88
|
+
endif
|
|
89
|
+
|
|
90
|
+
# Timing strip
|
|
91
|
+
qrcodeMatrixTimingStrips(matrix)
|
|
92
|
+
|
|
93
|
+
# Format strips - use mask 0 as placeholder bits for qrcodeDataPixels
|
|
94
|
+
qrcodeMatrixFormatStrips(matrix, level, 0)
|
|
95
|
+
|
|
96
|
+
# Version blocks
|
|
97
|
+
qrcodeMatrixVersionBlocks(matrix, qrcode)
|
|
98
|
+
|
|
99
|
+
# Get the message bits (mode, version, message, terminator) - non-null means we've found it
|
|
100
|
+
messageBits = qrcodeMessageBits(qrcode, level, message)
|
|
101
|
+
if messageBits != null:
|
|
102
|
+
# Compute the data pixels
|
|
103
|
+
dataPixels = qrcodeDataPixels(matrix)
|
|
104
|
+
|
|
105
|
+
# Compute the data bits
|
|
106
|
+
dataBits = qrcodeDataBits(qrcode, level, message)
|
|
107
|
+
|
|
108
|
+
# Add the data remainder bits
|
|
109
|
+
remainderLength = arrayLength(dataPixels) - totalCodewords * 8
|
|
110
|
+
if remainderLength > 0:
|
|
111
|
+
arrayExtend(dataBits, arrayNewSize(remainderLength, 0))
|
|
112
|
+
endif
|
|
113
|
+
|
|
114
|
+
# Compute the penalty for each mask
|
|
115
|
+
bestMatrix = null
|
|
116
|
+
bestPenalty = null
|
|
117
|
+
mask = 0
|
|
118
|
+
while mask < 8:
|
|
119
|
+
# Set full data bits with mask
|
|
120
|
+
maskMatrix = jsonParse(jsonStringify(matrix))
|
|
121
|
+
qrcodeMatrixPixels(maskMatrix, dataPixels, dataBits, mask)
|
|
122
|
+
|
|
123
|
+
# Set the format strips bits
|
|
124
|
+
qrcodeMatrixFormatStrips(maskMatrix, level, mask)
|
|
125
|
+
|
|
126
|
+
# Compute the mask's penalty
|
|
127
|
+
maskPenalty = qrcodeMatrixPenalty(maskMatrix)
|
|
128
|
+
if bestPenalty == null || maskPenalty < bestPenalty:
|
|
129
|
+
bestMatrix = maskMatrix
|
|
130
|
+
bestPenalty = maskPenalty
|
|
131
|
+
endif
|
|
132
|
+
mask = mask + 1
|
|
133
|
+
endwhile
|
|
134
|
+
|
|
135
|
+
return bestMatrix
|
|
136
|
+
endif
|
|
137
|
+
endfor
|
|
138
|
+
|
|
139
|
+
# Message too long
|
|
140
|
+
systemLogDebug('qrcode.bare: qrcodeMatrix - Message too long (' + stringLength(message) + ')')
|
|
141
|
+
return null
|
|
142
|
+
endfunction
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Set a position square's pixels
|
|
146
|
+
function qrcodeMatrixPositionSquare(matrix, x, y):
|
|
147
|
+
qrcodeMatrixSquare(matrix, x - 4, y - 4, 9, 0)
|
|
148
|
+
qrcodeMatrixSquare(matrix, x - 3, y - 3, 7, 1)
|
|
149
|
+
qrcodeMatrixSquare(matrix, x - 2, y - 2, 5, 0)
|
|
150
|
+
qrcodeMatrixSquare(matrix, x - 1, y - 1, 3, 1)
|
|
151
|
+
endfunction
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Set an alignment pattern's pixels
|
|
155
|
+
function qrcodeMatrixAlignmentPattern(matrix, x, y):
|
|
156
|
+
qrcodeMatrixSquare(matrix, x - 2, y - 2, 5, 1)
|
|
157
|
+
qrcodeMatrixSquare(matrix, x - 1, y - 1, 3, 0)
|
|
158
|
+
qrcodeMatrixSquare(matrix, x, y, 1, 1)
|
|
159
|
+
endfunction
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Set a solid square's pixels
|
|
163
|
+
function qrcodeMatrixSquare(matrix, x, y, size, colorValue):
|
|
164
|
+
xEnd = mathMin(arrayLength(matrix), x + size)
|
|
165
|
+
yEnd = mathMin(arrayLength(matrix), y + size)
|
|
166
|
+
iy = mathMax(0, y)
|
|
167
|
+
while iy < yEnd:
|
|
168
|
+
ix = mathMax(0, x)
|
|
169
|
+
while ix < xEnd:
|
|
170
|
+
arraySet(arrayGet(matrix, iy), ix, colorValue)
|
|
171
|
+
ix = ix + 1
|
|
172
|
+
endwhile
|
|
173
|
+
iy = iy + 1
|
|
174
|
+
endwhile
|
|
175
|
+
endfunction
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# Set the timing strip pixels
|
|
179
|
+
function qrcodeMatrixTimingStrips(matrix):
|
|
180
|
+
size = arrayLength(matrix)
|
|
181
|
+
ix = 8
|
|
182
|
+
ixEnd = size - 8
|
|
183
|
+
while ix < ixEnd:
|
|
184
|
+
arraySet(arrayGet(matrix, 6), ix, if(ix % 2, 0, 1))
|
|
185
|
+
arraySet(arrayGet(matrix, ix), 6, if(ix % 2, 0, 1))
|
|
186
|
+
ix = ix + 1
|
|
187
|
+
endwhile
|
|
188
|
+
endfunction
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Set the format strips pixels
|
|
192
|
+
function qrcodeMatrixFormatStrips(matrix, level, mask):
|
|
193
|
+
# The "dark pixel"
|
|
194
|
+
size = arrayLength(matrix)
|
|
195
|
+
arraySet(arrayGet(matrix, size - 8), 8, 1)
|
|
196
|
+
|
|
197
|
+
# Set the format strip bits
|
|
198
|
+
formatStripBits = objectGet(objectGet(qrcodeMatrixFormatStripsBitsLookup, level), stringNew(mask))
|
|
199
|
+
pixelsTopLeft = [ \
|
|
200
|
+
[0, 8], [1, 8], [2, 8], [3, 8], [4, 8], [5, 8], [7, 8], [8, 8], \
|
|
201
|
+
[8, 7], [8, 5], [8, 4], [8, 3], [8, 2], [8, 1], [8, 0] \
|
|
202
|
+
]
|
|
203
|
+
pixelsNonTopLeft = [ \
|
|
204
|
+
[8, size - 1], [8, size - 2], [8, size - 3], [8, size - 4], [8, size - 5], [8, size - 6], [8, size - 7], [size - 8, 8], \
|
|
205
|
+
[size - 7, 8], [size - 6, 8], [size - 5, 8], [size - 4, 8], [size - 3, 8], [size - 2, 8], [size - 1, 8] \
|
|
206
|
+
]
|
|
207
|
+
qrcodeMatrixPixels(matrix, pixelsTopLeft, formatStripBits)
|
|
208
|
+
qrcodeMatrixPixels(matrix, pixelsNonTopLeft, formatStripBits)
|
|
209
|
+
endfunction
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# Format strip bits lookup table
|
|
213
|
+
qrcodeMatrixFormatStripsBitsLookup = { \
|
|
214
|
+
'low': { \
|
|
215
|
+
'0': [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0], \
|
|
216
|
+
'1': [1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1], \
|
|
217
|
+
'2': [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0], \
|
|
218
|
+
'3': [1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1], \
|
|
219
|
+
'4': [1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1], \
|
|
220
|
+
'5': [1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0], \
|
|
221
|
+
'6': [1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], \
|
|
222
|
+
'7': [1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0] \
|
|
223
|
+
}, \
|
|
224
|
+
'medium': { \
|
|
225
|
+
'0': [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0], \
|
|
226
|
+
'1': [1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1], \
|
|
227
|
+
'2': [1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0], \
|
|
228
|
+
'3': [1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1], \
|
|
229
|
+
'4': [1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1], \
|
|
230
|
+
'5': [1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0], \
|
|
231
|
+
'6': [1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1], \
|
|
232
|
+
'7': [1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0] \
|
|
233
|
+
}, \
|
|
234
|
+
'quartile': { \
|
|
235
|
+
'0': [0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1], \
|
|
236
|
+
'1': [0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0], \
|
|
237
|
+
'2': [0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1], \
|
|
238
|
+
'3': [0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0], \
|
|
239
|
+
'4': [0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0], \
|
|
240
|
+
'5': [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1], \
|
|
241
|
+
'6': [0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0], \
|
|
242
|
+
'7': [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1] \
|
|
243
|
+
}, \
|
|
244
|
+
'high': { \
|
|
245
|
+
'0': [0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1], \
|
|
246
|
+
'1': [0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0], \
|
|
247
|
+
'2': [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1], \
|
|
248
|
+
'3': [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0], \
|
|
249
|
+
'4': [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0], \
|
|
250
|
+
'5': [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1], \
|
|
251
|
+
'6': [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0], \
|
|
252
|
+
'7': [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1] \
|
|
253
|
+
} \
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# Set the version blocks pixels
|
|
258
|
+
function qrcodeMatrixVersionBlocks(matrix, qrcode):
|
|
259
|
+
version = objectGet(qrcode, 'version')
|
|
260
|
+
versionBits = objectGet(qrcodeMatrixVersionBitsLookup, stringNew(version))
|
|
261
|
+
if versionBits:
|
|
262
|
+
size = arrayLength(matrix)
|
|
263
|
+
bottomLeftPixels = [ \
|
|
264
|
+
[5, size - 9], [5, size - 10], [5, size - 11], [4, size - 9], [4, size - 10], [4, size - 11], \
|
|
265
|
+
[3, size - 9], [3, size - 10], [3, size - 11], [2, size - 9], [2, size - 10], [2, size - 11], \
|
|
266
|
+
[1, size - 9], [1, size - 10], [1, size - 11], [0, size - 9], [0, size - 10], [0, size - 11] \
|
|
267
|
+
]
|
|
268
|
+
topRightPixels = [ \
|
|
269
|
+
[size - 9, 5], [size - 10, 5], [size - 11, 5], [size - 9, 4], [size - 10, 4], [size - 11, 4], \
|
|
270
|
+
[size - 9, 3], [size - 10, 3], [size - 11, 3], [size - 9, 2], [size - 10, 2], [size - 11, 2], \
|
|
271
|
+
[size - 9, 1], [size - 10, 1], [size - 11, 1], [size - 9, 0], [size - 10, 0], [size - 11, 0] \
|
|
272
|
+
]
|
|
273
|
+
qrcodeMatrixPixels(matrix, bottomLeftPixels, versionBits)
|
|
274
|
+
qrcodeMatrixPixels(matrix, topRightPixels, versionBits)
|
|
275
|
+
endif
|
|
276
|
+
endfunction
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# Version bits lookup table
|
|
280
|
+
qrcodeMatrixVersionBitsLookup = { \
|
|
281
|
+
'7': [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0], \
|
|
282
|
+
'10': [0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1], \
|
|
283
|
+
'15': [0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0] \
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Set a pixel array's pixels
|
|
288
|
+
function qrcodeMatrixPixels(matrix, pixels, bits, mask):
|
|
289
|
+
for bitValue, ixBit in bits:
|
|
290
|
+
pixel = arrayGet(pixels, ixBit)
|
|
291
|
+
ix = arrayGet(pixel, 0)
|
|
292
|
+
iy = arrayGet(pixel, 1)
|
|
293
|
+
|
|
294
|
+
# Apply an XOR mask, if requested
|
|
295
|
+
if mask != null:
|
|
296
|
+
if mask == 0:
|
|
297
|
+
maskBit = if((iy + ix) % 2, 0, 1)
|
|
298
|
+
elif mask == 1:
|
|
299
|
+
maskBit = if(iy % 2, 0, 1)
|
|
300
|
+
elif mask == 2:
|
|
301
|
+
maskBit = if(ix % 3, 0, 1)
|
|
302
|
+
elif mask == 3:
|
|
303
|
+
maskBit = if((iy + ix) % 3, 0, 1)
|
|
304
|
+
elif mask == 4:
|
|
305
|
+
maskBit = if((mathFloor(iy / 2) + mathFloor(ix / 3)) % 2, 0, 1)
|
|
306
|
+
elif mask == 5:
|
|
307
|
+
maskBit = if((iy * ix) % 2 + (iy * ix) % 3, 0, 1)
|
|
308
|
+
elif mask == 6:
|
|
309
|
+
maskBit = if((((iy * ix) % 2) + ((iy * ix) % 3)) % 2, 0, 1)
|
|
310
|
+
else: # mask == 7
|
|
311
|
+
maskBit = if((((iy + ix) % 2) + ((iy * ix) % 3)) % 2, 0, 1)
|
|
312
|
+
endif
|
|
313
|
+
bitValue = bitValue ^ maskBit
|
|
314
|
+
endif
|
|
315
|
+
|
|
316
|
+
# Set the pixel
|
|
317
|
+
arraySet(arrayGet(matrix, iy), ix, bitValue)
|
|
318
|
+
endfor
|
|
319
|
+
endfunction
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# Get the list of available data pixel positions in the QR code
|
|
323
|
+
function qrcodeDataPixels(matrix):
|
|
324
|
+
dataPixels = []
|
|
325
|
+
size = arrayLength(matrix)
|
|
326
|
+
|
|
327
|
+
# Iterate pixel pairs - up and down, right to left
|
|
328
|
+
x = size - 1
|
|
329
|
+
y = size - 1
|
|
330
|
+
up = true
|
|
331
|
+
while true:
|
|
332
|
+
# Add the pixel pair (right then left) if its unused
|
|
333
|
+
if arrayGet(arrayGet(matrix, y), x) == null:
|
|
334
|
+
arrayPush(dataPixels, [x, y])
|
|
335
|
+
endif
|
|
336
|
+
if x > 0 && arrayGet(arrayGet(matrix, y), x - 1) == null:
|
|
337
|
+
arrayPush(dataPixels, [x - 1, y])
|
|
338
|
+
endif
|
|
339
|
+
|
|
340
|
+
# Move the pixel pair
|
|
341
|
+
if (up && y == 0) || (!up && y == size - 1):
|
|
342
|
+
up = !up
|
|
343
|
+
x = x - 2
|
|
344
|
+
if x == 6:
|
|
345
|
+
x = 5
|
|
346
|
+
elif x < 0:
|
|
347
|
+
break
|
|
348
|
+
endif
|
|
349
|
+
else:
|
|
350
|
+
y = y + if(up, -1, 1)
|
|
351
|
+
endif
|
|
352
|
+
endwhile
|
|
353
|
+
|
|
354
|
+
return dataPixels
|
|
355
|
+
endfunction
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# Get the full data bits (includes mode, size, message, terminator, padding, ec)
|
|
359
|
+
function qrcodeDataBits(qrcode, level, message):
|
|
360
|
+
ecLevel = objectGet(qrcode, level)
|
|
361
|
+
totalMessageCodewords = objectGet(ecLevel, 'totalMessageCodewords')
|
|
362
|
+
errorCodewordsPerBlock = objectGet(ecLevel, 'errorCodewordsPerBlock')
|
|
363
|
+
blockGroups = objectGet(ecLevel, 'blockGroups')
|
|
364
|
+
|
|
365
|
+
# Compute the message bits
|
|
366
|
+
messageBits = qrcodeMessageBits(qrcode, level, message)
|
|
367
|
+
|
|
368
|
+
# Padding bits
|
|
369
|
+
paddingBits = []
|
|
370
|
+
remainingBitCount = totalMessageCodewords * 8 - arrayLength(messageBits)
|
|
371
|
+
byteCount = mathFloor(remainingBitCount / 8)
|
|
372
|
+
ix = 0
|
|
373
|
+
while ix < byteCount:
|
|
374
|
+
padValue = if(ix % 2, 17, 236)
|
|
375
|
+
padBits = qrcodeBytesToBits([padValue])
|
|
376
|
+
arrayExtend(paddingBits, padBits)
|
|
377
|
+
ix = ix + 1
|
|
378
|
+
endwhile
|
|
379
|
+
|
|
380
|
+
# Compute the full data bytes
|
|
381
|
+
fullMessageBits = []
|
|
382
|
+
arrayExtend(fullMessageBits, messageBits)
|
|
383
|
+
arrayExtend(fullMessageBits, paddingBits)
|
|
384
|
+
fullMessageBytes = qrcodeBitsToBytes(fullMessageBits)
|
|
385
|
+
|
|
386
|
+
# Data group/block interleaving and error correction codeword generation
|
|
387
|
+
allDataBlocks = []
|
|
388
|
+
allECBlocks = []
|
|
389
|
+
maxDataBlocks = 0
|
|
390
|
+
byteIndex = 0
|
|
391
|
+
for blockGroup in blockGroups:
|
|
392
|
+
blockCount = objectGet(blockGroup, 'blockCount')
|
|
393
|
+
dataCodewordsPerBlock = objectGet(blockGroup, 'dataCodewordsPerBlock')
|
|
394
|
+
ixBlock = 0
|
|
395
|
+
while ixBlock < blockCount:
|
|
396
|
+
# Extract data bytes for this block
|
|
397
|
+
blockDataBytes = arraySlice(fullMessageBytes, byteIndex, byteIndex + dataCodewordsPerBlock)
|
|
398
|
+
maxDataBlocks = mathMax(maxDataBlocks, arrayLength(blockDataBytes))
|
|
399
|
+
arrayPush(allDataBlocks, blockDataBytes)
|
|
400
|
+
|
|
401
|
+
# Generate error correction codewords
|
|
402
|
+
ecCodewords = qrcodeRSEncode(blockDataBytes, errorCodewordsPerBlock)
|
|
403
|
+
arrayPush(allECBlocks, ecCodewords)
|
|
404
|
+
|
|
405
|
+
byteIndex = byteIndex + dataCodewordsPerBlock
|
|
406
|
+
ixBlock = ixBlock + 1
|
|
407
|
+
endwhile
|
|
408
|
+
endfor
|
|
409
|
+
|
|
410
|
+
# Interleave data blocks
|
|
411
|
+
interleaved = []
|
|
412
|
+
ixByte = 0
|
|
413
|
+
while ixByte < maxDataBlocks:
|
|
414
|
+
for dataBlock in allDataBlocks:
|
|
415
|
+
if ixByte < arrayLength(dataBlock):
|
|
416
|
+
arrayPush(interleaved, arrayGet(dataBlock, ixByte))
|
|
417
|
+
endif
|
|
418
|
+
endfor
|
|
419
|
+
ixByte = ixByte + 1
|
|
420
|
+
endwhile
|
|
421
|
+
|
|
422
|
+
# Interleave error correction blocks
|
|
423
|
+
ixByte = 0
|
|
424
|
+
while ixByte < errorCodewordsPerBlock:
|
|
425
|
+
for ecBlock in allECBlocks:
|
|
426
|
+
if ixByte < arrayLength(ecBlock):
|
|
427
|
+
arrayPush(interleaved, arrayGet(ecBlock, ixByte))
|
|
428
|
+
endif
|
|
429
|
+
endfor
|
|
430
|
+
ixByte = ixByte + 1
|
|
431
|
+
endwhile
|
|
432
|
+
|
|
433
|
+
# Convert back to bits
|
|
434
|
+
return qrcodeBytesToBits(interleaved)
|
|
435
|
+
endfunction
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# Get the terminated message bits (mode, size, message, terminator)
|
|
439
|
+
function qrcodeMessageBits(qrcode, level, message):
|
|
440
|
+
version = objectGet(qrcode, 'version')
|
|
441
|
+
ecLevel = objectGet(qrcode, level)
|
|
442
|
+
totalMessageCodewords = objectGet(ecLevel, 'totalMessageCodewords')
|
|
443
|
+
|
|
444
|
+
# Determine the message mode
|
|
445
|
+
# Note: Kanji and ECI modes not supported
|
|
446
|
+
if regexMatch(qrcodeModeNumericRegex, message):
|
|
447
|
+
modeBits = [0, 0, 0, 1]
|
|
448
|
+
messageBits = qrcodeModeNumericBits(message)
|
|
449
|
+
messageSize = stringLength(message)
|
|
450
|
+
sizeBitsLength = if(version < 10, 10, if(version < 27, 12, 14))
|
|
451
|
+
elif regexMatch(qrcodeModeAlphanumericRegex, message):
|
|
452
|
+
modeBits = [0, 0, 1, 0]
|
|
453
|
+
messageBits = qrcodeModeAlphanumericBits(message)
|
|
454
|
+
messageSize = stringLength(message)
|
|
455
|
+
sizeBitsLength = if(version < 10, 9, if(version < 27, 11, 13))
|
|
456
|
+
else:
|
|
457
|
+
modeBits = [0, 1, 0, 0]
|
|
458
|
+
messageBytes = stringEncode(message)
|
|
459
|
+
messageBits = qrcodeBytesToBits(messageBytes)
|
|
460
|
+
messageSize = arrayLength(messageBytes)
|
|
461
|
+
sizeBitsLength = if(version < 10, 8, 16)
|
|
462
|
+
endif
|
|
463
|
+
|
|
464
|
+
# Add terminator (min(4, remaining) zeros)
|
|
465
|
+
usedBitCount = arrayLength(modeBits) + sizeBitsLength + arrayLength(messageBits)
|
|
466
|
+
remainingBitCount = totalMessageCodewords * 8 - usedBitCount
|
|
467
|
+
if remainingBitCount < 0:
|
|
468
|
+
return null
|
|
469
|
+
endif
|
|
470
|
+
termLen = mathMin(4, remainingBitCount)
|
|
471
|
+
terminatorBits = arrayNewSize(termLen, 0)
|
|
472
|
+
|
|
473
|
+
# Add bit padding (zeros to align to multiple of 8)
|
|
474
|
+
usedBitCount = usedBitCount + termLen
|
|
475
|
+
alignZeros = (8 - (usedBitCount % 8)) % 8
|
|
476
|
+
alignBits = arrayNewSize(alignZeros, 0)
|
|
477
|
+
|
|
478
|
+
# Compute the total message bits
|
|
479
|
+
totalMessageBits = []
|
|
480
|
+
arrayExtend(totalMessageBits, modeBits)
|
|
481
|
+
arrayExtend(totalMessageBits, qrcodeBytesToBits([messageSize], sizeBitsLength))
|
|
482
|
+
arrayExtend(totalMessageBits, messageBits)
|
|
483
|
+
arrayExtend(totalMessageBits, terminatorBits)
|
|
484
|
+
arrayExtend(totalMessageBits, alignBits)
|
|
485
|
+
return totalMessageBits
|
|
486
|
+
endfunction
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# Mode determination regex
|
|
490
|
+
qrcodeModeNumericRegex = regexNew('^[0-9]+$')
|
|
491
|
+
qrcodeModeAlphanumericRegex = regexNew('^[0-9A-Z $%*+-./:]+$')
|
|
492
|
+
qrcodeModeAlphanumericStr = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
# Numeric mode message encoding
|
|
496
|
+
function qrcodeModeNumericBits(message):
|
|
497
|
+
messageBits = []
|
|
498
|
+
messageLength = stringLength(message)
|
|
499
|
+
ixChar = 0
|
|
500
|
+
while ixChar < messageLength:
|
|
501
|
+
chunk = stringSlice(message, ixChar, mathMin(messageLength, ixChar + 3))
|
|
502
|
+
chunkLength = stringLength(chunk)
|
|
503
|
+
chunkValue = numberParseInt(chunk)
|
|
504
|
+
if chunkLength == 1:
|
|
505
|
+
arrayExtend(messageBits, qrcodeBytesToBits([chunkValue], 4))
|
|
506
|
+
elif chunkLength == 2:
|
|
507
|
+
arrayExtend(messageBits, qrcodeBytesToBits([chunkValue], 7))
|
|
508
|
+
else: # chunkLength == 3
|
|
509
|
+
arrayExtend(messageBits, qrcodeBytesToBits([chunkValue], 10))
|
|
510
|
+
endif
|
|
511
|
+
ixChar = ixChar + 3
|
|
512
|
+
endwhile
|
|
513
|
+
return messageBits
|
|
514
|
+
endfunction
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# Numeric mode message encoding
|
|
518
|
+
function qrcodeModeAlphanumericBits(message):
|
|
519
|
+
messageBits = []
|
|
520
|
+
messageLength = stringLength(message)
|
|
521
|
+
messageLengthMinusOne = messageLength - 1
|
|
522
|
+
ixChar = 0
|
|
523
|
+
while ixChar < messageLength:
|
|
524
|
+
value1 = stringIndexOf(qrcodeModeAlphanumericStr, stringSlice(message, ixChar, ixChar + 1))
|
|
525
|
+
value2 = if(ixChar < messageLengthMinusOne, stringIndexOf(qrcodeModeAlphanumericStr, stringSlice(message, ixChar + 1, ixChar + 2)))
|
|
526
|
+
if value2 == null:
|
|
527
|
+
arrayExtend(messageBits, qrcodeBytesToBits([value1], 6))
|
|
528
|
+
else:
|
|
529
|
+
arrayExtend(messageBits, qrcodeBytesToBits([45 * value1 + value2], 11))
|
|
530
|
+
endif
|
|
531
|
+
ixChar = ixChar + 2
|
|
532
|
+
endwhile
|
|
533
|
+
return messageBits
|
|
534
|
+
endfunction
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# Convert a byte value array to bit array
|
|
538
|
+
function qrcodeBytesToBits(bytes, bitsLength):
|
|
539
|
+
bitsLength = if(bitsLength, bitsLength, 8)
|
|
540
|
+
bits = []
|
|
541
|
+
for byteValue in bytes:
|
|
542
|
+
reversedBits = []
|
|
543
|
+
ixBit = 0
|
|
544
|
+
while ixBit < bitsLength:
|
|
545
|
+
arrayPush(reversedBits, byteValue & 1)
|
|
546
|
+
byteValue = byteValue >> 1
|
|
547
|
+
ixBit = ixBit + 1
|
|
548
|
+
endwhile
|
|
549
|
+
arrayExtend(bits, arrayReverse(reversedBits))
|
|
550
|
+
endfor
|
|
551
|
+
return bits
|
|
552
|
+
endfunction
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
# Convert bit array to byte array
|
|
556
|
+
function qrcodeBitsToBytes(bits):
|
|
557
|
+
bytes = []
|
|
558
|
+
ixBit = 0
|
|
559
|
+
bitsLength = arrayLength(bits)
|
|
560
|
+
while ixBit < bitsLength:
|
|
561
|
+
byteBits = arraySlice(bits, ixBit, mathMin(ixBit + 8, bitsLength))
|
|
562
|
+
arrayExtend(byteBits, arrayNewSize(8 - arrayLength(byteBits), 0))
|
|
563
|
+
byteValue = numberParseInt(arrayJoin(byteBits, ''), 2)
|
|
564
|
+
arrayPush(bytes, byteValue)
|
|
565
|
+
ixBit = ixBit + 8
|
|
566
|
+
endwhile
|
|
567
|
+
return bytes
|
|
568
|
+
endfunction
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
# Compute the masked matrix penalty
|
|
572
|
+
function qrcodeMatrixPenalty(matrix):
|
|
573
|
+
size = arrayLength(matrix)
|
|
574
|
+
penalty = 0
|
|
575
|
+
|
|
576
|
+
# Add the string rows
|
|
577
|
+
stringRowsAndColumns = []
|
|
578
|
+
for row in matrix:
|
|
579
|
+
arrayPush(stringRowsAndColumns, arrayJoin(row, ''))
|
|
580
|
+
endfor
|
|
581
|
+
|
|
582
|
+
# Add the string columns - also compute the 2x2 count and "dark" count
|
|
583
|
+
twoByCount = 0
|
|
584
|
+
twoBySize = size - 1
|
|
585
|
+
darkCount = 0
|
|
586
|
+
ix = 0
|
|
587
|
+
while ix < size:
|
|
588
|
+
column = []
|
|
589
|
+
iy = 0
|
|
590
|
+
while iy < size:
|
|
591
|
+
row = arrayGet(matrix, iy)
|
|
592
|
+
row2 = if(ix < twoBySize && iy < twoBySize, arrayGet(matrix, iy + 1))
|
|
593
|
+
bit = arrayGet(row, ix)
|
|
594
|
+
|
|
595
|
+
# Add the column bit
|
|
596
|
+
arrayPush(column, bit)
|
|
597
|
+
|
|
598
|
+
# Count the 2x2 blocks
|
|
599
|
+
if row2 && bit == arrayGet(row, ix + 1) && bit == arrayGet(row2, ix) && bit == arrayGet(row2, ix + 1):
|
|
600
|
+
twoByCount = twoByCount + 1
|
|
601
|
+
endif
|
|
602
|
+
|
|
603
|
+
# Count "dark" pixels
|
|
604
|
+
if bit:
|
|
605
|
+
darkCount = darkCount + 1
|
|
606
|
+
endif
|
|
607
|
+
|
|
608
|
+
iy = iy + 1
|
|
609
|
+
endwhile
|
|
610
|
+
|
|
611
|
+
# Add the column string
|
|
612
|
+
arrayPush(stringRowsAndColumns, arrayJoin(column, ''))
|
|
613
|
+
ix = ix + 1
|
|
614
|
+
endwhile
|
|
615
|
+
|
|
616
|
+
# Penalty 1: Five or more same-colored modules in a row/column
|
|
617
|
+
for stringRow in stringRowsAndColumns:
|
|
618
|
+
for runMatch in regexMatchAll(qrcodeMatrixPenaltyRunRegex, stringRow):
|
|
619
|
+
penalty = penalty + 3 + (stringLength(objectGet(objectGet(runMatch, 'groups'), '0')) - 5)
|
|
620
|
+
endfor
|
|
621
|
+
endfor
|
|
622
|
+
|
|
623
|
+
# Penalty 2: 2x2 blocks of same color
|
|
624
|
+
penalty = penalty + 3 * twoByCount
|
|
625
|
+
|
|
626
|
+
# Penalty 3: Finder-like patterns (1011101 with 4 white modules on either side)
|
|
627
|
+
for stringRow in stringRowsAndColumns:
|
|
628
|
+
penalty = penalty + 40 * arrayLength(regexMatchAll(qrcodeMatrixPenaltyPatternRegex, stringRow))
|
|
629
|
+
endfor
|
|
630
|
+
|
|
631
|
+
# Penalty 4: Proportion of dark modules
|
|
632
|
+
darkPercent = (darkCount * 100) / (size * size)
|
|
633
|
+
penalty = penalty + mathFloor(mathAbs(darkPercent - 50) / 5) * 10
|
|
634
|
+
|
|
635
|
+
return penalty
|
|
636
|
+
endfunction
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# QR matrix scorinn pattern regular expressions
|
|
640
|
+
qrcodeMatrixPenaltyRunRegex = regexNew('(?:0{5,}|1{5,})')
|
|
641
|
+
qrcodeMatrixPenaltyPatternRegex = regexNew('(?:10111010000|00001011101)')
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
# Generate Reed-Solomon error correction codewords
|
|
645
|
+
function qrcodeRSEncode(bytesIn, numCodewords):
|
|
646
|
+
# Copy input message and append zero bytes for EC codewords
|
|
647
|
+
bytesOut = arrayCopy(bytesIn)
|
|
648
|
+
arrayExtend(bytesOut, arrayNewSize(numCodewords, 0))
|
|
649
|
+
|
|
650
|
+
# Get the generator polynomial coefficients (stored as alpha exponents)
|
|
651
|
+
genExponents = objectGet(qrcodeRSGeneratorPolynomials, stringNew(numCodewords))
|
|
652
|
+
lenGen = arrayLength(genExponents)
|
|
653
|
+
lenMsg = arrayLength(bytesIn)
|
|
654
|
+
|
|
655
|
+
# Perform Reed-Solomon division (long division in GF(256))
|
|
656
|
+
i = 0
|
|
657
|
+
while i < lenMsg:
|
|
658
|
+
leadCoef = arrayGet(bytesOut, i)
|
|
659
|
+
if leadCoef != 0:
|
|
660
|
+
# Precompute log of leading coefficient for faster GF multiplication
|
|
661
|
+
logLead = arrayGet(qrcodeGaloisFieldLogTable, leadCoef)
|
|
662
|
+
|
|
663
|
+
# Apply generator polynomial (skip j=0 term — it's always 1)
|
|
664
|
+
j = 1
|
|
665
|
+
while j < lenGen:
|
|
666
|
+
oldVal = arrayGet(bytesOut, i + j)
|
|
667
|
+
|
|
668
|
+
# Convert generator coefficient from alpha exponent to field element
|
|
669
|
+
genCoefExp = arrayGet(genExponents, j)
|
|
670
|
+
genCoef = if(genCoefExp == 0, 1, arrayGet(qrcodeGaloisFieldExpTable, genCoefExp))
|
|
671
|
+
|
|
672
|
+
# GF multiply: leadCoef * genCoef → using log tables
|
|
673
|
+
productExp = (logLead + arrayGet(qrcodeGaloisFieldLogTable, genCoef)) % 255
|
|
674
|
+
product = arrayGet(qrcodeGaloisFieldExpTable, productExp)
|
|
675
|
+
|
|
676
|
+
# XOR with existing value (subtraction in GF(2^8))
|
|
677
|
+
arraySet(bytesOut, i + j, oldVal ^ product)
|
|
678
|
+
j = j + 1
|
|
679
|
+
endwhile
|
|
680
|
+
endif
|
|
681
|
+
i = i + 1
|
|
682
|
+
endwhile
|
|
683
|
+
|
|
684
|
+
# Return only the error correction codewords (the remainder)
|
|
685
|
+
return arraySlice(bytesOut, lenMsg)
|
|
686
|
+
endfunction
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
# Reed-Solomon error correction codeword count (string) to generator polynomial exponents
|
|
690
|
+
qrcodeRSGeneratorPolynomials = { \
|
|
691
|
+
'10': [1, 251, 67, 46, 61, 118, 70, 64, 94, 32, 45], \
|
|
692
|
+
'15': [1, 8, 183, 61, 91, 202, 37, 51, 58, 58, 237, 140, 124, 5, 99, 105], \
|
|
693
|
+
'16': [1, 120, 104, 107, 109, 102, 161, 76, 3, 91, 191, 147, 169, 182, 194, 225, 120], \
|
|
694
|
+
'18': [1, 215, 234, 158, 94, 184, 97, 118, 170, 79, 187, 152, 148, 252, 179, 5, 98, 96, 153], \
|
|
695
|
+
'20': [1, 17, 60, 79, 50, 61, 163, 26, 187, 202, 180, 221, 225, 83, 239, 156, 164, 212, 212, 188, 190], \
|
|
696
|
+
'22': [1, 210, 171, 247, 242, 93, 230, 14, 109, 221, 53, 200, 74, 8, 172, 98, 80, 219, 134, 160, 105, 165, 231], \
|
|
697
|
+
'24': [1, 229, 121, 135, 48, 211, 117, 251, 126, 159, 180, 169, 152, 192, 226, 228, 218, 111, 0, 117, 232, 87, 96, 227, 21], \
|
|
698
|
+
'26': [1, 173, 125, 158, 2, 103, 182, 118, 17, 145, 201, 111, 28, 165, 53, 161, 21, 245, 142, 13, 102, 48, 227, 153, 145, 218, 70], \
|
|
699
|
+
'28': [1, 168, 223, 200, 104, 224, 234, 108, 180, 110, 190, 195, 147, 205, 27, 232, 201, 21, 43, 245, 87, 42, 195, 212, 119, 242, 37, 9, 123], \
|
|
700
|
+
'30': [1, 41, 173, 145, 152, 216, 31, 179, 182, 50, 48, 110, 86, 239, 96, 222, 125, 42, 173, 226, 193, 224, 130, 156, 37, 251, 216, 238, 40, 192, 180] \
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
# GF(256) EXP table
|
|
705
|
+
qrcodeGaloisFieldExpTable = [ \
|
|
706
|
+
1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, \
|
|
707
|
+
24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, \
|
|
708
|
+
160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, \
|
|
709
|
+
253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, \
|
|
710
|
+
26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, \
|
|
711
|
+
46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, \
|
|
712
|
+
228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, \
|
|
713
|
+
150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, \
|
|
714
|
+
83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, \
|
|
715
|
+
247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1 \
|
|
716
|
+
]
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
# GF(256) LOG table
|
|
720
|
+
qrcodeGaloisFieldLogTable = [ \
|
|
721
|
+
null, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, \
|
|
722
|
+
248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, \
|
|
723
|
+
39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, \
|
|
724
|
+
136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, \
|
|
725
|
+
107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, \
|
|
726
|
+
192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, \
|
|
727
|
+
217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, \
|
|
728
|
+
20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, \
|
|
729
|
+
111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, \
|
|
730
|
+
81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 \
|
|
731
|
+
]
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
# QR Code version information table
|
|
735
|
+
# 0 - Version
|
|
736
|
+
# 1 - Size
|
|
737
|
+
# 2 - Alignment Pattern Center Positions
|
|
738
|
+
qrcodeVersionTable = [ \
|
|
739
|
+
[2, 25, 6, 18], \
|
|
740
|
+
[3, 29, 6, 22], \
|
|
741
|
+
[5, 37, 6, 30], \
|
|
742
|
+
[7, 45, 6, 22, 38], \
|
|
743
|
+
[10, 57, 6, 28, 50], \
|
|
744
|
+
[15, 77, 6, 26, 48, 70] \
|
|
745
|
+
]
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
# Error correction configuration table
|
|
749
|
+
# 0 - Version
|
|
750
|
+
# 1 - EC Level
|
|
751
|
+
# 2 - Total Number of Data Codewords for this Version and EC Level
|
|
752
|
+
# 3 - EC Codewords Per Block
|
|
753
|
+
# 4 - Number of Blocks in Group 1
|
|
754
|
+
# 5 - Number of Data Codewords in Each of Group 1's Blocks
|
|
755
|
+
# 6 - Number of Blocks in Group 2
|
|
756
|
+
# 7 - Number of Data Codewords in Each of Group 2's Blocks
|
|
757
|
+
qrcodeErrorCorrectionTable = [ \
|
|
758
|
+
[2, 'low', 34, 10, 1, 34, null, null], \
|
|
759
|
+
[2, 'medium', 28, 16, 1, 28, null, null], \
|
|
760
|
+
[2, 'quartile', 22, 22, 1, 22, null, null], \
|
|
761
|
+
[2, 'high', 16, 28, 1, 16, null, null], \
|
|
762
|
+
[3, 'low', 55, 15, 1, 55, null, null], \
|
|
763
|
+
[3, 'medium', 44, 26, 1, 44, null, null], \
|
|
764
|
+
[3, 'quartile', 34, 18, 2, 17, null, null], \
|
|
765
|
+
[3, 'high', 26, 22, 2, 13, null, null], \
|
|
766
|
+
[5, 'low', 108, 26, 1, 108, null, null], \
|
|
767
|
+
[5, 'medium', 86, 24, 2, 43, null, null], \
|
|
768
|
+
[5, 'quartile', 62, 18, 2, 15, 2, 16], \
|
|
769
|
+
[5, 'high', 46, 22, 2, 11, 2, 12], \
|
|
770
|
+
[7, 'low', 156, 20, 2, 78, null, null], \
|
|
771
|
+
[7, 'medium', 124, 18, 4, 31, null, null], \
|
|
772
|
+
[7, 'quartile', 88, 18, 2, 14, 4, 15], \
|
|
773
|
+
[7, 'high', 66, 26, 4, 13, 1, 14], \
|
|
774
|
+
[10, 'low', 274, 18, 2, 68, 2, 69], \
|
|
775
|
+
[10, 'medium', 216, 26, 4, 43, 1, 44], \
|
|
776
|
+
[10, 'quartile', 154, 24, 6, 19, 2, 20], \
|
|
777
|
+
[10, 'high', 122, 28, 6, 15, 2, 16], \
|
|
778
|
+
[15, 'low', 523, 22, 5, 87, 1, 88], \
|
|
779
|
+
[15, 'medium', 415, 24, 5, 41, 5, 42], \
|
|
780
|
+
[15, 'quartile', 295, 30, 5, 24, 7, 25], \
|
|
781
|
+
[15, 'high', 223, 24, 11, 12, 7, 13] \
|
|
782
|
+
]
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
# QR code models sorted ascending by version
|
|
786
|
+
function qrcodeVersionsCreate():
|
|
787
|
+
versions = []
|
|
788
|
+
for row in qrcodeVersionTable:
|
|
789
|
+
# Create the QR code model
|
|
790
|
+
version = arrayGet(row, 0)
|
|
791
|
+
qrcode = { \
|
|
792
|
+
'version': version, \
|
|
793
|
+
'size': arrayGet(row, 1), \
|
|
794
|
+
'alignmentPatterns': arraySlice(row, 2) \
|
|
795
|
+
}
|
|
796
|
+
arrayPush(versions, qrcode)
|
|
797
|
+
|
|
798
|
+
# Add the error correction configurations
|
|
799
|
+
for ecRow in qrcodeErrorCorrectionTable:
|
|
800
|
+
ecVersion = arrayGet(ecRow, 0)
|
|
801
|
+
if ecVersion == version:
|
|
802
|
+
level = arrayGet(ecRow, 1)
|
|
803
|
+
totalDataCodewords = arrayGet(ecRow, 2)
|
|
804
|
+
ecCodewordsPerBlock = arrayGet(ecRow, 3)
|
|
805
|
+
numBlocksGroup1 = arrayGet(ecRow, 4)
|
|
806
|
+
numBlocksGroup2 = arrayGet(ecRow, 6)
|
|
807
|
+
totalECCodewords = ecCodewordsPerBlock * (numBlocksGroup1 + (numBlocksGroup2 || 0))
|
|
808
|
+
blockGroups = [ \
|
|
809
|
+
{'blockCount': numBlocksGroup1, 'dataCodewordsPerBlock': arrayGet(ecRow, 5)} \
|
|
810
|
+
]
|
|
811
|
+
if numBlocksGroup2 != null:
|
|
812
|
+
arrayPush(blockGroups, {'blockCount': numBlocksGroup2, 'dataCodewordsPerBlock': arrayGet(ecRow, 7)})
|
|
813
|
+
endif
|
|
814
|
+
objectSet(qrcode, level, { \
|
|
815
|
+
'totalCodewords': totalDataCodewords + totalECCodewords, \
|
|
816
|
+
'totalMessageCodewords': totalDataCodewords, \
|
|
817
|
+
'errorCodewordsPerBlock': ecCodewordsPerBlock, \
|
|
818
|
+
'blockGroups': blockGroups \
|
|
819
|
+
})
|
|
820
|
+
endif
|
|
821
|
+
endfor
|
|
822
|
+
endfor
|
|
823
|
+
return versions
|
|
824
|
+
endfunction
|
|
825
|
+
qrcodeVersions = qrcodeVersionsCreate()
|
package/lib/library.js
CHANGED
|
@@ -281,6 +281,21 @@ const arrayPushArgs = valueArgsModel([
|
|
|
281
281
|
]);
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
// $function: arrayReverse
|
|
285
|
+
// $group: array
|
|
286
|
+
// $doc: Reverse an array in place
|
|
287
|
+
// $arg array: The array
|
|
288
|
+
// $return: The reversed array
|
|
289
|
+
function arrayReverse(args) {
|
|
290
|
+
const [array] = valueArgsValidate(arrayReverseArgs, args);
|
|
291
|
+
return array.reverse();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const arrayReverseArgs = valueArgsModel([
|
|
295
|
+
{'name': 'array', 'type': 'array'}
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
|
|
284
299
|
// $function: arraySet
|
|
285
300
|
// $group: array
|
|
286
301
|
// $doc: Set an array element value
|
|
@@ -352,7 +367,7 @@ const arraySliceArgs = valueArgsModel([
|
|
|
352
367
|
|
|
353
368
|
// $function: arraySort
|
|
354
369
|
// $group: array
|
|
355
|
-
// $doc: Sort an array
|
|
370
|
+
// $doc: Sort an array in place
|
|
356
371
|
// $arg array: The array
|
|
357
372
|
// $arg compareFn: Optional (default is null). The comparison function.
|
|
358
373
|
// $return: The sorted array
|
|
@@ -1175,6 +1190,23 @@ const numberToFixedArgs = valueArgsModel([
|
|
|
1175
1190
|
const rNumberCleanup = /\.0*$/;
|
|
1176
1191
|
|
|
1177
1192
|
|
|
1193
|
+
// $function: numberToString
|
|
1194
|
+
// $group: number
|
|
1195
|
+
// $doc: Convert an integer to a string
|
|
1196
|
+
// $arg x: The integer
|
|
1197
|
+
// $arg radix: Optional (default is 10). The number base.
|
|
1198
|
+
// $return: The integer as a string of the given base
|
|
1199
|
+
function numberToString(args) {
|
|
1200
|
+
const [x, radix] = valueArgsValidate(numberToStringArgs, args);
|
|
1201
|
+
return x.toString(radix);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const numberToStringArgs = valueArgsModel([
|
|
1205
|
+
{'name': 'x', 'type': 'number', 'integer': true, 'gte': 0},
|
|
1206
|
+
{'name': 'radix', 'type': 'number', 'default': 10, 'integer': true, 'gte': 2, 'lte': 36}
|
|
1207
|
+
]);
|
|
1208
|
+
|
|
1209
|
+
|
|
1178
1210
|
//
|
|
1179
1211
|
// Object functions
|
|
1180
1212
|
//
|
|
@@ -1624,6 +1656,38 @@ const stringCharCodeAtArgs = valueArgsModel([
|
|
|
1624
1656
|
]);
|
|
1625
1657
|
|
|
1626
1658
|
|
|
1659
|
+
// $function: stringDecode
|
|
1660
|
+
// $group: string
|
|
1661
|
+
// $doc: Decode a UTF-8 byte value array to a string
|
|
1662
|
+
// $arg bytes: The UTF-8 byte array
|
|
1663
|
+
// $return: The string
|
|
1664
|
+
function stringDecode(args) {
|
|
1665
|
+
const [bytes] = valueArgsValidate(stringDecodeArgs, args);
|
|
1666
|
+
const utf8Decoder = new TextDecoder();
|
|
1667
|
+
return utf8Decoder.decode(new Uint8Array(bytes));
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
const stringDecodeArgs = valueArgsModel([
|
|
1671
|
+
{'name': 'bytes', 'type': 'array'}
|
|
1672
|
+
]);
|
|
1673
|
+
|
|
1674
|
+
|
|
1675
|
+
// $function: stringEncode
|
|
1676
|
+
// $group: string
|
|
1677
|
+
// $doc: Encode a string as a UTF-8 byte value array
|
|
1678
|
+
// $arg string: The string
|
|
1679
|
+
// $return: The UTF-8 byte array
|
|
1680
|
+
function stringEncode(args) {
|
|
1681
|
+
const [string] = valueArgsValidate(stringEncodeArgs, args);
|
|
1682
|
+
const utf8Encoder = new TextEncoder();
|
|
1683
|
+
return [...utf8Encoder.encode(string)];
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
const stringEncodeArgs = valueArgsModel([
|
|
1687
|
+
{'name': 'string', 'type': 'string'}
|
|
1688
|
+
]);
|
|
1689
|
+
|
|
1690
|
+
|
|
1627
1691
|
// $function: stringEndsWith
|
|
1628
1692
|
// $group: string
|
|
1629
1693
|
// $doc: Determine if a string ends with a search string
|
|
@@ -2183,6 +2247,7 @@ export const scriptFunctions = {
|
|
|
2183
2247
|
arrayNewSize,
|
|
2184
2248
|
arrayPop,
|
|
2185
2249
|
arrayPush,
|
|
2250
|
+
arrayReverse,
|
|
2186
2251
|
arraySet,
|
|
2187
2252
|
arrayShift,
|
|
2188
2253
|
arraySlice,
|
|
@@ -2235,6 +2300,7 @@ export const scriptFunctions = {
|
|
|
2235
2300
|
numberParseFloat,
|
|
2236
2301
|
numberParseInt,
|
|
2237
2302
|
numberToFixed,
|
|
2303
|
+
numberToString,
|
|
2238
2304
|
objectAssign,
|
|
2239
2305
|
objectCopy,
|
|
2240
2306
|
objectDelete,
|
|
@@ -2255,6 +2321,8 @@ export const scriptFunctions = {
|
|
|
2255
2321
|
schemaValidate,
|
|
2256
2322
|
schemaValidateTypeModel,
|
|
2257
2323
|
stringCharCodeAt,
|
|
2324
|
+
stringDecode,
|
|
2325
|
+
stringEncode,
|
|
2258
2326
|
stringEndsWith,
|
|
2259
2327
|
stringFromCharCode,
|
|
2260
2328
|
stringIndexOf,
|