bare-script 3.8.16 → 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']
@@ -32,12 +32,9 @@ function qrcodeDraw(message, x, y, size, level):
32
32
  while ixCol < qrcodeSize:
33
33
  pixelValue = arrayGet(arrayGet(matrix, ixRow), ixCol)
34
34
  if pixelValue:
35
- drawPathRect( \
36
- mathRound(borderSize + x + ixCol * pixelSize, precision), \
37
- mathRound(borderSize + y + ixRow * pixelSize, precision), \
38
- pixelSizeRound, \
39
- pixelSizeRound \
40
- )
35
+ pixelLeft = mathRound(borderSize + x + ixCol * pixelSize, precision)
36
+ pixelTop = mathRound(borderSize + y + ixRow * pixelSize, precision)
37
+ drawPathRect(pixelLeft, pixelTop, pixelSizeRound, pixelSizeRound)
41
38
  endif
42
39
  ixCol = ixCol + 1
43
40
  endwhile
@@ -46,6 +43,69 @@ function qrcodeDraw(message, x, y, size, level):
46
43
  endfunction
47
44
 
48
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
+
49
109
  # $function: qrcodeMatrix
50
110
  # $group: qrcode.bare
51
111
  # $doc: Generate a QR code pixel matrix
@@ -55,90 +115,92 @@ endfunction
55
115
  function qrcodeMatrix(message, level):
56
116
  level = if(level != null, level, 'low')
57
117
 
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
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
88
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')
89
134
 
90
- # Timing strip
91
- qrcodeMatrixTimingStrips(matrix)
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
92
159
 
93
- # Format strips - use mask 0 as placeholder bits for qrcodeDataPixels
94
- qrcodeMatrixFormatStrips(matrix, level, 0)
160
+ # Timing strips
161
+ qrcodeMatrixTimingStrips(matrix)
95
162
 
96
- # Version blocks
97
- qrcodeMatrixVersionBlocks(matrix, qrcode)
163
+ # Format strips - use mask 0 as placeholder bits for qrcodeDataPixels
164
+ qrcodeMatrixFormatStrips(matrix, level, 0)
98
165
 
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)
166
+ # Version blocks
167
+ qrcodeMatrixVersionBlocks(matrix, qrcode)
104
168
 
105
- # Compute the data bits
106
- dataBits = qrcodeDataBits(qrcode, level, message)
169
+ # Compute the data pixels
170
+ dataPixels = qrcodeDataPixels(matrix)
107
171
 
108
- # Add the data remainder bits
109
- remainderLength = arrayLength(dataPixels) - totalCodewords * 8
110
- if remainderLength > 0:
111
- arrayExtend(dataBits, arrayNewSize(remainderLength, 0))
112
- endif
172
+ # Compute the data bits
173
+ dataBits = qrcodeDataBits(qrcode, level, message)
113
174
 
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
175
+ # Add the data remainder bits
176
+ remainderLength = arrayLength(dataPixels) - totalCodewords * 8
177
+ if remainderLength > 0:
178
+ arrayExtend(dataBits, arrayNewSize(remainderLength, 0))
179
+ endif
134
180
 
135
- return bestMatrix
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
136
198
  endif
137
- endfor
138
199
 
139
- # Message too long
140
- systemLogDebug('qrcode.bare: qrcodeMatrix - Message too long (' + stringLength(message) + ')')
141
- return null
200
+ mask = mask + 1
201
+ endwhile
202
+
203
+ return bestMatrix
142
204
  endfunction
143
205
 
144
206
 
@@ -190,12 +252,13 @@ endfunction
190
252
 
191
253
  # Set the format strips pixels
192
254
  function qrcodeMatrixFormatStrips(matrix, level, mask):
193
- # The "dark pixel"
194
255
  size = arrayLength(matrix)
256
+
257
+ # The "dark pixel"
195
258
  arraySet(arrayGet(matrix, size - 8), 8, 1)
196
259
 
197
260
  # Set the format strip bits
