bare-script 3.8.15 → 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.
@@ -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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "bare-script",
4
- "version": "3.8.15",
4
+ "version": "3.8.16",
5
5
  "description": "BareScript; a lightweight scripting and expression language",
6
6
  "keywords": [
7
7
  "expression",