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