198
- formatStripBits = objectGet(objectGet(qrcodeMatrixFormatStripsBitsLookup, level), stringNew(mask))
261
+ formatStripBits = objectGet(objectGet(qrcodeMatrixFormatStripsBitsTable, level), stringNew(mask))
199
262
  pixelsTopLeft = [ \
200
263
  [0, 8], [1, 8], [2, 8], [3, 8], [4, 8], [5, 8], [7, 8], [8, 8], \
201
264
  [8, 7], [8, 5], [8, 4], [8, 3], [8, 2], [8, 1], [8, 0] \
@@ -210,7 +273,7 @@ endfunction
210
273
 
211
274
 
212
275
  # Format strip bits lookup table
213
- qrcodeMatrixFormatStripsBitsLookup = { \
276
+ qrcodeMatrixFormatStripsBitsTable = { \
214
277
  'low': { \
215
278
  '0': [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0], \
216
279
  '1': [1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1], \
@@ -257,7 +320,7 @@ qrcodeMatrixFormatStripsBitsLookup = { \
257
320
  # Set the version blocks pixels
258
321
  function qrcodeMatrixVersionBlocks(matrix, qrcode):
259
322
  version = objectGet(qrcode, 'version')
260
- versionBits = objectGet(qrcodeMatrixVersionBitsLookup, stringNew(version))
323
+ versionBits = objectGet(qrcodeMatrixVersionBitsTable, stringNew(version))
261
324
  if versionBits:
262
325
  size = arrayLength(matrix)
263
326
  bottomLeftPixels = [ \
@@ -277,7 +340,7 @@ endfunction
277
340
 
278
341
 
279
342
  # Version bits lookup table
280
- qrcodeMatrixVersionBitsLookup = { \
343
+ qrcodeMatrixVersionBitsTable = { \
281
344
  '7': [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0], \
282
345
  '10': [0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1], \
283
346
  '15': [0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0] \
@@ -438,8 +501,7 @@ endfunction
438
501
  # Get the terminated message bits (mode, size, message, terminator)
439
502
  function qrcodeMessageBits(qrcode, level, message):
440
503
  version = objectGet(qrcode, 'version')
441
- ecLevel = objectGet(qrcode, level)
442
- totalMessageCodewords = objectGet(ecLevel, 'totalMessageCodewords')
504
+ totalMessageCodewords = objectGet(objectGet(qrcode, level), 'totalMessageCodewords')
443
505
 
444
506
  # Determine the message mode
445
507
  # Note: Kanji and ECI modes not supported
@@ -573,60 +635,54 @@ function qrcodeMatrixPenalty(matrix):
573
635
  size = arrayLength(matrix)
574
636
  penalty = 0
575
637
 
576
- # Add the string rows
577
- stringRowsAndColumns = []
638
+ # Compute row penalties
578
639
  for row in matrix:
579
- arrayPush(stringRowsAndColumns, arrayJoin(row, ''))
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))
580
649
  endfor
581
650
 
582
- # Add the string columns - also compute the 2x2 count and "dark" count
583
- twoByCount = 0
584
- twoBySize = size - 1
651
+ # Compute column penalties - also count dark modules
585
652
  darkCount = 0
653
+ twoBySize = size - 1
586
654
  ix = 0
587
655
  while ix < size:
588
656
  column = []
589
657
  iy = 0
590
658
  while iy < size:
591
659
  row = arrayGet(matrix, iy)
592
- row2 = if(ix < twoBySize && iy < twoBySize, arrayGet(matrix, iy + 1))
593
660
  bit = arrayGet(row, ix)
594
-
595
- # Add the column bit
596
661
  arrayPush(column, bit)
597
662
 
598
- # Count the 2x2 blocks
663
+ # Penalty 2: 2x2 blocks of same color
664
+ row2 = if(ix < twoBySize && iy < twoBySize, arrayGet(matrix, iy + 1))
599
665
  if row2 && bit == arrayGet(row, ix + 1) && bit == arrayGet(row2, ix) && bit == arrayGet(row2, ix + 1):
600
- twoByCount = twoByCount + 1
666
+ penalty = penalty + 3
601
667
  endif
602
668
 
603
669
  # Count "dark" pixels
604
- if bit:
605
- darkCount = darkCount + 1
606
- endif
670
+ darkCount = darkCount + bit
607
671
 
608
672
  iy = iy + 1
609
673
  endwhile
674
+ stringColumn = arrayJoin(column, '')
610
675
 
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):
676
+ # Penalty 1: Five or more same-colored modules in a column
677
+ for runMatch in regexMatchAll(qrcodeMatrixPenaltyRunRegex, stringColumn):
619
678
  penalty = penalty + 3 + (stringLength(objectGet(objectGet(runMatch, 'groups'), '0')) - 5)
620
679
  endfor
621
- endfor
622
680
 
623
- # Penalty 2: 2x2 blocks of same color
624
- penalty = penalty + 3 * twoByCount
681
+ # Penalty 3: Finder-like patterns (1011101 with 4 white modules on either side)
682
+ penalty = penalty + 40 * arrayLength(regexMatchAll(qrcodeMatrixPenaltyPatternRegex, stringColumn))
625
683
 
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
684
+ ix = ix + 1
685
+ endwhile
630
686
 
631
687
  # Penalty 4: Proportion of dark modules
632
688
  darkPercent = (darkCount * 100) / (size * size)
@@ -675,9 +731,11 @@ function qrcodeRSEncode(bytesIn, numCodewords):
675
731
 
676
732
  # XOR with existing value (subtraction in GF(2^8))
677
733
  arraySet(bytesOut, i + j, oldVal ^ product)
734
+
678
735
  j = j + 1
679
736
  endwhile
680
737
  endif
738
+
681
739
  i = i + 1
682
740
  endwhile
683
741
 
@@ -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.16",
4
+ "version": "3.8.17",
5
5
  "description": "BareScript; a lightweight scripting and expression language",
6
6
  "keywords": [
7
7
  "expression",