bare-script 3.8.15 → 3.8.17

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.
@@ -1,8 +1,6 @@
1
1
  # Licensed under the MIT License
2
2
  # https://github.com/craigahobbs/bare-script/blob/main/LICENSE
3
3
 
4
- include <dataTable.bare>
5
-
6
4
 
7
5
  # The URL arguments model
8
6
  argsTypes = schemaParse( \
@@ -183,7 +181,7 @@ endfunction
183
181
  # $doc: Generate the [arguments model's](model.html#var.vName='ArgsArguments') help content
184
182
  # $arg arguments: The [arguments model](model.html#var.vName='ArgsArguments')
185
183
  # $return: The array of help Markdown line strings
186
- function argsHelp(arguments):
184
+ async function argsHelp(arguments):
187
185
  # Create the help data
188
186
  helpData = []
189
187
  anyDefault = false
@@ -210,6 +208,10 @@ function argsHelp(arguments):
210
208
  anyDescription = anyDescription || (description != null)
211
209
  endfor
212
210
 
211
+ # Dynamically include dataTable.bare
212
+ include <dataTable.bare>
213
+ dataTableMarkdown = systemGlobalGet('dataTableMarkdown')
214
+
213
215
  # Render the help table
214
216
  helpFields = ['Variable', 'Type']
215
217
  if anyDefault:
@@ -0,0 +1,178 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/bare-script/blob/main/LICENSE
3
+
4
+
5
+ # $function: elementModelValidate
6
+ # $group: elementModel.bare
7
+ # $doc: Validate an element model
8
+ # $arg elements: The element model.
9
+ # $arg elements: An element model is either null, an element object, or an array of any of these.
10
+ # $return: The element model if valid, null otherwise
11
+ function elementModelValidate(elements):
12
+ # Null?
13
+ if elements == null:
14
+ return elements
15
+ endif
16
+
17
+ # Array?
18
+ if systemType(elements) == 'array':
19
+ for subElements in elements:
20
+ if subElements != null && elementModelValidate(subElements) == null:
21
+ return null
22
+ endif
23
+ endfor
24
+ return elements
25
+ endif
26
+
27
+ # Non-object?
28
+ if systemType(elements) != 'object':
29
+ return null
30
+ endif
31
+
32
+ # Check for element tag members and unknown members
33
+ tagMembers = []
34
+ unknownMembers = []
35
+ for elementMember in objectKeys(elements):
36
+ if elementMember == 'html' || elementMember == 'svg' || elementMember == 'text':
37
+ arrayPush(tagMembers, elementMember)
38
+ endif
39
+ if elementMember != 'html' && elementMember != 'svg' && elementMember != 'text' && \
40
+ elementMember != 'attr' && elementMember != 'elem' && elementMember != 'callback':
41
+ arrayPush(unknownMembers, elementMember)
42
+ endif
43
+ endfor
44
+ if arrayLength(tagMembers) != 1:
45
+ return null
46
+ endif
47
+ if arrayLength(unknownMembers) != 0:
48
+ return null
49
+ endif
50
+
51
+ # Validate the tag
52
+ tagMember = arrayGet(tagMembers, 0)
53
+ tag = objectGet(elements, tagMember)
54
+ if tagMember != 'text' && (systemType(tag) != 'string' || stringLength(tag) == 0):
55
+ return null
56
+ endif
57
+
58
+ # Validate attributes
59
+ attr = objectGet(elements, 'attr')
60
+ if objectHas(elements, 'attr') && tagMember == 'text':
61
+ return null
62
+ endif
63
+ if attr != null:
64
+ if systemType(attr) != 'object':
65
+ return null
66
+ endif
67
+ endif
68
+
69
+ # Validate child elements
70
+ elem = objectGet(elements, 'elem')
71
+ if objectHas(elements, 'elem') && tagMember == 'text':
72
+ return null
73
+ endif
74
+ if elem != null:
75
+ if elementModelValidate(elem) == null:
76
+ return null
77
+ endif
78
+ endif
79
+
80
+ # Validate creation callback
81
+ callback = objectGet(elements, 'callback')
82
+ if callback != null && systemType(callback) != 'function':
83
+ return null
84
+ endif
85
+
86
+ return elements
87
+ endfunction
88
+
89
+
90
+ # $function: elementModelToString
91
+ # $group: elementModel.bare
92
+ # $doc: Render an element model to an HTML or SVG string
93
+ # $arg elements: The element model.
94
+ # $arg elements: An element model is either null, an element object, or an array of any of these.
95
+ # $arg indent: Optional (default is null). The indentation string or number of spaces
96
+ # $return: The HTML or SVG string
97
+ function elementModelToString(elements, indent):
98
+ # Compute the indent string
99
+ indentStr = ''
100
+ if systemType(indent) == 'string':
101
+ indentStr = indent
102
+ elif systemType(indent) == 'number':
103
+ indentStr = stringRepeat(' ', mathMax(0, indent))
104
+ endif
105
+
106
+ # Render the element model as an HTML/SVG string
107
+ elementStr = elementModelToStringHelper(elements, indentStr, 0)
108
+ if stringStartsWith(elementStr, '<html'):
109
+ elementStr = '<!DOCTYPE html>' + stringFromCharCode(10) + elementStr
110
+ endif
111
+ return elementStr
112
+ endfunction
113
+
114
+
115
+ # Helper function to render elements to string
116
+ function elementModelToStringHelper(elements, indentStr, level):
117
+ # Null?
118
+ if elements == null:
119
+ return ''
120
+ endif
121
+
122
+ # Array?
123
+ if systemType(elements) == 'array':
124
+ results = []
125
+ for element in elements:
126
+ arrayPush(results, elementModelToStringHelper(element, indentStr, level))
127
+ endfor
128
+ return arrayJoin(results, '')
129
+ endif
130
+
131
+ # Text node
132
+ indentPrefix = stringRepeat(indentStr, level)
133
+ newline = if(indentStr, stringFromCharCode(10), '')
134
+ if objectHas(elements, 'text'):
135
+ text = stringNew(objectGet(elements, 'text'))
136
+ text = stringReplace(text, '&', '&amp;')
137
+ text = stringReplace(text, '<', '&lt;')
138
+ text = stringReplace(text, '>', '&gt;')
139
+ text = stringReplace(text, '"', '&quot;')
140
+ text = stringReplace(text, "'", '&#039;')
141
+ return indentPrefix + text + newline
142
+ endif
143
+
144
+ # Attributes
145
+ tag = stringLower(objectGet(elements, 'html') || objectGet(elements, 'svg'))
146
+ attrStr = ''
147
+ attr = objectGet(elements, 'attr')
148
+ if tag == 'svg':
149
+ if attr == null:
150
+ attr = {}
151
+ endif
152
+ objectSet(attr, 'xmlns', 'http://www.w3.org/2000/svg')
153
+ endif
154
+ if attr != null:
155
+ attrNames = arraySort(objectKeys(attr))
156
+ for attrName in attrNames:
157
+ value = objectGet(attr, attrName)
158
+ if value != null:
159
+ attrStr = attrStr + ' ' + attrName + '="' + stringReplace(stringReplace(stringNew(value), '&', '&amp;'), '"', '&quot;') + '"'
160
+ endif
161
+ endfor
162
+ endif
163
+
164
+ # HTML void element?
165
+ isVoid = arrayIndexOf(elementModelVoidTags, tag) != -1
166
+ if isVoid:
167
+ return indentPrefix + '<' + tag + attrStr + ' />' + newline
168
+ endif
169
+
170
+ # Render the element
171
+ elem = objectGet(elements, 'elem')
172
+ childrenStr = if(elem != null, elementModelToStringHelper(elem, indentStr, level + 1), '')
173
+ return indentPrefix + '<' + tag + attrStr + '>' + newline + childrenStr + indentPrefix + '</' + tag + '>' + newline
174
+ endfunction
175
+
176
+
177
+ # Void elements (simplified list)
178
+ elementModelVoidTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']
@@ -0,0 +1,883 @@
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
+ pixelLeft = mathRound(borderSize + x + ixCol * pixelSize, precision)
36
+ pixelTop = mathRound(borderSize + y + ixRow * pixelSize, precision)
37
+ drawPathRect(pixelLeft, pixelTop, pixelSizeRound, pixelSizeRound)
38
+ endif
39
+ ixCol = ixCol + 1
40
+ endwhile
41
+ ixRow = ixRow + 1
42
+ endwhile
43
+ endfunction
44
+
45
+
46
+ # $function: qrcodeElements
47
+ # $group: qrcode.bare
48
+ # $doc: Generate the element model for a QR code
49
+ # $arg message: The QR code message or the QR code matrix
50
+ # $arg size: The size of the QR code, in pixels
51
+ # $arg level: Optional (default is 'low'). The error correction level: 'low', 'medium', 'quartile', or 'high'.
52
+ # $return: The QR code SVG [element model](https://github.com/craigahobbs/element-model#readme)
53
+ function qrcodeElements(message, size, level):
54
+ # Get the QR code pixel matrix
55
+ matrix = if(systemType(message) == 'string', qrcodeMatrix(message, level), message)
56
+ qrcodeSize = arrayLength(matrix)
57
+
58
+ # Compute the black pixel paths
59
+ # Note: The standard recommendation is a 4-module quiet zone on all sides.
60
+ cellPaths = []
61
+ ixRow = 0
62
+ pixelSize = size / (qrcodeSize + 8)
63
+ borderSize = pixelSize * 4
64
+ precision = 6
65
+ while ixRow < qrcodeSize:
66
+ ixCol = 0
67
+ while ixCol < qrcodeSize:
68
+ pixelValue = arrayGet(arrayGet(matrix, ixRow), ixCol)
69
+ if pixelValue:
70
+ pixelLeft = borderSize + ixCol * pixelSize
71
+ pixelTop = borderSize + ixRow * pixelSize
72
+ arrayPush(cellPaths, arrayJoin([ \
73
+ 'M', numberToFixed(pixelLeft, precision), numberToFixed(pixelTop, precision), \
74
+ 'H', numberToFixed(pixelLeft + pixelSize, precision), \
75
+ 'V', numberToFixed(pixelTop + pixelSize, precision), \
76
+ 'H', numberToFixed(pixelLeft, precision), \
77
+ 'Z' \
78
+ ], ' '))
79
+ endif
80
+ ixCol = ixCol + 1
81
+ endwhile
82
+ ixRow = ixRow + 1
83
+ endwhile
84
+
85
+ # Create the QR code SVG element model
86
+ return { \
87
+ 'svg': 'svg', \
88
+ 'attr': {'width': size, 'height': size}, \
89
+ 'elem': [ \
90
+ { \
91
+ 'svg': 'rect', \
92
+ 'attr': { \
93
+ 'x': '0', 'y': '0', 'width': size, 'height': size, \
94
+ 'fill': 'white', 'stroke': 'none', 'stroke-width': '0', 'stroke-dasharray': 'none' \
95
+ } \
96
+ }, \
97
+ { \
98
+ 'svg': 'path', \
99
+ 'attr': { \
100
+ 'fill': 'black', 'stroke': 'none', 'stroke-width': '0', 'stroke-dasharray': 'none', \
101
+ 'd': arrayJoin(cellPaths, ' ') \
102
+ } \
103
+ } \
104
+ ] \
105
+ }
106
+ endfunction
107
+
108
+
109
+ # $function: qrcodeMatrix
110
+ # $group: qrcode.bare
111
+ # $doc: Generate a QR code pixel matrix
112
+ # $arg message: The QR code message
113
+ # $arg level: Optional (default is 'low'). The error correction level: 'low', 'medium', 'quartile', or 'high'.
114
+ # $return: The QR code pixel matrix
115
+ function qrcodeMatrix(message, level):
116
+ level = if(level != null, level, 'low')
117
+
118
+ # Find the QR code model that fits the message bits (mode, version, message, terminator)
119
+ qrcode = null
120
+ for qrcodeFind in qrcodeVersions:
121
+ messageBits = qrcodeMessageBits(qrcodeFind, level, message)
122
+ if messageBits != null:
123
+ qrcode = qrcodeFind
124
+ break
125
+ endif
126
+ endfor
127
+ if qrcode == null:
128
+ systemLogDebug('qrcode.bare: qrcodeMatrix - Message too long (' + stringLength(message) + ')')
129
+ return null
130
+ endif
131
+ size = objectGet(qrcode, 'size')
132
+ alignmentPatterns = objectGet(qrcode, 'alignmentPatterns')
133
+ totalCodewords = objectGet(objectGet(qrcode, level), 'totalCodewords')
134
+
135
+ # Create the pixel matrix
136
+ matrix = []
137
+ ixRow = 0
138
+ while ixRow < size:
139
+ arrayPush(matrix, arrayNewSize(size, null))
140
+ ixRow = ixRow + 1
141
+ endwhile
142
+
143
+ # Position squares
144
+ qrcodeMatrixPositionSquare(matrix, 3, 3)
145
+ qrcodeMatrixPositionSquare(matrix, 3, size - 4)
146
+ qrcodeMatrixPositionSquare(matrix, size - 4, 3)
147
+
148
+ # Alignment patterns
149
+ if alignmentPatterns:
150
+ ixLast = arrayLength(alignmentPatterns) - 1
151
+ for alignX, ixAX in alignmentPatterns:
152
+ for alignY, ixAY in alignmentPatterns:
153
+ if !(ixAX == 0 && ixAY == 0) && !(ixAX == 0 && ixAY == ixLast) && !(ixAX == ixLast && ixAY == 0):
154
+ qrcodeMatrixAlignmentPattern(matrix, alignX, alignY)
155
+ endif
156
+ endfor
157
+ endfor
158
+ endif
159
+
160
+ # Timing strips
161
+ qrcodeMatrixTimingStrips(matrix)
162
+
163
+ # Format strips - use mask 0 as placeholder bits for qrcodeDataPixels
164
+ qrcodeMatrixFormatStrips(matrix, level, 0)
165
+
166
+ # Version blocks
167
+ qrcodeMatrixVersionBlocks(matrix, qrcode)
168
+
169
+ # Compute the data pixels
170
+ dataPixels = qrcodeDataPixels(matrix)
171
+
172
+ # Compute the data bits
173
+ dataBits = qrcodeDataBits(qrcode, level, message)
174
+
175
+ # Add the data remainder bits
176
+ remainderLength = arrayLength(dataPixels) - totalCodewords * 8
177
+ if remainderLength > 0:
178
+ arrayExtend(dataBits, arrayNewSize(remainderLength, 0))
179
+ endif
180
+
181
+ # Compute the penalty for each mask
182
+ bestMatrix = null
183
+ bestPenalty = null
184
+ mask = 0
185
+ while mask < 8:
186
+ # Set full data bits with mask
187
+ maskMatrix = jsonParse(jsonStringify(matrix))
188
+ qrcodeMatrixPixels(maskMatrix, dataPixels, dataBits, mask)
189
+
190
+ # Set the format strips bits
191
+ qrcodeMatrixFormatStrips(maskMatrix, level, mask)
192
+
193
+ # Compute the mask's penalty
194
+ maskPenalty = qrcodeMatrixPenalty(maskMatrix)
195
+ if bestPenalty == null || maskPenalty < bestPenalty:
196
+ bestMatrix = maskMatrix
197
+ bestPenalty = maskPenalty
198
+ endif
199
+
200
+ mask = mask + 1
201
+ endwhile
202
+
203
+ return bestMatrix
204
+ endfunction
205
+
206
+
207
+ # Set a position square's pixels
208
+ function qrcodeMatrixPositionSquare(matrix, x, y):
209
+ qrcodeMatrixSquare(matrix, x - 4, y - 4, 9, 0)
210
+ qrcodeMatrixSquare(matrix, x - 3, y - 3, 7, 1)
211
+ qrcodeMatrixSquare(matrix, x - 2, y - 2, 5, 0)
212
+ qrcodeMatrixSquare(matrix, x - 1, y - 1, 3, 1)
213
+ endfunction
214
+
215
+
216
+ # Set an alignment pattern's pixels
217
+ function qrcodeMatrixAlignmentPattern(matrix, x, y):
218
+ qrcodeMatrixSquare(matrix, x - 2, y - 2, 5, 1)
219
+ qrcodeMatrixSquare(matrix, x - 1, y - 1, 3, 0)
220
+ qrcodeMatrixSquare(matrix, x, y, 1, 1)
221
+ endfunction
222
+
223
+
224
+ # Set a solid square's pixels
225
+ function qrcodeMatrixSquare(matrix, x, y, size, colorValue):
226
+ xEnd = mathMin(arrayLength(matrix), x + size)
227
+ yEnd = mathMin(arrayLength(matrix), y + size)
228
+ iy = mathMax(0, y)
229
+ while iy < yEnd:
230
+ ix = mathMax(0, x)
231
+ while ix < xEnd:
232
+ arraySet(arrayGet(matrix, iy), ix, colorValue)
233
+ ix = ix + 1
234
+ endwhile
235
+ iy = iy + 1
236
+ endwhile
237
+ endfunction
238
+
239
+
240
+ # Set the timing strip pixels
241
+ function qrcodeMatrixTimingStrips(matrix):
242
+ size = arrayLength(matrix)
243
+ ix = 8
244
+ ixEnd = size - 8
245
+ while ix < ixEnd:
246
+ arraySet(arrayGet(matrix, 6), ix, if(ix % 2, 0, 1))
247
+ arraySet(arrayGet(matrix, ix), 6, if(ix % 2, 0, 1))
248
+ ix = ix + 1
249
+ endwhile
250
+ endfunction
251
+
252
+
253
+ # Set the format strips pixels
254
+ function qrcodeMatrixFormatStrips(matrix, level, mask):
255
+ size = arrayLength(matrix)
256
+
257
+ # The "dark pixel"
258
+ arraySet(arrayGet(matrix, size - 8), 8, 1)
259
+
260
+ # Set the format strip bits
261
+ formatStripBits = objectGet(objectGet(qrcodeMatrixFormatStripsBitsTable, level), stringNew(mask))
262
+ pixelsTopLeft = [ \
263
+ [0, 8], [1, 8], [2, 8], [3, 8], [4, 8], [5, 8], [7, 8], [8, 8], \
264
+ [8, 7], [8, 5], [8, 4], [8, 3], [8, 2], [8, 1], [8, 0] \
265
+ ]
266
+ pixelsNonTopLeft = [ \
267
+ [8, size - 1], [8, size - 2], [8, size - 3], [8, size - 4], [8, size - 5], [8, size - 6], [8, size - 7], [size - 8, 8], \
268
+ [size - 7, 8], [size - 6, 8], [size - 5, 8], [size - 4, 8], [size - 3, 8], [size - 2, 8], [size - 1, 8] \
269
+ ]
270
+ qrcodeMatrixPixels(matrix, pixelsTopLeft, formatStripBits)
271
+ qrcodeMatrixPixels(matrix, pixelsNonTopLeft, formatStripBits)
272
+ endfunction
273
+
274
+
275
+ # Format strip bits lookup table
276
+ qrcodeMatrixFormatStripsBitsTable = { \
277
+ 'low': { \
278
+ '0': [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0], \
279
+ '1': [1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1], \
280
+ '2': [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0], \
281
+ '3': [1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1], \
282
+ '4': [1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1], \
283
+ '5': [1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0], \
284
+ '6': [1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], \
285
+ '7': [1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0] \
286
+ }, \
287
+ 'medium': { \
288
+ '0': [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0], \
289
+ '1': [1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1], \
290
+ '2': [1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0], \
291
+ '3': [1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1], \
292
+ '4': [1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1], \
293
+ '5': [1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0], \
294
+ '6': [1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1], \
295
+ '7': [1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0] \
296
+ }, \
297
+ 'quartile': { \
298
+ '0': [0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1], \
299
+ '1': [0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0], \
300
+ '2': [0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1], \
301
+ '3': [0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0], \
302
+ '4': [0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0], \
303
+ '5': [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1], \
304
+ '6': [0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0], \
305
+ '7': [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1] \
306
+ }, \
307
+ 'high': { \
308
+ '0': [0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1], \
309
+ '1': [0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0], \
310
+ '2': [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1], \
311
+ '3': [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0], \
312
+ '4': [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0], \
313
+ '5': [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1], \
314
+ '6': [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0], \
315
+ '7': [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1] \
316
+ } \
317
+ }
318
+
319
+
320
+ # Set the version blocks pixels
321
+ function qrcodeMatrixVersionBlocks(matrix, qrcode):
322
+ version = objectGet(qrcode, 'version')
323
+ versionBits = objectGet(qrcodeMatrixVersionBitsTable, stringNew(version))
324
+ if versionBits:
325
+ size = arrayLength(matrix)
326
+ bottomLeftPixels = [ \
327
+ [5, size - 9], [5, size - 10], [5, size - 11], [4, size - 9], [4, size - 10], [4, size - 11], \
328
+ [3, size - 9], [3, size - 10], [3, size - 11], [2, size - 9], [2, size - 10], [2, size - 11], \
329
+ [1, size - 9], [1, size - 10], [1, size - 11], [0, size - 9], [0, size - 10], [0, size - 11] \
330
+ ]
331
+ topRightPixels = [ \
332
+ [size - 9, 5], [size - 10, 5], [size - 11, 5], [size - 9, 4], [size - 10, 4], [size - 11, 4], \
333
+ [size - 9, 3], [size - 10, 3], [size - 11, 3], [size - 9, 2], [size - 10, 2], [size - 11, 2], \
334
+ [size - 9, 1], [size - 10, 1], [size - 11, 1], [size - 9, 0], [size - 10, 0], [size - 11, 0] \
335
+ ]
336
+ qrcodeMatrixPixels(matrix, bottomLeftPixels, versionBits)
337
+ qrcodeMatrixPixels(matrix, topRightPixels, versionBits)
338
+ endif
339
+ endfunction
340
+
341
+
342
+ # Version bits lookup table
343
+ qrcodeMatrixVersionBitsTable = { \
344
+ '7': [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0], \
345
+ '10': [0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1], \
346
+ '15': [0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0] \
347
+ }
348
+
349
+
350
+ # Set a pixel array's pixels
351
+ function qrcodeMatrixPixels(matrix, pixels, bits, mask):
352
+ for bitValue, ixBit in bits:
353
+ pixel = arrayGet(pixels, ixBit)
354
+ ix = arrayGet(pixel, 0)
355
+ iy = arrayGet(pixel, 1)
356
+
357
+ # Apply an XOR mask, if requested
358
+ if mask != null:
359
+ if mask == 0:
360
+ maskBit = if((iy + ix) % 2, 0, 1)
361
+ elif mask == 1:
362
+ maskBit = if(iy % 2, 0, 1)
363
+ elif mask == 2:
364
+ maskBit = if(ix % 3, 0, 1)
365
+ elif mask == 3:
366
+ maskBit = if((iy + ix) % 3, 0, 1)
367
+ elif mask == 4:
368
+ maskBit = if((mathFloor(iy / 2) + mathFloor(ix / 3)) % 2, 0, 1)
369
+ elif mask == 5:
370
+ maskBit = if((iy * ix) % 2 + (iy * ix) % 3, 0, 1)
371
+ elif mask == 6:
372
+ maskBit = if((((iy * ix) % 2) + ((iy * ix) % 3)) % 2, 0, 1)
373
+ else: # mask == 7
374
+ maskBit = if((((iy + ix) % 2) + ((iy * ix) % 3)) % 2, 0, 1)
375
+ endif
376
+ bitValue = bitValue ^ maskBit
377
+ endif
378
+
379
+ # Set the pixel
380
+ arraySet(arrayGet(matrix, iy), ix, bitValue)
381
+ endfor
382
+ endfunction
383
+
384
+
385
+ # Get the list of available data pixel positions in the QR code
386
+ function qrcodeDataPixels(matrix):
387
+ dataPixels = []
388
+ size = arrayLength(matrix)
389
+
390
+ # Iterate pixel pairs - up and down, right to left
391
+ x = size - 1
392
+ y = size - 1
393
+ up = true
394
+ while true:
395
+ # Add the pixel pair (right then left) if its unused
396
+ if arrayGet(arrayGet(matrix, y), x) == null:
397
+ arrayPush(dataPixels, [x, y])
398
+ endif
399
+ if x > 0 && arrayGet(arrayGet(matrix, y), x - 1) == null:
400
+ arrayPush(dataPixels, [x - 1, y])
401
+ endif
402
+
403
+ # Move the pixel pair
404
+ if (up && y == 0) || (!up && y == size - 1):
405
+ up = !up
406
+ x = x - 2
407
+ if x == 6:
408
+ x = 5
409
+ elif x < 0:
410
+ break
411
+ endif
412
+ else:
413
+ y = y + if(up, -1, 1)
414
+ endif
415
+ endwhile
416
+
417
+ return dataPixels
418
+ endfunction
419
+
420
+
421
+ # Get the full data bits (includes mode, size, message, terminator, padding, ec)
422
+ function qrcodeDataBits(qrcode, level, message):
423
+ ecLevel = objectGet(qrcode, level)
424
+ totalMessageCodewords = objectGet(ecLevel, 'totalMessageCodewords')
425
+ errorCodewordsPerBlock = objectGet(ecLevel, 'errorCodewordsPerBlock')
426
+ blockGroups = objectGet(ecLevel, 'blockGroups')
427
+
428
+ # Compute the message bits
429
+ messageBits = qrcodeMessageBits(qrcode, level, message)
430
+
431
+ # Padding bits
432
+ paddingBits = []
433
+ remainingBitCount = totalMessageCodewords * 8 - arrayLength(messageBits)
434
+ byteCount = mathFloor(remainingBitCount / 8)
435
+ ix = 0
436
+ while ix < byteCount:
437
+ padValue = if(ix % 2, 17, 236)
438
+ padBits = qrcodeBytesToBits([padValue])
439
+ arrayExtend(paddingBits, padBits)
440
+ ix = ix + 1
441
+ endwhile
442
+
443
+ # Compute the full data bytes
444
+ fullMessageBits = []
445
+ arrayExtend(fullMessageBits, messageBits)
446
+ arrayExtend(fullMessageBits, paddingBits)
447
+ fullMessageBytes = qrcodeBitsToBytes(fullMessageBits)
448
+
449
+ # Data group/block interleaving and error correction codeword generation
450
+ allDataBlocks = []
451
+ allECBlocks = []
452
+ maxDataBlocks = 0
453
+ byteIndex = 0
454
+ for blockGroup in blockGroups:
455
+ blockCount = objectGet(blockGroup, 'blockCount')
456
+ dataCodewordsPerBlock = objectGet(blockGroup, 'dataCodewordsPerBlock')
457
+ ixBlock = 0
458
+ while ixBlock < blockCount:
459
+ # Extract data bytes for this block
460
+ blockDataBytes = arraySlice(fullMessageBytes, byteIndex, byteIndex + dataCodewordsPerBlock)
461
+ maxDataBlocks = mathMax(maxDataBlocks, arrayLength(blockDataBytes))
462
+ arrayPush(allDataBlocks, blockDataBytes)
463
+
464
+ # Generate error correction codewords
465
+ ecCodewords = qrcodeRSEncode(blockDataBytes, errorCodewordsPerBlock)
466
+ arrayPush(allECBlocks, ecCodewords)
467
+
468
+ byteIndex = byteIndex + dataCodewordsPerBlock
469
+ ixBlock = ixBlock + 1
470
+ endwhile
471
+ endfor
472
+
473
+ # Interleave data blocks
474
+ interleaved = []
475
+ ixByte = 0
476
+ while ixByte < maxDataBlocks:
477
+ for dataBlock in allDataBlocks:
478
+ if ixByte < arrayLength(dataBlock):
479
+ arrayPush(interleaved, arrayGet(dataBlock, ixByte))
480
+ endif
481
+ endfor
482
+ ixByte = ixByte + 1
483
+ endwhile
484
+
485
+ # Interleave error correction blocks
486
+ ixByte = 0
487
+ while ixByte < errorCodewordsPerBlock:
488
+ for ecBlock in allECBlocks:
489
+ if ixByte < arrayLength(ecBlock):
490
+ arrayPush(interleaved, arrayGet(ecBlock, ixByte))
491
+ endif
492
+ endfor
493
+ ixByte = ixByte + 1
494
+ endwhile
495
+
496
+ # Convert back to bits
497
+ return qrcodeBytesToBits(interleaved)
498
+ endfunction
499
+
500
+
501
+ # Get the terminated message bits (mode, size, message, terminator)
502
+ function qrcodeMessageBits(qrcode, level, message):
503
+ version = objectGet(qrcode, 'version')
504
+ totalMessageCodewords = objectGet(objectGet(qrcode, level), 'totalMessageCodewords')
505
+
506
+ # Determine the message mode
507
+ # Note: Kanji and ECI modes not supported
508
+ if regexMatch(qrcodeModeNumericRegex, message):
509
+ modeBits = [0, 0, 0, 1]
510
+ messageBits = qrcodeModeNumericBits(message)
511
+ messageSize = stringLength(message)
512
+ sizeBitsLength = if(version < 10, 10, if(version < 27, 12, 14))
513
+ elif regexMatch(qrcodeModeAlphanumericRegex, message):
514
+ modeBits = [0, 0, 1, 0]
515
+ messageBits = qrcodeModeAlphanumericBits(message)
516
+ messageSize = stringLength(message)
517
+ sizeBitsLength = if(version < 10, 9, if(version < 27, 11, 13))
518
+ else:
519
+ modeBits = [0, 1, 0, 0]
520
+ messageBytes = stringEncode(message)
521
+ messageBits = qrcodeBytesToBits(messageBytes)
522
+ messageSize = arrayLength(messageBytes)
523
+ sizeBitsLength = if(version < 10, 8, 16)
524
+ endif
525
+
526
+ # Add terminator (min(4, remaining) zeros)
527
+ usedBitCount = arrayLength(modeBits) + sizeBitsLength + arrayLength(messageBits)
528
+ remainingBitCount = totalMessageCodewords * 8 - usedBitCount
529
+ if remainingBitCount < 0:
530
+ return null
531
+ endif
532
+ termLen = mathMin(4, remainingBitCount)
533
+ terminatorBits = arrayNewSize(termLen, 0)
534
+
535
+ # Add bit padding (zeros to align to multiple of 8)
536
+ usedBitCount = usedBitCount + termLen
537
+ alignZeros = (8 - (usedBitCount % 8)) % 8
538
+ alignBits = arrayNewSize(alignZeros, 0)
539
+
540
+ # Compute the total message bits
541
+ totalMessageBits = []
542
+ arrayExtend(totalMessageBits, modeBits)
543
+ arrayExtend(totalMessageBits, qrcodeBytesToBits([messageSize], sizeBitsLength))
544
+ arrayExtend(totalMessageBits, messageBits)
545
+ arrayExtend(totalMessageBits, terminatorBits)
546
+ arrayExtend(totalMessageBits, alignBits)
547
+ return totalMessageBits
548
+ endfunction
549
+
550
+
551
+ # Mode determination regex
552
+ qrcodeModeNumericRegex = regexNew('^[0-9]+$')
553
+ qrcodeModeAlphanumericRegex = regexNew('^[0-9A-Z $%*+-./:]+$')
554
+ qrcodeModeAlphanumericStr = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'
555
+
556
+
557
+ # Numeric mode message encoding
558
+ function qrcodeModeNumericBits(message):
559
+ messageBits = []
560
+ messageLength = stringLength(message)
561
+ ixChar = 0
562
+ while ixChar < messageLength:
563
+ chunk = stringSlice(message, ixChar, mathMin(messageLength, ixChar + 3))
564
+ chunkLength = stringLength(chunk)
565
+ chunkValue = numberParseInt(chunk)
566
+ if chunkLength == 1:
567
+ arrayExtend(messageBits, qrcodeBytesToBits([chunkValue], 4))
568
+ elif chunkLength == 2:
569
+ arrayExtend(messageBits, qrcodeBytesToBits([chunkValue], 7))
570
+ else: # chunkLength == 3
571
+ arrayExtend(messageBits, qrcodeBytesToBits([chunkValue], 10))
572
+ endif
573
+ ixChar = ixChar + 3
574
+ endwhile
575
+ return messageBits
576
+ endfunction
577
+
578
+
579
+ # Numeric mode message encoding
580
+ function qrcodeModeAlphanumericBits(message):
581
+ messageBits = []
582
+ messageLength = stringLength(message)
583
+ messageLengthMinusOne = messageLength - 1
584
+ ixChar = 0
585
+ while ixChar < messageLength:
586
+ value1 = stringIndexOf(qrcodeModeAlphanumericStr, stringSlice(message, ixChar, ixChar + 1))
587
+ value2 = if(ixChar < messageLengthMinusOne, stringIndexOf(qrcodeModeAlphanumericStr, stringSlice(message, ixChar + 1, ixChar + 2)))
588
+ if value2 == null:
589
+ arrayExtend(messageBits, qrcodeBytesToBits([value1], 6))
590
+ else:
591
+ arrayExtend(messageBits, qrcodeBytesToBits([45 * value1 + value2], 11))
592
+ endif
593
+ ixChar = ixChar + 2
594
+ endwhile
595
+ return messageBits
596
+ endfunction
597
+
598
+
599
+ # Convert a byte value array to bit array
600
+ function qrcodeBytesToBits(bytes, bitsLength):
601
+ bitsLength = if(bitsLength, bitsLength, 8)
602
+ bits = []
603
+ for byteValue in bytes:
604
+ reversedBits = []
605
+ ixBit = 0
606
+ while ixBit < bitsLength:
607
+ arrayPush(reversedBits, byteValue & 1)
608
+ byteValue = byteValue >> 1
609
+ ixBit = ixBit + 1
610
+ endwhile
611
+ arrayExtend(bits, arrayReverse(reversedBits))
612
+ endfor
613
+ return bits
614
+ endfunction
615
+
616
+
617
+ # Convert bit array to byte array
618
+ function qrcodeBitsToBytes(bits):
619
+ bytes = []
620
+ ixBit = 0
621
+ bitsLength = arrayLength(bits)
622
+ while ixBit < bitsLength:
623
+ byteBits = arraySlice(bits, ixBit, mathMin(ixBit + 8, bitsLength))
624
+ arrayExtend(byteBits, arrayNewSize(8 - arrayLength(byteBits), 0))
625
+ byteValue = numberParseInt(arrayJoin(byteBits, ''), 2)
626
+ arrayPush(bytes, byteValue)
627
+ ixBit = ixBit + 8
628
+ endwhile
629
+ return bytes
630
+ endfunction
631
+
632
+
633
+ # Compute the masked matrix penalty
634
+ function qrcodeMatrixPenalty(matrix):
635
+ size = arrayLength(matrix)
636
+ penalty = 0
637
+
638
+ # Compute row penalties
639
+ for row in matrix:
640
+ stringRow = arrayJoin(row, '')
641
+
642
+ # Penalty 1: Five or more same-colored modules in a row
643
+ for runMatch in regexMatchAll(qrcodeMatrixPenaltyRunRegex, stringRow):
644
+ penalty = penalty + 3 + (stringLength(objectGet(objectGet(runMatch, 'groups'), '0')) - 5)
645
+ endfor
646
+
647
+ # Penalty 3: Finder-like patterns (1011101 with 4 white modules on either side)
648
+ penalty = penalty + 40 * arrayLength(regexMatchAll(qrcodeMatrixPenaltyPatternRegex, stringRow))
649
+ endfor
650
+
651
+ # Compute column penalties - also count dark modules
652
+ darkCount = 0
653
+ twoBySize = size - 1
654
+ ix = 0
655
+ while ix < size:
656
+ column = []
657
+ iy = 0
658
+ while iy < size:
659
+ row = arrayGet(matrix, iy)
660
+ bit = arrayGet(row, ix)
661
+ arrayPush(column, bit)
662
+
663
+ # Penalty 2: 2x2 blocks of same color
664
+ row2 = if(ix < twoBySize && iy < twoBySize, arrayGet(matrix, iy + 1))
665
+ if row2 && bit == arrayGet(row, ix + 1) && bit == arrayGet(row2, ix) && bit == arrayGet(row2, ix + 1):
666
+ penalty = penalty + 3
667
+ endif
668
+
669
+ # Count "dark" pixels
670
+ darkCount = darkCount + bit
671
+
672
+ iy = iy + 1
673
+ endwhile
674
+ stringColumn = arrayJoin(column, '')
675
+
676
+ # Penalty 1: Five or more same-colored modules in a column
677
+ for runMatch in regexMatchAll(qrcodeMatrixPenaltyRunRegex, stringColumn):
678
+ penalty = penalty + 3 + (stringLength(objectGet(objectGet(runMatch, 'groups'), '0')) - 5)
679
+ endfor
680
+
681
+ # Penalty 3: Finder-like patterns (1011101 with 4 white modules on either side)
682
+ penalty = penalty + 40 * arrayLength(regexMatchAll(qrcodeMatrixPenaltyPatternRegex, stringColumn))
683
+
684
+ ix = ix + 1
685
+ endwhile
686
+
687
+ # Penalty 4: Proportion of dark modules
688
+ darkPercent = (darkCount * 100) / (size * size)
689
+ penalty = penalty + mathFloor(mathAbs(darkPercent - 50) / 5) * 10
690
+
691
+ return penalty
692
+ endfunction
693
+
694
+
695
+ # QR matrix scorinn pattern regular expressions
696
+ qrcodeMatrixPenaltyRunRegex = regexNew('(?:0{5,}|1{5,})')
697
+ qrcodeMatrixPenaltyPatternRegex = regexNew('(?:10111010000|00001011101)')
698
+
699
+
700
+ # Generate Reed-Solomon error correction codewords
701
+ function qrcodeRSEncode(bytesIn, numCodewords):
702
+ # Copy input message and append zero bytes for EC codewords
703
+ bytesOut = arrayCopy(bytesIn)
704
+ arrayExtend(bytesOut, arrayNewSize(numCodewords, 0))
705
+
706
+ # Get the generator polynomial coefficients (stored as alpha exponents)
707
+ genExponents = objectGet(qrcodeRSGeneratorPolynomials, stringNew(numCodewords))
708
+ lenGen = arrayLength(genExponents)
709
+ lenMsg = arrayLength(bytesIn)
710
+
711
+ # Perform Reed-Solomon division (long division in GF(256))
712
+ i = 0
713
+ while i < lenMsg:
714
+ leadCoef = arrayGet(bytesOut, i)
715
+ if leadCoef != 0:
716
+ # Precompute log of leading coefficient for faster GF multiplication
717
+ logLead = arrayGet(qrcodeGaloisFieldLogTable, leadCoef)
718
+
719
+ # Apply generator polynomial (skip j=0 term — it's always 1)
720
+ j = 1
721
+ while j < lenGen:
722
+ oldVal = arrayGet(bytesOut, i + j)
723
+
724
+ # Convert generator coefficient from alpha exponent to field element
725
+ genCoefExp = arrayGet(genExponents, j)
726
+ genCoef = if(genCoefExp == 0, 1, arrayGet(qrcodeGaloisFieldExpTable, genCoefExp))
727
+
728
+ # GF multiply: leadCoef * genCoef → using log tables
729
+ productExp = (logLead + arrayGet(qrcodeGaloisFieldLogTable, genCoef)) % 255
730
+ product = arrayGet(qrcodeGaloisFieldExpTable, productExp)
731
+
732
+ # XOR with existing value (subtraction in GF(2^8))
733
+ arraySet(bytesOut, i + j, oldVal ^ product)
734
+
735
+ j = j + 1
736
+ endwhile
737
+ endif
738
+
739
+ i = i + 1
740
+ endwhile
741
+
742
+ # Return only the error correction codewords (the remainder)
743
+ return arraySlice(bytesOut, lenMsg)
744
+ endfunction
745
+
746
+
747
+ # Reed-Solomon error correction codeword count (string) to generator polynomial exponents
748
+ qrcodeRSGeneratorPolynomials = { \
749
+ '10': [1, 251, 67, 46, 61, 118, 70, 64, 94, 32, 45], \
750
+ '15': [1, 8, 183, 61, 91, 202, 37, 51, 58, 58, 237, 140, 124, 5, 99, 105], \
751
+ '16': [1, 120, 104, 107, 109, 102, 161, 76, 3, 91, 191, 147, 169, 182, 194, 225, 120], \
752
+ '18': [1, 215, 234, 158, 94, 184, 97, 118, 170, 79, 187, 152, 148, 252, 179, 5, 98, 96, 153], \
753
+ '20': [1, 17, 60, 79, 50, 61, 163, 26, 187, 202, 180, 221, 225, 83, 239, 156, 164, 212, 212, 188, 190], \
754
+ '22': [1, 210, 171, 247, 242, 93, 230, 14, 109, 221, 53, 200, 74, 8, 172, 98, 80, 219, 134, 160, 105, 165, 231], \
755
+ '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], \
756
+ '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], \
757
+ '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], \
758
+ '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] \
759
+ }
760
+
761
+
762
+ # GF(256) EXP table
763
+ qrcodeGaloisFieldExpTable = [ \
764
+ 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, \
765
+ 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, \
766
+ 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, \
767
+ 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, \
768
+ 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, \
769
+ 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, \
770
+ 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, \
771
+ 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, \
772
+ 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, \
773
+ 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1 \
774
+ ]
775
+
776
+
777
+ # GF(256) LOG table
778
+ qrcodeGaloisFieldLogTable = [ \
779
+ 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, \
780
+ 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, \
781
+ 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, \
782
+ 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, \
783
+ 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, \
784
+ 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, \
785
+ 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, \
786
+ 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, \
787
+ 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, \
788
+ 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 \
789
+ ]
790
+
791
+
792
+ # QR Code version information table
793
+ # 0 - Version
794
+ # 1 - Size
795
+ # 2 - Alignment Pattern Center Positions
796
+ qrcodeVersionTable = [ \
797
+ [2, 25, 6, 18], \
798
+ [3, 29, 6, 22], \
799
+ [5, 37, 6, 30], \
800
+ [7, 45, 6, 22, 38], \
801
+ [10, 57, 6, 28, 50], \
802
+ [15, 77, 6, 26, 48, 70] \
803
+ ]
804
+
805
+
806
+ # Error correction configuration table
807
+ # 0 - Version
808
+ # 1 - EC Level
809
+ # 2 - Total Number of Data Codewords for this Version and EC Level
810
+ # 3 - EC Codewords Per Block
811
+ # 4 - Number of Blocks in Group 1
812
+ # 5 - Number of Data Codewords in Each of Group 1's Blocks
813
+ # 6 - Number of Blocks in Group 2
814
+ # 7 - Number of Data Codewords in Each of Group 2's Blocks
815
+ qrcodeErrorCorrectionTable = [ \
816
+ [2, 'low', 34, 10, 1, 34, null, null], \
817
+ [2, 'medium', 28, 16, 1, 28, null, null], \
818
+ [2, 'quartile', 22, 22, 1, 22, null, null], \
819
+ [2, 'high', 16, 28, 1, 16, null, null], \
820
+ [3, 'low', 55, 15, 1, 55, null, null], \
821
+ [3, 'medium', 44, 26, 1, 44, null, null], \
822
+ [3, 'quartile', 34, 18, 2, 17, null, null], \
823
+ [3, 'high', 26, 22, 2, 13, null, null], \
824
+ [5, 'low', 108, 26, 1, 108, null, null], \
825
+ [5, 'medium', 86, 24, 2, 43, null, null], \
826
+ [5, 'quartile', 62, 18, 2, 15, 2, 16], \
827
+ [5, 'high', 46, 22, 2, 11, 2, 12], \
828
+ [7, 'low', 156, 20, 2, 78, null, null], \
829
+ [7, 'medium', 124, 18, 4, 31, null, null], \
830
+ [7, 'quartile', 88, 18, 2, 14, 4, 15], \
831
+ [7, 'high', 66, 26, 4, 13, 1, 14], \
832
+ [10, 'low', 274, 18, 2, 68, 2, 69], \
833
+ [10, 'medium', 216, 26, 4, 43, 1, 44], \
834
+ [10, 'quartile', 154, 24, 6, 19, 2, 20], \
835
+ [10, 'high', 122, 28, 6, 15, 2, 16], \
836
+ [15, 'low', 523, 22, 5, 87, 1, 88], \
837
+ [15, 'medium', 415, 24, 5, 41, 5, 42], \
838
+ [15, 'quartile', 295, 30, 5, 24, 7, 25], \
839
+ [15, 'high', 223, 24, 11, 12, 7, 13] \
840
+ ]
841
+
842
+
843
+ # QR code models sorted ascending by version
844
+ function qrcodeVersionsCreate():
845
+ versions = []
846
+ for row in qrcodeVersionTable:
847
+ # Create the QR code model
848
+ version = arrayGet(row, 0)
849
+ qrcode = { \
850
+ 'version': version, \
851
+ 'size': arrayGet(row, 1), \
852
+ 'alignmentPatterns': arraySlice(row, 2) \
853
+ }
854
+ arrayPush(versions, qrcode)
855
+
856
+ # Add the error correction configurations
857
+ for ecRow in qrcodeErrorCorrectionTable:
858
+ ecVersion = arrayGet(ecRow, 0)
859
+ if ecVersion == version:
860
+ level = arrayGet(ecRow, 1)
861
+ totalDataCodewords = arrayGet(ecRow, 2)
862
+ ecCodewordsPerBlock = arrayGet(ecRow, 3)
863
+ numBlocksGroup1 = arrayGet(ecRow, 4)
864
+ numBlocksGroup2 = arrayGet(ecRow, 6)
865
+ totalECCodewords = ecCodewordsPerBlock * (numBlocksGroup1 + (numBlocksGroup2 || 0))
866
+ blockGroups = [ \
867
+ {'blockCount': numBlocksGroup1, 'dataCodewordsPerBlock': arrayGet(ecRow, 5)} \
868
+ ]
869
+ if numBlocksGroup2 != null:
870
+ arrayPush(blockGroups, {'blockCount': numBlocksGroup2, 'dataCodewordsPerBlock': arrayGet(ecRow, 7)})
871
+ endif
872
+ objectSet(qrcode, level, { \
873
+ 'totalCodewords': totalDataCodewords + totalECCodewords, \
874
+ 'totalMessageCodewords': totalDataCodewords, \
875
+ 'errorCodewordsPerBlock': ecCodewordsPerBlock, \
876
+ 'blockGroups': blockGroups \
877
+ })
878
+ endif
879
+ endfor
880
+ endfor
881
+ return versions
882
+ endfunction
883
+ qrcodeVersions = qrcodeVersionsCreate()
@@ -3,7 +3,6 @@
3
3
 
4
4
  include <args.bare>
5
5
  include <dataTable.bare>
6
- include <schemaDoc.bare>
7
6
 
8
7
 
9
8
  # $function: schemaDocMain
package/lib/library.js CHANGED
@@ -1635,6 +1635,27 @@ const schemaValidateTypeModelArgs = valueArgsModel([
1635
1635
  //
1636
1636
 
1637
1637
 
1638
+ // $function: stringCharAt
1639
+ // $group: string
1640
+ // $doc: Get the character of a string at an index
1641
+ // $arg string: The string
1642
+ // $arg index: The index of the character
1643
+ // $return: The character string
1644
+ function stringCharAt(args) {
1645
+ const [string, index] = valueArgsValidate(stringCharAtArgs, args);
1646
+ if (index >= string.length) {
1647
+ throw new ValueArgsError('index', index);
1648
+ }
1649
+
1650
+ return string[index];
1651
+ }
1652
+
1653
+ const stringCharAtArgs = valueArgsModel([
1654
+ {'name': 'string', 'type': 'string'},
1655
+ {'name': 'index', 'type': 'number', 'integer': true, 'gte': 0}
1656
+ ]);
1657
+
1658
+
1638
1659
  // $function: stringCharCodeAt
1639
1660
  // $group: string
1640
1661
  // $doc: Get a string index's character code
@@ -1730,7 +1751,7 @@ function stringFromCharCode(charCodes) {
1730
1751
  // $return: The first index of the search string; -1 if not found.
1731
1752
  function stringIndexOf(args) {
1732
1753
  const [string, search, index] = valueArgsValidate(stringIndexOfArgs, args, -1);
1733
- if (index >= string.length) {
1754
+ if (index > string.length) {
1734
1755
  throw new ValueArgsError('index', index, -1);
1735
1756
  }
1736
1757
 
@@ -1754,7 +1775,7 @@ const stringIndexOfArgs = valueArgsModel([
1754
1775
  function stringLastIndexOf(args) {
1755
1776
  const [string, search, indexArg] = valueArgsValidate(stringLastIndexOfArgs, args, -1);
1756
1777
  const index = indexArg !== null ? indexArg : string.length - 1;
1757
- if (index >= string.length) {
1778
+ if (index > string.length) {
1758
1779
  throw new ValueArgsError('index', index, -1);
1759
1780
  }
1760
1781
 
@@ -2320,6 +2341,7 @@ export const scriptFunctions = {
2320
2341
  schemaTypeModel,
2321
2342
  schemaValidate,
2322
2343
  schemaValidateTypeModel,
2344
+ stringCharAt,
2323
2345
  stringCharCodeAt,
2324
2346
  stringDecode,
2325
2347
  stringEncode,
package/lib/model.js CHANGED
@@ -569,6 +569,8 @@ function isAsyncStatement(statement, globals, isAsyncScope) {
569
569
  const [statementKey] = Object.keys(statement);
570
570
  if (statementKey === 'expr') {
571
571
  return isAsyncExpression(statement.expr.expr, globals, isAsyncScope);
572
+ } else if (statementKey === 'include') {
573
+ return true;
572
574
  } else if (statementKey === 'jump') {
573
575
  return 'expr' in statement.jump ? isAsyncExpression(statement.jump.expr, globals, isAsyncScope) : false;
574
576
  } else if (statementKey === 'return') {
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.17",
5
5
  "description": "BareScript; a lightweight scripting and expression language",
6
6
  "keywords": [
7
7
  "expression",