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.
- package/lib/include/args.bare +5 -3
- package/lib/include/elementModel.bare +178 -0
- package/lib/include/qrcode.bare +883 -0
- package/lib/include/schemaDoc.bare +0 -1
- package/lib/library.js +24 -2
- package/lib/model.js +2 -0
- package/package.json +1 -1
package/lib/include/args.bare
CHANGED
|
@@ -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, '&', '&')
|
|
137
|
+
text = stringReplace(text, '<', '<')
|
|
138
|
+
text = stringReplace(text, '>', '>')
|
|
139
|
+
text = stringReplace(text, '"', '"')
|
|
140
|
+
text = stringReplace(text, "'", ''')
|
|
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), '&', '&'), '"', '"') + '"'
|
|
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()
|
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
|
|
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
|
|
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') {
|