@vkontakte/vk-qr 3.0.0 → 3.1.0
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/README.md +21 -2
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/figures.d.ts +9 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +848 -672
- package/dist/index.js.map +1 -1
- package/dist/qr.d.ts +62 -70
- package/dist/qr.d.ts.map +1 -1
- package/dist/svg-path.d.ts +8 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg.d.ts +5 -9
- package/dist/svg.d.ts.map +1 -1
- package/dist/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/constants.ts +9 -0
- package/src/figures.ts +106 -0
- package/src/index.ts +16 -10
- package/src/qr.ts +707 -871
- package/src/svg-path.ts +254 -0
- package/src/svg.ts +129 -270
- package/src/types.ts +15 -1
package/dist/index.js
CHANGED
|
@@ -1,194 +1,398 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
function isFinderPattern(figure, size) {
|
|
2
|
+
const { x, y, pixels } = figure;
|
|
3
|
+
const width = pixels[0]?.length ?? 0;
|
|
4
|
+
const height = pixels.length;
|
|
5
|
+
const right = x + width - 1;
|
|
6
|
+
const bottom = y + height - 1;
|
|
7
|
+
const finderRegions = [
|
|
8
|
+
[
|
|
9
|
+
0,
|
|
10
|
+
0,
|
|
11
|
+
6,
|
|
12
|
+
6
|
|
13
|
+
],
|
|
14
|
+
[
|
|
15
|
+
size - 7,
|
|
16
|
+
0,
|
|
17
|
+
size - 1,
|
|
18
|
+
6
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
0,
|
|
22
|
+
size - 7,
|
|
23
|
+
6,
|
|
24
|
+
size - 1
|
|
25
|
+
]
|
|
26
|
+
];
|
|
27
|
+
for (const [rx1, ry1, rx2, ry2] of finderRegions)if (x >= rx1 && y >= ry1 && right <= rx2 && bottom <= ry2) return true;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function extractFigures(image) {
|
|
31
|
+
const height = image.length;
|
|
32
|
+
const width = image[0]?.length ?? 0;
|
|
33
|
+
const visited = Array.from({
|
|
34
|
+
length: height
|
|
35
|
+
}, ()=>Array(width).fill(false));
|
|
36
|
+
const figures = [];
|
|
37
|
+
const directions = [
|
|
38
|
+
[
|
|
39
|
+
0,
|
|
40
|
+
-1
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
1,
|
|
44
|
+
0
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
0,
|
|
48
|
+
1
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
-1,
|
|
52
|
+
0
|
|
53
|
+
]
|
|
54
|
+
];
|
|
55
|
+
function isInside(x, y) {
|
|
56
|
+
return y >= 0 && y < height && x >= 0 && x < width;
|
|
17
57
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
let xCoord = 0;
|
|
52
|
-
let yCoord = 0;
|
|
53
|
-
for(let y = 0; y < qrCode.size; y++){
|
|
54
|
-
leftPadding = 0;
|
|
55
|
-
for(let x = 0; x < qrCode.size; x++){
|
|
56
|
-
xCoord = x + leftPadding;
|
|
57
|
-
leftPadding += TILE_SIZE;
|
|
58
|
-
yCoord = y + topPadding;
|
|
59
|
-
const neighbors = getNeighbors(x, y, qrCode.size, qrCode.modules, options.isShowLogo);
|
|
60
|
-
let path = '';
|
|
61
|
-
let selector = '';
|
|
62
|
-
if (neighbors.current) {
|
|
63
|
-
selector = selector || neighbors.l || neighbors.r || neighbors.t || neighbors.b ? '' : 'empty';
|
|
64
|
-
selector = !selector && neighbors.l && neighbors.r || neighbors.t && neighbors.b ? 'rect' : '';
|
|
65
|
-
if (!selector) {
|
|
66
|
-
selector += neighbors.l ? 'l' : neighbors.r ? 'r' : '';
|
|
67
|
-
selector += neighbors.t ? 't' : neighbors.b ? 'b' : '';
|
|
68
|
-
if (!selector) selector = 'empty';
|
|
58
|
+
for(let y = 0; y < height; y++)for(let x = 0; x < width; x++){
|
|
59
|
+
if (visited[y][x]) continue;
|
|
60
|
+
if (!image[y][x]) continue;
|
|
61
|
+
const cells = [
|
|
62
|
+
[
|
|
63
|
+
x,
|
|
64
|
+
y
|
|
65
|
+
]
|
|
66
|
+
];
|
|
67
|
+
visited[y][x] = true;
|
|
68
|
+
let minX = x;
|
|
69
|
+
let maxX = x;
|
|
70
|
+
let minY = y;
|
|
71
|
+
let maxY = y;
|
|
72
|
+
for(let head = 0; head < cells.length; head++){
|
|
73
|
+
const [cx, cy] = cells[head];
|
|
74
|
+
minX = Math.min(minX, cx);
|
|
75
|
+
maxX = Math.max(maxX, cx);
|
|
76
|
+
minY = Math.min(minY, cy);
|
|
77
|
+
maxY = Math.max(maxY, cy);
|
|
78
|
+
for (const [dx, dy] of directions){
|
|
79
|
+
const nx = cx + dx;
|
|
80
|
+
const ny = cy + dy;
|
|
81
|
+
if (isInside(nx, ny)) {
|
|
82
|
+
if (!visited[ny][nx]) {
|
|
83
|
+
if (image[ny][nx]) {
|
|
84
|
+
visited[ny][nx] = true;
|
|
85
|
+
cells.push([
|
|
86
|
+
nx,
|
|
87
|
+
ny
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
69
91
|
}
|
|
70
|
-
} else {
|
|
71
|
-
selector = !selector && neighbors.l && neighbors.t && getPixel(x - 1, y - 1, qrCode.size, qrCode.modules, options.isShowLogo) ? 'n_lt' : '';
|
|
72
|
-
selector = !selector && neighbors.l && neighbors.b && getPixel(x - 1, y + 1, qrCode.size, qrCode.modules, options.isShowLogo) ? 'n_lb' : '';
|
|
73
|
-
selector = !selector && neighbors.r && neighbors.t && getPixel(x + 1, y - 1, qrCode.size, qrCode.modules, options.isShowLogo) ? 'n_rt' : '';
|
|
74
|
-
selector = !selector && neighbors.r && neighbors.b && getPixel(x + 1, y + 1, qrCode.size, qrCode.modules, options.isShowLogo) ? 'n_rb' : '';
|
|
75
92
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
}
|
|
94
|
+
const figureWidth = maxX - minX + 1;
|
|
95
|
+
const figureHeight = maxY - minY + 1;
|
|
96
|
+
const pixels = Array.from({
|
|
97
|
+
length: figureHeight
|
|
98
|
+
}, ()=>Array(figureWidth).fill(false));
|
|
99
|
+
for (const [cellX, cellY] of cells)pixels[cellY - minY][cellX - minX] = true;
|
|
100
|
+
figures.push({
|
|
101
|
+
x: minX,
|
|
102
|
+
y: minY,
|
|
103
|
+
pixels
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return figures;
|
|
107
|
+
}
|
|
108
|
+
function figureToSvgPath(figure, options = {}) {
|
|
109
|
+
const pixelSize = options.pixelSize ?? 100;
|
|
110
|
+
const radius = options.radius ?? 35;
|
|
111
|
+
const contours = buildContours(getBoundaryEdges(figure, pixelSize));
|
|
112
|
+
const cornerRadius = createCornerRadiusResolver(figure, pixelSize, radius);
|
|
113
|
+
return contours.map((contour)=>contourToRoundedPath(contour, cornerRadius)).join(' ');
|
|
114
|
+
}
|
|
115
|
+
function hasPixel(pixels, x, y) {
|
|
116
|
+
return pixels[y]?.[x] ?? false;
|
|
117
|
+
}
|
|
118
|
+
function getBoundaryEdges(figure, pixelSize) {
|
|
119
|
+
const pixels = figure.pixels;
|
|
120
|
+
const height = pixels.length;
|
|
121
|
+
const width = pixels[0]?.length ?? 0;
|
|
122
|
+
const edges = [];
|
|
123
|
+
function px(x) {
|
|
124
|
+
return (figure.x + x) * pixelSize;
|
|
125
|
+
}
|
|
126
|
+
function py(y) {
|
|
127
|
+
return (figure.y + y) * pixelSize;
|
|
128
|
+
}
|
|
129
|
+
for(let y = 0; y < height; y++)for(let x = 0; x < width; x++){
|
|
130
|
+
if (!pixels[y][x]) continue;
|
|
131
|
+
const left = px(x);
|
|
132
|
+
const right = px(x + 1);
|
|
133
|
+
const top = py(y);
|
|
134
|
+
const bottom = py(y + 1);
|
|
135
|
+
if (!hasPixel(pixels, x, y - 1)) edges.push({
|
|
136
|
+
from: {
|
|
137
|
+
x: left,
|
|
138
|
+
y: top
|
|
139
|
+
},
|
|
140
|
+
to: {
|
|
141
|
+
x: right,
|
|
142
|
+
y: top
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
if (!hasPixel(pixels, x + 1, y)) edges.push({
|
|
146
|
+
from: {
|
|
147
|
+
x: right,
|
|
148
|
+
y: top
|
|
149
|
+
},
|
|
150
|
+
to: {
|
|
151
|
+
x: right,
|
|
152
|
+
y: bottom
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (!hasPixel(pixels, x, y + 1)) edges.push({
|
|
156
|
+
from: {
|
|
157
|
+
x: right,
|
|
158
|
+
y: bottom
|
|
159
|
+
},
|
|
160
|
+
to: {
|
|
161
|
+
x: left,
|
|
162
|
+
y: bottom
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
if (!hasPixel(pixels, x - 1, y)) edges.push({
|
|
166
|
+
from: {
|
|
167
|
+
x: left,
|
|
168
|
+
y: bottom
|
|
169
|
+
},
|
|
170
|
+
to: {
|
|
171
|
+
x: left,
|
|
172
|
+
y: top
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return edges;
|
|
177
|
+
}
|
|
178
|
+
function hasFilledCorner(pixels, x, y, dx, dy) {
|
|
179
|
+
return hasPixel(pixels, x + dx, y) && hasPixel(pixels, x, y + dy) && hasPixel(pixels, x + dx, y + dy);
|
|
180
|
+
}
|
|
181
|
+
function createCornerRadiusResolver(figure, pixelSize, radius) {
|
|
182
|
+
return (prev, current, next)=>{
|
|
183
|
+
if (getTurn(prev, current, next) < 0) {
|
|
184
|
+
const x = Math.round(Math.min(prev.x, current.x, next.x) / pixelSize) - figure.x;
|
|
185
|
+
const y = Math.round(Math.min(prev.y, current.y, next.y) / pixelSize) - figure.y;
|
|
186
|
+
const topLeft = hasFilledCorner(figure.pixels, x, y, -1, -1);
|
|
187
|
+
const bottomLeft = hasFilledCorner(figure.pixels, x, y, -1, 1);
|
|
188
|
+
const topRight = hasFilledCorner(figure.pixels, x, y, 1, -1);
|
|
189
|
+
const bottomRight = hasFilledCorner(figure.pixels, x, y, 1, 1);
|
|
190
|
+
const roundsBottomRight = bottomRight && (!topRight || bottomLeft && !topLeft);
|
|
191
|
+
return roundsBottomRight && current.x === (figure.x + (x + 1)) * pixelSize && current.y === (figure.y + (y + 1)) * pixelSize ? radius : 0;
|
|
192
|
+
}
|
|
193
|
+
return radius;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function buildContours(edges) {
|
|
197
|
+
const edgeMap = new Map();
|
|
198
|
+
for (const edge of edges){
|
|
199
|
+
const key = pointKey(edge.from);
|
|
200
|
+
const list = edgeMap.get(key);
|
|
201
|
+
if (list) list.push(edge.to);
|
|
202
|
+
else edgeMap.set(key, [
|
|
203
|
+
edge.to
|
|
204
|
+
]);
|
|
205
|
+
}
|
|
206
|
+
const contours = [];
|
|
207
|
+
while(true){
|
|
208
|
+
const startKey = edgeMap.keys().next().value;
|
|
209
|
+
if (void 0 === startKey) break;
|
|
210
|
+
const [x, y] = startKey.split(',').map(Number);
|
|
211
|
+
const start = {
|
|
212
|
+
x,
|
|
213
|
+
y
|
|
214
|
+
};
|
|
215
|
+
const contour = [
|
|
216
|
+
start
|
|
217
|
+
];
|
|
218
|
+
let current = start;
|
|
219
|
+
while(true){
|
|
220
|
+
const candidates = edgeMap.get(pointKey(current));
|
|
221
|
+
if (!candidates || 0 === candidates.length) throw new Error('Broken contour: edge chain is not closed');
|
|
222
|
+
let nextIndex = 0;
|
|
223
|
+
if (candidates.length > 1 && contour.length > 1) {
|
|
224
|
+
const prev = contour[contour.length - 2];
|
|
225
|
+
const rightTurn = candidates.findIndex((candidate)=>getTurn(prev, current, candidate) > 0);
|
|
226
|
+
if (-1 !== rightTurn) nextIndex = rightTurn;
|
|
79
227
|
}
|
|
228
|
+
const [next] = candidates.splice(nextIndex, 1);
|
|
229
|
+
if (0 === candidates.length) edgeMap.delete(pointKey(current));
|
|
230
|
+
if (samePoint(next, start)) break;
|
|
231
|
+
contour.push(next);
|
|
232
|
+
current = next;
|
|
80
233
|
}
|
|
81
|
-
|
|
234
|
+
contours.push(contour);
|
|
235
|
+
}
|
|
236
|
+
return contours;
|
|
237
|
+
}
|
|
238
|
+
function contourToRoundedPath(contour, cornerRadius) {
|
|
239
|
+
const n = contour.length;
|
|
240
|
+
if (0 === n) return '';
|
|
241
|
+
const commands = [];
|
|
242
|
+
for(let i = 0; i < n; i++){
|
|
243
|
+
const prev = contour[(i - 1 + n) % n];
|
|
244
|
+
const current = contour[i];
|
|
245
|
+
const next = contour[(i + 1) % n];
|
|
246
|
+
const incomingLength = distance(current, prev);
|
|
247
|
+
const outgoingLength = distance(current, next);
|
|
248
|
+
const r = Math.min(cornerRadius(prev, current, next), incomingLength / 2, outgoingLength / 2);
|
|
249
|
+
const beforeCorner = moveTowards(current, prev, r);
|
|
250
|
+
const afterCorner = moveTowards(current, next, r);
|
|
251
|
+
commands.push(`${0 === i ? 'M' : 'L'} ${beforeCorner.x} ${beforeCorner.y}`);
|
|
252
|
+
if (r > 0) commands.push(`Q ${current.x} ${current.y} ${afterCorner.x} ${afterCorner.y}`);
|
|
82
253
|
}
|
|
254
|
+
commands.push('Z');
|
|
255
|
+
return commands.join(' ');
|
|
256
|
+
}
|
|
257
|
+
function moveTowards(from, to, distanceValue) {
|
|
258
|
+
const length = distance(from, to);
|
|
259
|
+
if (0 === length) return {
|
|
260
|
+
...from
|
|
261
|
+
};
|
|
262
|
+
const ratio = distanceValue / length;
|
|
263
|
+
return {
|
|
264
|
+
x: from.x + (to.x - from.x) * ratio,
|
|
265
|
+
y: from.y + (to.y - from.y) * ratio
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function distance(a, b) {
|
|
269
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
270
|
+
}
|
|
271
|
+
function pointKey(point) {
|
|
272
|
+
return `${point.x},${point.y}`;
|
|
273
|
+
}
|
|
274
|
+
function samePoint(a, b) {
|
|
275
|
+
return a.x === b.x && a.y === b.y;
|
|
276
|
+
}
|
|
277
|
+
function getTurn(prev, current, next) {
|
|
278
|
+
return (current.x - prev.x) * (next.y - current.y) - (current.y - prev.y) * (next.x - current.x);
|
|
279
|
+
}
|
|
280
|
+
const INC_TILE_SIZE = 100;
|
|
281
|
+
function convertSegmentsToSvgString(qrCode, options) {
|
|
282
|
+
if ('number' != typeof options.qrSize) throw new Error('Size should be a number');
|
|
283
|
+
if ('string' != typeof options.className) throw new Error('Classname should be a string');
|
|
284
|
+
const background = getBackgroundLayout(qrCode.size, options);
|
|
285
|
+
const parts = renderFinderPatterns(qrCode.size, options);
|
|
286
|
+
if (options.isShowLogo) parts.push(renderLogo(qrCode.size, options));
|
|
287
|
+
return `
|
|
288
|
+
<svg
|
|
289
|
+
version="1.1"
|
|
290
|
+
viewBox="${-background.padding} ${-background.padding} ${background.size} ${background.size}"
|
|
291
|
+
width="${options.qrSize}px"
|
|
292
|
+
height="${options.qrSize}px"
|
|
293
|
+
style="display: block"
|
|
294
|
+
${options.className ? `class="${options.className}"` : ''}
|
|
295
|
+
xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
296
|
+
>
|
|
297
|
+
${renderDefinitions(options)}
|
|
298
|
+
|
|
299
|
+
${renderBackground(background, options)}
|
|
300
|
+
|
|
301
|
+
<g${background.hideQr ? ' display="none"' : ''}>
|
|
302
|
+
${renderModules(qrCode, options)}
|
|
303
|
+
${parts.join('\n')}
|
|
304
|
+
</g>
|
|
305
|
+
|
|
306
|
+
</svg>`;
|
|
307
|
+
}
|
|
308
|
+
function getVisibleModules(qrCode, isShowLogo) {
|
|
309
|
+
if (!isShowLogo) return qrCode.modules;
|
|
310
|
+
const logoAreaSize = qrCode.size <= 25 ? 7 : 9;
|
|
311
|
+
const logoAreaStart = (qrCode.size - logoAreaSize) / 2;
|
|
312
|
+
return qrCode.modules.map((row, y)=>row.map((pixel, x)=>pixel && !(x >= logoAreaStart && x < logoAreaStart + logoAreaSize && y >= logoAreaStart && y < logoAreaStart + logoAreaSize)));
|
|
313
|
+
}
|
|
314
|
+
function renderModules(qrCode, options) {
|
|
315
|
+
return extractFigures(getVisibleModules(qrCode, options.isShowLogo)).filter((figure)=>!isFinderPattern(figure, qrCode.size)).map((figure)=>`
|
|
316
|
+
<path
|
|
317
|
+
d="${figureToSvgPath(figure)}"
|
|
318
|
+
fill="${options.foregroundColor}"
|
|
319
|
+
fill-rule="evenodd"
|
|
320
|
+
/>`).join('');
|
|
321
|
+
}
|
|
322
|
+
function renderFinderPatterns(size, options) {
|
|
323
|
+
const pointPosition = (size - 7) * 100;
|
|
324
|
+
return [
|
|
325
|
+
`<use fill-rule="evenodd" transform="translate(0,0)" xlink:href="#point-${options.suffix}"/>`,
|
|
326
|
+
`<use fill-rule="evenodd" transform="translate(${pointPosition},0)" xlink:href="#point-${options.suffix}"/>`,
|
|
327
|
+
`<use fill-rule="evenodd" transform="translate(0,${pointPosition})" xlink:href="#point-${options.suffix}"/>`
|
|
328
|
+
];
|
|
329
|
+
}
|
|
330
|
+
function renderLogo(size, options) {
|
|
83
331
|
let scale = '';
|
|
84
|
-
let position = (
|
|
85
|
-
if (
|
|
332
|
+
let position = (size - 21) / 2 * 100 + 700 - 10;
|
|
333
|
+
if (size <= 25) {
|
|
86
334
|
scale = 'scale(0.85)';
|
|
87
335
|
position += 50;
|
|
88
336
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
337
|
+
if (!options.logoData) return `
|
|
338
|
+
<use style="width: 750px; height: 750px;" width="750" height="750"
|
|
339
|
+
fill="none"
|
|
340
|
+
fill-rule="evenodd"
|
|
341
|
+
transform="translate(${position},${position}) ${scale}" xlink:href="#vk_logo-${options.suffix}"
|
|
342
|
+
/>
|
|
343
|
+
`;
|
|
344
|
+
return `
|
|
94
345
|
<image
|
|
95
346
|
preserveAspectRatio="xMidYMid slice" clip-path="url(#logo-mask-${options.suffix})"
|
|
96
347
|
style="width: 750px; height: 750px;" width="750" height="750"
|
|
97
348
|
transform="translate(${position},${position}) ${scale}"
|
|
98
349
|
xlink:href="${options.logoData}"
|
|
99
350
|
/>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
function getBackgroundLayout(size, options) {
|
|
354
|
+
const contentSize = 100 * size;
|
|
355
|
+
if (!options.isShowBackground) return {
|
|
356
|
+
size: contentSize,
|
|
357
|
+
padding: 0,
|
|
358
|
+
radius: 0,
|
|
359
|
+
hideQr: false
|
|
360
|
+
};
|
|
361
|
+
const frameWidth = Math.min(options.backgroundPadding, options.qrSize / 2);
|
|
362
|
+
const contentWidth = options.qrSize - 2 * frameWidth;
|
|
363
|
+
const hideQr = contentWidth <= 0;
|
|
364
|
+
const backgroundSize = hideQr ? contentSize : contentSize * options.qrSize / contentWidth;
|
|
365
|
+
const padding = (backgroundSize - contentSize) / 2;
|
|
366
|
+
const radius = Math.min(options.backgroundRadius, options.qrSize / 2) / options.qrSize * backgroundSize;
|
|
367
|
+
return {
|
|
368
|
+
size: backgroundSize,
|
|
369
|
+
padding,
|
|
370
|
+
radius,
|
|
371
|
+
hideQr
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function renderBackground(layout, options) {
|
|
375
|
+
if (!options.isShowBackground) return '';
|
|
376
|
+
const { size, padding, radius } = layout;
|
|
377
|
+
return `
|
|
116
378
|
<rect
|
|
117
|
-
x="
|
|
118
|
-
|
|
119
|
-
|
|
379
|
+
x="${-padding}"
|
|
380
|
+
y="${-padding}"
|
|
381
|
+
width="${size}"
|
|
382
|
+
height="${size}"
|
|
120
383
|
rx="${radius}"
|
|
121
384
|
fill="${options.backgroundColor}"
|
|
122
385
|
/>`;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const twoSides = `M0,0 L${_100},0 L${_100},66 C${_100},${_84_7} ${_84_7},${_100} 66,${_100} L0,${_100} L0,0 Z`;
|
|
128
|
-
return `
|
|
129
|
-
<svg
|
|
130
|
-
version="1.1"
|
|
131
|
-
viewBox="0 0 ${backgroundSize} ${backgroundSize}"
|
|
132
|
-
width="${options.qrSize}px"
|
|
133
|
-
height="${options.qrSize}px"
|
|
134
|
-
${options.className ? `class="${options.className}"` : ''}
|
|
135
|
-
xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
136
|
-
>
|
|
137
|
-
<defs>
|
|
138
|
-
<rect id="rect-${options.suffix}" width="100" height="100" fill="${options.foregroundColor}"/>
|
|
139
|
-
<path
|
|
140
|
-
id="empty-${options.suffix}"
|
|
141
|
-
d="M0,${_28_6}v${_42_9}C0,${_87_3},${_12_8},${_100},${_28_6},${_100}h${_42_9}c${_15_9},0,${_28_6}-${_12_8},\
|
|
142
|
-
${_28_6}-${_28_6}V${_28_6}C${_100},${_12_7},${_87_2},0,${_71_4},0H${_28_6} C${_12_8},0,0,${_12_8},0,${_28_6}z"
|
|
143
|
-
fill="${options.foregroundColor}"
|
|
144
|
-
/>
|
|
145
|
-
<path id="b-${options.suffix}" d="${oneSide}" transform="rotate(-90 50 50)" fill="${options.foregroundColor}"/>
|
|
146
|
-
<path id="r-${options.suffix}" d="${oneSide}" transform="rotate(-180 50 50)" fill="${options.foregroundColor}"/>
|
|
147
|
-
<path id="l-${options.suffix}" d="${oneSide}" fill="${options.foregroundColor}"/>
|
|
148
|
-
<path id="t-${options.suffix}" d="${oneSide}" transform="rotate(90 50 50)" fill="${options.foregroundColor}"/>
|
|
149
|
-
<path id="l-${options.suffix}" d="${twoSides}" transform="rotate(-90 50 50)" fill="${options.foregroundColor}"/>
|
|
150
|
-
<path id="lt-${options.suffix}" d="${twoSides}" fill="${options.foregroundColor}"/>
|
|
151
|
-
<path id="lb-${options.suffix}" d="${twoSides}" transform="rotate(-90 50 50)" fill="${options.foregroundColor}"/>
|
|
152
|
-
<path id="rb-${options.suffix}" d="${twoSides}" transform="rotate(-180 50 50)" fill="${options.foregroundColor}"/>
|
|
153
|
-
<path id="rt-${options.suffix}" d="${twoSides}" transform="rotate(90 50 50)" fill="${options.foregroundColor}"/>
|
|
154
|
-
<path
|
|
155
|
-
id="n_lt-${options.suffix}"
|
|
156
|
-
d="M${_30_5},${_2}V0H0v${_30_5}h${_2}C${_2},${_14_7},${_14_8},${_2},${_30_5},${_2}z"
|
|
157
|
-
fill="${options.foregroundColor}"
|
|
158
|
-
/>
|
|
159
|
-
<path
|
|
160
|
-
id="n_lb-${options.suffix}"
|
|
161
|
-
d="M${_2},${_69_5}H0V${_100}h${_30_5}v-${_2}C${_14_7},${_98},${_2},${_85_2},${_2},${_69_5}z"
|
|
162
|
-
fill="${options.foregroundColor}"
|
|
163
|
-
/>
|
|
164
|
-
<path
|
|
165
|
-
id="n_rt-${options.suffix}"
|
|
166
|
-
d="M${_98},${_30_5}h${_2}V0H${_69_5}v${_2}C${_85_3},${_2},${_98},${_14_8},${_98},${_30_5}z"
|
|
167
|
-
fill="${options.foregroundColor}"
|
|
168
|
-
/>
|
|
169
|
-
<path id="n_rb-${options.suffix}"
|
|
170
|
-
d="M${_69_5},${_98}v${_2}H${_100}V${_69_5}h-${_2}C${_98},${_85_3},${_85_2},${_98},${_69_5},${_98}z"
|
|
171
|
-
fill="${options.foregroundColor}"
|
|
172
|
-
/>
|
|
173
|
-
<path
|
|
386
|
+
}
|
|
387
|
+
function renderDefinitions(options) {
|
|
388
|
+
return `<defs>
|
|
389
|
+
<g
|
|
174
390
|
id="point-${options.suffix}"
|
|
175
391
|
fill="${options.foregroundColor}"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
C99.9982143,552.627632 112.972983,575.877216 118.547883,581.464617 C124.122784,587.027017 \
|
|
181
|
-
147.372368,600.001786 242.670667,600.001786 L457.329333,600.001786 C552.627632,600.001786 \
|
|
182
|
-
575.877216,587.027017 581.464617,581.464617 C587.039517,575.877216 600.001786,552.627632 \
|
|
183
|
-
600.001786,457.329333 Z M457.329333,0 C653.338333,0 700,46.6616668 700,242.658167 C700,438.667167 \
|
|
184
|
-
700,261.332833 700,457.329333 C700,653.338333 653.338333,700 457.329333,700 C261.332833,700 438.667167,700 \
|
|
185
|
-
242.670667,700 C46.6616668,700 0,653.338333 0,457.329333 C0,261.332833 0,352.118712 0,242.658167 \
|
|
186
|
-
C0,46.6616668 46.6616668,0 242.670667,0 C438.667167,0 261.332833,0 457.329333,0 Z M395.996667,200 \
|
|
187
|
-
C480.004166,200 500,220.008332 500,303.990835 C500,387.998334 500,312.001666 500,395.996667 \
|
|
188
|
-
C500,479.991668 480.004166,500 395.996667,500 C312.001666,500 387.998334,500 304.003333,500 C220.008332,500 \
|
|
189
|
-
200,479.991668 200,395.996667 C200,312.001666 200,350.906061 200,303.990835 C200,220.008332 220.008332,200 \
|
|
190
|
-
304.003333,200 C387.998334,200 312.001666,200 395.996667,200 Z"
|
|
191
|
-
/>
|
|
392
|
+
>
|
|
393
|
+
<rect width="300" height="300" x="200" y="200" rx="67" />
|
|
394
|
+
<rect width="600" height="600" x="50" y="50" rx="134" stroke-width="100" fill="none" stroke="${options.foregroundColor}" />
|
|
395
|
+
</g>
|
|
192
396
|
<g id="vk_logo-${options.suffix}">
|
|
193
397
|
<path
|
|
194
398
|
fill="${options.logoColor}"
|
|
@@ -209,358 +413,511 @@ C500,479.991668 480.004166,500 395.996667,500 C312.001666,500 387.998334,500 304
|
|
|
209
413
|
<clipPath id="logo-mask-${options.suffix}">
|
|
210
414
|
<rect x="0" y="0" width="750" height="750" />
|
|
211
415
|
</clipPath>
|
|
212
|
-
</defs
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
416
|
+
</defs>`;
|
|
417
|
+
}
|
|
418
|
+
const QrCodeEcc = {
|
|
419
|
+
LOW: {
|
|
420
|
+
ordinal: 0,
|
|
421
|
+
formatBits: 1
|
|
422
|
+
},
|
|
423
|
+
MEDIUM: {
|
|
424
|
+
ordinal: 1,
|
|
425
|
+
formatBits: 0
|
|
426
|
+
},
|
|
427
|
+
QUARTILE: {
|
|
428
|
+
ordinal: 2,
|
|
429
|
+
formatBits: 3
|
|
430
|
+
},
|
|
431
|
+
HIGH: {
|
|
432
|
+
ordinal: 3,
|
|
433
|
+
formatBits: 2
|
|
225
434
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
435
|
+
};
|
|
436
|
+
const QrSegmentMode = {
|
|
437
|
+
NUMERIC: {
|
|
438
|
+
modeBits: 0x1,
|
|
439
|
+
numBitsCharCount: [
|
|
440
|
+
10,
|
|
441
|
+
12,
|
|
442
|
+
14
|
|
443
|
+
]
|
|
444
|
+
},
|
|
445
|
+
ALPHANUMERIC: {
|
|
446
|
+
modeBits: 0x2,
|
|
447
|
+
numBitsCharCount: [
|
|
448
|
+
9,
|
|
449
|
+
11,
|
|
450
|
+
13
|
|
451
|
+
]
|
|
452
|
+
},
|
|
453
|
+
BYTE: {
|
|
454
|
+
modeBits: 0x4,
|
|
455
|
+
numBitsCharCount: [
|
|
456
|
+
8,
|
|
457
|
+
16,
|
|
458
|
+
16
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
KANJI: {
|
|
462
|
+
modeBits: 0x8,
|
|
463
|
+
numBitsCharCount: [
|
|
464
|
+
8,
|
|
465
|
+
10,
|
|
466
|
+
12
|
|
467
|
+
]
|
|
468
|
+
},
|
|
469
|
+
ECI: {
|
|
470
|
+
modeBits: 0x7,
|
|
471
|
+
numBitsCharCount: [
|
|
472
|
+
0,
|
|
473
|
+
0,
|
|
474
|
+
0
|
|
475
|
+
]
|
|
231
476
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
QrCodeEcc.MEDIUM,
|
|
247
|
-
QrCodeEcc.QUARTILE,
|
|
248
|
-
QrCodeEcc.HIGH
|
|
249
|
-
])if (boostEcl && dataUsedBits <= 8 * QrCode.getNumDataCodewords(version, newEcl)) ecl = newEcl;
|
|
250
|
-
const bb = new BitBuffer();
|
|
251
|
-
for (const seg of segs){
|
|
252
|
-
bb.appendBits(seg.mode.modeBits, 4);
|
|
253
|
-
bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version));
|
|
254
|
-
for (const b of seg.getData())bb.array.push(b);
|
|
477
|
+
};
|
|
478
|
+
function numCharCountBits(mode, ver) {
|
|
479
|
+
return mode.numBitsCharCount[Math.floor((ver + 7) / 17)];
|
|
480
|
+
}
|
|
481
|
+
function encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) {
|
|
482
|
+
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) throw 'Invalid value';
|
|
483
|
+
let version;
|
|
484
|
+
let dataUsedBits;
|
|
485
|
+
for(version = minVersion;; version++){
|
|
486
|
+
const dataCapacityBits = 8 * getNumDataCodewords(version, ecl);
|
|
487
|
+
const usedBits = getTotalBits(segs, version);
|
|
488
|
+
if (usedBits <= dataCapacityBits) {
|
|
489
|
+
dataUsedBits = usedBits;
|
|
490
|
+
break;
|
|
255
491
|
}
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
492
|
+
if (version >= maxVersion) throw 'Data too long';
|
|
493
|
+
}
|
|
494
|
+
let finalEcl = ecl;
|
|
495
|
+
for (const newEcl of [
|
|
496
|
+
QrCodeEcc.MEDIUM,
|
|
497
|
+
QrCodeEcc.QUARTILE,
|
|
498
|
+
QrCodeEcc.HIGH
|
|
499
|
+
])if (boostEcl && dataUsedBits <= 8 * getNumDataCodewords(version, newEcl)) finalEcl = newEcl;
|
|
500
|
+
const bb = createBitBuffer();
|
|
501
|
+
for (const seg of segs){
|
|
502
|
+
bb.appendBits(seg.mode.modeBits, 4);
|
|
503
|
+
bb.appendBits(seg.numChars, numCharCountBits(seg.mode, version));
|
|
504
|
+
for (const b of seg.getData())bb.array.push(b);
|
|
269
505
|
}
|
|
270
|
-
|
|
271
|
-
|
|
506
|
+
if (bb.array.length !== dataUsedBits) throw 'Assertion error';
|
|
507
|
+
const dataCapacityBits = 8 * getNumDataCodewords(version, finalEcl);
|
|
508
|
+
if (bb.array.length > dataCapacityBits) throw 'Assertion error';
|
|
509
|
+
bb.appendBits(0, Math.min(4, dataCapacityBits - bb.array.length));
|
|
510
|
+
bb.appendBits(0, (8 - bb.array.length % 8) % 8);
|
|
511
|
+
if (bb.array.length % 8 !== 0) throw 'Assertion error';
|
|
512
|
+
for(let padByte = 0xec; bb.array.length < dataCapacityBits; padByte ^= 253)bb.appendBits(padByte, 8);
|
|
513
|
+
const dataCodewords = [];
|
|
514
|
+
while(8 * dataCodewords.length < bb.array.length)dataCodewords.push(0);
|
|
515
|
+
bb.array.forEach((b, i)=>{
|
|
516
|
+
dataCodewords[i >>> 3] |= b << 7 - (7 & i);
|
|
517
|
+
});
|
|
518
|
+
return createQrCode(version, finalEcl, dataCodewords, mask);
|
|
519
|
+
}
|
|
520
|
+
function createQrCode(version, errorCorrectionLevel, dataCodewords, mask) {
|
|
521
|
+
if (version < MIN_VERSION || version > MAX_VERSION) throw 'Version value out of range';
|
|
522
|
+
if (mask < -1 || mask > 7) throw 'Mask value out of range';
|
|
523
|
+
const size = 4 * version + 17;
|
|
524
|
+
const row = [];
|
|
525
|
+
for(let i = 0; i < size; i++)row.push(false);
|
|
526
|
+
const modules = [];
|
|
527
|
+
const isFunction = [];
|
|
528
|
+
for(let i = 0; i < size; i++){
|
|
529
|
+
modules.push(row.slice());
|
|
530
|
+
isFunction.push(row.slice());
|
|
272
531
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
<rect width="100%" height="100%" fill="#FFFFFF"/>
|
|
281
|
-
<path d="${parts.join(' ')}" fill="#000000"/>
|
|
282
|
-
</svg>
|
|
283
|
-
`;
|
|
532
|
+
const setFunctionModule = (x, y, isBlack)=>{
|
|
533
|
+
modules[y][x] = isBlack;
|
|
534
|
+
isFunction[y][x] = true;
|
|
535
|
+
};
|
|
536
|
+
for(let i = 0; i < size; i++){
|
|
537
|
+
setFunctionModule(6, i, i % 2 === 0);
|
|
538
|
+
setFunctionModule(i, 6, i % 2 === 0);
|
|
284
539
|
}
|
|
285
|
-
|
|
286
|
-
for(let
|
|
287
|
-
|
|
288
|
-
|
|
540
|
+
const drawFinderPattern = (x, y)=>{
|
|
541
|
+
for(let dy = -4; dy <= 4; dy++)for(let dx = -4; dx <= 4; dx++){
|
|
542
|
+
const dist = Math.max(Math.abs(dx), Math.abs(dy));
|
|
543
|
+
const xx = x + dx;
|
|
544
|
+
const yy = y + dy;
|
|
545
|
+
if (0 <= xx && xx < size && 0 <= yy && yy < size) setFunctionModule(xx, yy, 2 !== dist && 4 !== dist);
|
|
289
546
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
547
|
+
};
|
|
548
|
+
drawFinderPattern(3, 3);
|
|
549
|
+
drawFinderPattern(size - 4, 3);
|
|
550
|
+
drawFinderPattern(3, size - 4);
|
|
551
|
+
const alignPatPos = getAlignmentPatternPositions(version, size);
|
|
552
|
+
const numAlign = alignPatPos.length;
|
|
553
|
+
for(let i = 0; i < numAlign; i++)for(let j = 0; j < numAlign; j++)if (!(0 === i && 0 === j || 0 === i && j === numAlign - 1 || i === numAlign - 1 && 0 === j)) {
|
|
554
|
+
const drawAlignmentPattern = (x, y)=>{
|
|
555
|
+
for(let dy = -2; dy <= 2; dy++)for(let dx = -2; dx <= 2; dx++)setFunctionModule(x + dx, y + dy, 1 !== Math.max(Math.abs(dx), Math.abs(dy)));
|
|
556
|
+
};
|
|
557
|
+
drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
|
|
298
558
|
}
|
|
299
|
-
drawFormatBits(
|
|
300
|
-
const data =
|
|
559
|
+
const drawFormatBits = (ecl, msk)=>{
|
|
560
|
+
const data = ecl.formatBits << 3 | msk;
|
|
301
561
|
let rem = data;
|
|
302
562
|
for(let i = 0; i < 10; i++)rem = rem << 1 ^ (rem >>> 9) * 0x537;
|
|
303
563
|
const bits = (data << 10 | rem) ^ 0x5412;
|
|
304
564
|
if (bits >>> 15 !== 0) throw 'Assertion error';
|
|
305
|
-
for(let i = 0; i <= 5; i++)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
for(let i = 9; i < 15; i++)
|
|
310
|
-
for(let i = 0; i < 8; i++)
|
|
311
|
-
for(let i = 8; i < 15; i++)
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
let rem =
|
|
565
|
+
for(let i = 0; i <= 5; i++)setFunctionModule(8, i, getBit(bits, i));
|
|
566
|
+
setFunctionModule(8, 7, getBit(bits, 6));
|
|
567
|
+
setFunctionModule(8, 8, getBit(bits, 7));
|
|
568
|
+
setFunctionModule(7, 8, getBit(bits, 8));
|
|
569
|
+
for(let i = 9; i < 15; i++)setFunctionModule(14 - i, 8, getBit(bits, i));
|
|
570
|
+
for(let i = 0; i < 8; i++)setFunctionModule(size - 1 - i, 8, getBit(bits, i));
|
|
571
|
+
for(let i = 8; i < 15; i++)setFunctionModule(8, size - 15 + i, getBit(bits, i));
|
|
572
|
+
setFunctionModule(8, size - 8, true);
|
|
573
|
+
};
|
|
574
|
+
drawFormatBits(errorCorrectionLevel, 0);
|
|
575
|
+
if (version >= 7) {
|
|
576
|
+
let rem = version;
|
|
317
577
|
for(let i = 0; i < 12; i++)rem = rem << 1 ^ (rem >>> 11) * 0x1f25;
|
|
318
|
-
const bits =
|
|
578
|
+
const bits = version << 12 | rem;
|
|
319
579
|
if (bits >>> 18 !== 0) throw 'Assertion error';
|
|
320
580
|
for(let i = 0; i < 18; i++){
|
|
321
581
|
const color = getBit(bits, i);
|
|
322
|
-
const a =
|
|
582
|
+
const a = size - 11 + i % 3;
|
|
323
583
|
const b = Math.floor(i / 3);
|
|
324
|
-
|
|
325
|
-
|
|
584
|
+
setFunctionModule(a, b, color);
|
|
585
|
+
setFunctionModule(b, a, color);
|
|
326
586
|
}
|
|
327
587
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
588
|
+
const allCodewords = addEccAndInterleave(version, errorCorrectionLevel, dataCodewords);
|
|
589
|
+
if (allCodewords.length !== Math.floor(getNumRawDataModules(version) / 8)) throw 'Invalid argument';
|
|
590
|
+
let bitIndex = 0;
|
|
591
|
+
for(let right = size - 1; right >= 1; right -= 2){
|
|
592
|
+
if (6 === right) right = 5;
|
|
593
|
+
for(let vert = 0; vert < size; vert++)for(let j = 0; j < 2; j++){
|
|
594
|
+
const x = right - j;
|
|
595
|
+
const upward = (right + 1 & 2) === 0;
|
|
596
|
+
const y = upward ? size - 1 - vert : vert;
|
|
597
|
+
if (!isFunction[y][x] && bitIndex < 8 * allCodewords.length) {
|
|
598
|
+
modules[y][x] = getBit(allCodewords[bitIndex >>> 3], 7 - (7 & bitIndex));
|
|
599
|
+
bitIndex++;
|
|
600
|
+
}
|
|
334
601
|
}
|
|
335
602
|
}
|
|
336
|
-
|
|
337
|
-
|
|
603
|
+
if (bitIndex !== 8 * allCodewords.length) throw 'Assertion error';
|
|
604
|
+
let finalMask = mask;
|
|
605
|
+
if (-1 === finalMask) {
|
|
606
|
+
let minPenalty = 1000000000;
|
|
607
|
+
for(let i = 0; i < 8; i++){
|
|
608
|
+
applyMask(modules, isFunction, size, i);
|
|
609
|
+
drawFormatBits(errorCorrectionLevel, i);
|
|
610
|
+
const penalty = getPenaltyScore(modules, size);
|
|
611
|
+
if (penalty < minPenalty) {
|
|
612
|
+
finalMask = i;
|
|
613
|
+
minPenalty = penalty;
|
|
614
|
+
}
|
|
615
|
+
applyMask(modules, isFunction, size, i);
|
|
616
|
+
}
|
|
338
617
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
618
|
+
if (finalMask < 0 || finalMask > 7) throw 'Assertion error';
|
|
619
|
+
applyMask(modules, isFunction, size, finalMask);
|
|
620
|
+
{
|
|
621
|
+
const data = errorCorrectionLevel.formatBits << 3 | finalMask;
|
|
622
|
+
let rem = data;
|
|
623
|
+
for(let i = 0; i < 10; i++)rem = rem << 1 ^ (rem >>> 9) * 0x537;
|
|
624
|
+
const bits = (data << 10 | rem) ^ 0x5412;
|
|
625
|
+
if (bits >>> 15 !== 0) throw 'Assertion error';
|
|
626
|
+
for(let i = 0; i <= 5; i++)modules[i][8] = getBit(bits, i);
|
|
627
|
+
modules[7][8] = getBit(bits, 6);
|
|
628
|
+
modules[8][8] = getBit(bits, 7);
|
|
629
|
+
modules[8][7] = getBit(bits, 8);
|
|
630
|
+
for(let i = 9; i < 15; i++)modules[8][14 - i] = getBit(bits, i);
|
|
631
|
+
for(let i = 0; i < 8; i++)modules[8][size - 1 - i] = getBit(bits, i);
|
|
632
|
+
for(let i = 8; i < 15; i++)modules[size - 15 + i][8] = getBit(bits, i);
|
|
633
|
+
modules[size - 8][8] = true;
|
|
342
634
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
const result = [];
|
|
362
|
-
for(let i = 0; i < blocks[0].length; i++)for(let j = 0; j < blocks.length; j++)if (i !== shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(blocks[j][i]);
|
|
363
|
-
if (result.length !== rawCodewords) throw 'Assertion error';
|
|
635
|
+
return {
|
|
636
|
+
version,
|
|
637
|
+
errorCorrectionLevel,
|
|
638
|
+
size,
|
|
639
|
+
mask: finalMask,
|
|
640
|
+
modules
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function getAlignmentPatternPositions(version, size) {
|
|
644
|
+
if (1 === version) return [];
|
|
645
|
+
else {
|
|
646
|
+
const numAlign = Math.floor(version / 7) + 2;
|
|
647
|
+
const step = 32 === version ? 26 : 2 * Math.ceil((size - 13) / (2 * numAlign - 2));
|
|
648
|
+
const result = [
|
|
649
|
+
6
|
|
650
|
+
];
|
|
651
|
+
for(let pos = size - 7; result.length < numAlign; pos -= step)result.splice(1, 0, pos);
|
|
364
652
|
return result;
|
|
365
653
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
654
|
+
}
|
|
655
|
+
function applyMask(modules, isFunction, size, mask) {
|
|
656
|
+
if (mask < 0 || mask > 7) throw 'Mask value out of range';
|
|
657
|
+
for(let y = 0; y < size; y++)for(let x = 0; x < size; x++){
|
|
658
|
+
let invert;
|
|
659
|
+
switch(mask){
|
|
660
|
+
case 0:
|
|
661
|
+
invert = (x + y) % 2 === 0;
|
|
662
|
+
break;
|
|
663
|
+
case 1:
|
|
664
|
+
invert = y % 2 === 0;
|
|
665
|
+
break;
|
|
666
|
+
case 2:
|
|
667
|
+
invert = x % 3 === 0;
|
|
668
|
+
break;
|
|
669
|
+
case 3:
|
|
670
|
+
invert = (x + y) % 3 === 0;
|
|
671
|
+
break;
|
|
672
|
+
case 4:
|
|
673
|
+
invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0;
|
|
674
|
+
break;
|
|
675
|
+
case 5:
|
|
676
|
+
invert = x * y % 2 + x * y % 3 === 0;
|
|
677
|
+
break;
|
|
678
|
+
case 6:
|
|
679
|
+
invert = (x * y % 2 + x * y % 3) % 2 === 0;
|
|
680
|
+
break;
|
|
681
|
+
case 7:
|
|
682
|
+
invert = ((x + y) % 2 + x * y % 3) % 2 === 0;
|
|
683
|
+
break;
|
|
684
|
+
default:
|
|
685
|
+
throw 'Assertion error';
|
|
380
686
|
}
|
|
381
|
-
if (
|
|
687
|
+
if (!isFunction[y][x] && invert) modules[y][x] = !modules[y][x];
|
|
382
688
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
invert = (x * y % 2 + x * y % 3) % 2 === 0;
|
|
408
|
-
break;
|
|
409
|
-
case 7:
|
|
410
|
-
invert = ((x + y) % 2 + x * y % 3) % 2 === 0;
|
|
411
|
-
break;
|
|
412
|
-
default:
|
|
413
|
-
throw 'Assertion error';
|
|
414
|
-
}
|
|
415
|
-
if (!this.isFunction[y][x] && invert) this.modules[y][x] = !this.modules[y][x];
|
|
689
|
+
}
|
|
690
|
+
function getPenaltyScore(modules, size) {
|
|
691
|
+
let result = 0;
|
|
692
|
+
for(let y = 0; y < size; y++){
|
|
693
|
+
const runHistory = [
|
|
694
|
+
0,
|
|
695
|
+
0,
|
|
696
|
+
0,
|
|
697
|
+
0,
|
|
698
|
+
0,
|
|
699
|
+
0,
|
|
700
|
+
0
|
|
701
|
+
];
|
|
702
|
+
let color = false;
|
|
703
|
+
let runX = 0;
|
|
704
|
+
for(let x = 0; x < size; x++)if (modules[y][x] === color) {
|
|
705
|
+
runX++;
|
|
706
|
+
if (5 === runX) result += PENALTY_N1;
|
|
707
|
+
else if (runX > 5) result++;
|
|
708
|
+
} else {
|
|
709
|
+
addRunToHistory(runX, runHistory);
|
|
710
|
+
if (!color && hasFinderLikePattern(runHistory)) result += PENALTY_N3;
|
|
711
|
+
color = modules[y][x];
|
|
712
|
+
runX = 1;
|
|
416
713
|
}
|
|
714
|
+
addRunToHistory(runX, runHistory);
|
|
715
|
+
if (color) addRunToHistory(0, runHistory);
|
|
716
|
+
if (hasFinderLikePattern(runHistory)) result += PENALTY_N3;
|
|
417
717
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
color = this.modules[y][x];
|
|
440
|
-
runX = 1;
|
|
441
|
-
}
|
|
442
|
-
QrCode.addRunToHistory(runX, runHistory);
|
|
443
|
-
if (color) QrCode.addRunToHistory(0, runHistory);
|
|
444
|
-
if (QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
|
|
445
|
-
}
|
|
446
|
-
for(let x = 0; x < this.size; x++){
|
|
447
|
-
const runHistory = [
|
|
448
|
-
0,
|
|
449
|
-
0,
|
|
450
|
-
0,
|
|
451
|
-
0,
|
|
452
|
-
0,
|
|
453
|
-
0,
|
|
454
|
-
0
|
|
455
|
-
];
|
|
456
|
-
let color = false;
|
|
457
|
-
let runY = 0;
|
|
458
|
-
for(let y = 0; y < this.size; y++)if (this.modules[y][x] === color) {
|
|
459
|
-
runY++;
|
|
460
|
-
if (5 === runY) result += QrCode.PENALTY_N1;
|
|
461
|
-
else if (runY > 5) result++;
|
|
462
|
-
} else {
|
|
463
|
-
QrCode.addRunToHistory(runY, runHistory);
|
|
464
|
-
if (!color && QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
|
|
465
|
-
color = this.modules[y][x];
|
|
466
|
-
runY = 1;
|
|
467
|
-
}
|
|
468
|
-
QrCode.addRunToHistory(runY, runHistory);
|
|
469
|
-
if (color) QrCode.addRunToHistory(0, runHistory);
|
|
470
|
-
if (QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
|
|
471
|
-
}
|
|
472
|
-
for(let y = 0; y < this.size - 1; y++)for(let x = 0; x < this.size - 1; x++){
|
|
473
|
-
const color = this.modules[y][x];
|
|
474
|
-
if (color === this.modules[y][x + 1] && color === this.modules[y + 1][x] && color === this.modules[y + 1][x + 1]) result += QrCode.PENALTY_N2;
|
|
718
|
+
for(let x = 0; x < size; x++){
|
|
719
|
+
const runHistory = [
|
|
720
|
+
0,
|
|
721
|
+
0,
|
|
722
|
+
0,
|
|
723
|
+
0,
|
|
724
|
+
0,
|
|
725
|
+
0,
|
|
726
|
+
0
|
|
727
|
+
];
|
|
728
|
+
let color = false;
|
|
729
|
+
let runY = 0;
|
|
730
|
+
for(let y = 0; y < size; y++)if (modules[y][x] === color) {
|
|
731
|
+
runY++;
|
|
732
|
+
if (5 === runY) result += PENALTY_N1;
|
|
733
|
+
else if (runY > 5) result++;
|
|
734
|
+
} else {
|
|
735
|
+
addRunToHistory(runY, runHistory);
|
|
736
|
+
if (!color && hasFinderLikePattern(runHistory)) result += PENALTY_N3;
|
|
737
|
+
color = modules[y][x];
|
|
738
|
+
runY = 1;
|
|
475
739
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const k = Math.ceil(Math.abs(20 * black - 10 * total) / total) - 1;
|
|
480
|
-
result += k * QrCode.PENALTY_N4;
|
|
481
|
-
return result;
|
|
740
|
+
addRunToHistory(runY, runHistory);
|
|
741
|
+
if (color) addRunToHistory(0, runHistory);
|
|
742
|
+
if (hasFinderLikePattern(runHistory)) result += PENALTY_N3;
|
|
482
743
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const numAlign = Math.floor(this.version / 7) + 2;
|
|
487
|
-
const step = 32 === this.version ? 26 : 2 * Math.ceil((this.size - 13) / (2 * numAlign - 2));
|
|
488
|
-
const result = [
|
|
489
|
-
6
|
|
490
|
-
];
|
|
491
|
-
for(let pos = this.size - 7; result.length < numAlign; pos -= step)result.splice(1, 0, pos);
|
|
492
|
-
return result;
|
|
493
|
-
}
|
|
744
|
+
for(let y = 0; y < size - 1; y++)for(let x = 0; x < size - 1; x++){
|
|
745
|
+
const color = modules[y][x];
|
|
746
|
+
if (color === modules[y][x + 1] && color === modules[y + 1][x] && color === modules[y + 1][x + 1]) result += PENALTY_N2;
|
|
494
747
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
748
|
+
let black = 0;
|
|
749
|
+
for (const row of modules)for (const color of row)if (color) black++;
|
|
750
|
+
const total = size * size;
|
|
751
|
+
const k = Math.ceil(Math.abs(20 * black - 10 * total) / total) - 1;
|
|
752
|
+
result += k * PENALTY_N4;
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
755
|
+
function addRunToHistory(run, history) {
|
|
756
|
+
history.pop();
|
|
757
|
+
history.unshift(run);
|
|
758
|
+
}
|
|
759
|
+
function hasFinderLikePattern(runHistory) {
|
|
760
|
+
const n = runHistory[1];
|
|
761
|
+
return n > 0 && runHistory[2] === n && runHistory[4] === n && runHistory[5] === n && runHistory[3] === 3 * n && Math.max(runHistory[0], runHistory[6]) >= 4 * n;
|
|
762
|
+
}
|
|
763
|
+
function addEccAndInterleave(version, ecl, data) {
|
|
764
|
+
if (data.length !== getNumDataCodewords(version, ecl)) throw 'Invalid argument';
|
|
765
|
+
const numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][version];
|
|
766
|
+
const blockEccLen = ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][version];
|
|
767
|
+
const rawCodewords = Math.floor(getNumRawDataModules(version) / 8);
|
|
768
|
+
const numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
|
769
|
+
const shortBlockLen = Math.floor(rawCodewords / numBlocks);
|
|
770
|
+
const blocks = [];
|
|
771
|
+
const rs = createReedSolomonGenerator(blockEccLen);
|
|
772
|
+
for(let i = 0, k = 0; i < numBlocks; i++){
|
|
773
|
+
const dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
|
|
774
|
+
k += dat.length;
|
|
775
|
+
const ecc = rs.getRemainder(dat);
|
|
776
|
+
if (i < numShortBlocks) dat.push(0);
|
|
777
|
+
blocks.push(dat.concat(ecc));
|
|
504
778
|
}
|
|
505
|
-
|
|
506
|
-
|
|
779
|
+
const result = [];
|
|
780
|
+
for(let i = 0; i < blocks[0].length; i++)for(let j = 0; j < blocks.length; j++)if (i !== shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(blocks[j][i]);
|
|
781
|
+
if (result.length !== rawCodewords) throw 'Assertion error';
|
|
782
|
+
return result;
|
|
783
|
+
}
|
|
784
|
+
function getNumRawDataModules(ver) {
|
|
785
|
+
if (ver < MIN_VERSION || ver > MAX_VERSION) throw 'Version number out of range';
|
|
786
|
+
let result = (16 * ver + 128) * ver + 64;
|
|
787
|
+
if (ver >= 2) {
|
|
788
|
+
const numAlign = Math.floor(ver / 7) + 2;
|
|
789
|
+
result -= (25 * numAlign - 10) * numAlign - 55;
|
|
790
|
+
if (ver >= 7) result -= 36;
|
|
507
791
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
function getNumDataCodewords(ver, ecl) {
|
|
795
|
+
return Math.floor(getNumRawDataModules(ver) / 8) - ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] * NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
|
|
796
|
+
}
|
|
797
|
+
function getBit(x, i) {
|
|
798
|
+
return (x >>> i & 1) !== 0;
|
|
799
|
+
}
|
|
800
|
+
function createQrSegment(mode, numChars, bitData) {
|
|
801
|
+
if (numChars < 0) throw 'Invalid argument';
|
|
802
|
+
const data = bitData.slice();
|
|
803
|
+
return {
|
|
804
|
+
mode,
|
|
805
|
+
numChars,
|
|
806
|
+
getData: ()=>data.slice()
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function makeBytes(data) {
|
|
810
|
+
const bb = createBitBuffer();
|
|
811
|
+
for (const b of data)bb.appendBits(b, 8);
|
|
812
|
+
return createQrSegment(QrSegmentMode.BYTE, data.length, bb.array);
|
|
813
|
+
}
|
|
814
|
+
function makeNumeric(digits) {
|
|
815
|
+
if (!NUMERIC_REGEX.test(digits)) throw 'String contains non-numeric characters';
|
|
816
|
+
const bb = createBitBuffer();
|
|
817
|
+
for(let i = 0; i < digits.length;){
|
|
818
|
+
const n = Math.min(digits.length - i, 3);
|
|
819
|
+
bb.appendBits(parseInt(digits.substr(i, n), 10), 3 * n + 1);
|
|
820
|
+
i += n;
|
|
511
821
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
822
|
+
return createQrSegment(QrSegmentMode.NUMERIC, digits.length, bb.array);
|
|
823
|
+
}
|
|
824
|
+
function makeAlphanumeric(text) {
|
|
825
|
+
if (!ALPHANUMERIC_REGEX.test(text)) throw 'String contains unencodable characters in alphanumeric mode';
|
|
826
|
+
const bb = createBitBuffer();
|
|
827
|
+
let i;
|
|
828
|
+
for(i = 0; i + 2 <= text.length; i += 2){
|
|
829
|
+
let temp = 45 * ALPHANUMERIC_CHARSET.indexOf(text.charAt(i));
|
|
830
|
+
temp += ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
|
|
831
|
+
bb.appendBits(temp, 11);
|
|
515
832
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
833
|
+
if (i < text.length) bb.appendBits(ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
|
|
834
|
+
return createQrSegment(QrSegmentMode.ALPHANUMERIC, text.length, bb.array);
|
|
835
|
+
}
|
|
836
|
+
function makeSegments(text) {
|
|
837
|
+
if ('' === text) return [];
|
|
838
|
+
else if (NUMERIC_REGEX.test(text)) return [
|
|
839
|
+
makeNumeric(text)
|
|
840
|
+
];
|
|
841
|
+
else if (ALPHANUMERIC_REGEX.test(text)) return [
|
|
842
|
+
makeAlphanumeric(text)
|
|
843
|
+
];
|
|
844
|
+
else return [
|
|
845
|
+
makeBytes(toUtf8ByteArray(text))
|
|
846
|
+
];
|
|
847
|
+
}
|
|
848
|
+
function getTotalBits(segs, version) {
|
|
849
|
+
let result = 0;
|
|
850
|
+
for (const seg of segs){
|
|
851
|
+
const ccbits = numCharCountBits(seg.mode, version);
|
|
852
|
+
if (seg.numChars >= 1 << ccbits) return 1 / 0;
|
|
853
|
+
result += 4 + ccbits + seg.getData().length;
|
|
854
|
+
}
|
|
855
|
+
return result;
|
|
856
|
+
}
|
|
857
|
+
function toUtf8ByteArray(str) {
|
|
858
|
+
str = encodeURI(str);
|
|
859
|
+
const result = [];
|
|
860
|
+
for(let i = 0; i < str.length; i++)if ('%' !== str.charAt(i)) result.push(str.charCodeAt(i));
|
|
861
|
+
else {
|
|
862
|
+
result.push(parseInt(str.substr(i + 1, 2), 16));
|
|
863
|
+
i += 2;
|
|
864
|
+
}
|
|
865
|
+
return result;
|
|
866
|
+
}
|
|
867
|
+
function createBitBuffer() {
|
|
868
|
+
const array = [];
|
|
869
|
+
return {
|
|
870
|
+
array,
|
|
871
|
+
appendBits (val, len) {
|
|
872
|
+
if (len < 0 || len > 31 || val >>> len !== 0) throw 'Value out of range';
|
|
873
|
+
for(let i = len - 1; i >= 0; i--)array.push(val >>> i & 1);
|
|
533
874
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function createReedSolomonGenerator(degree) {
|
|
878
|
+
if (degree < 1 || degree > 255) throw 'Degree out of range';
|
|
879
|
+
const coefficients = [];
|
|
880
|
+
for(let i = 0; i < degree - 1; i++)coefficients.push(0);
|
|
881
|
+
coefficients.push(1);
|
|
882
|
+
let root = 1;
|
|
883
|
+
for(let i = 0; i < degree; i++){
|
|
884
|
+
for(let j = 0; j < coefficients.length; j++){
|
|
885
|
+
coefficients[j] = rsMultiply(coefficients[j], root);
|
|
886
|
+
if (j + 1 < coefficients.length) coefficients[j] ^= coefficients[j + 1];
|
|
887
|
+
}
|
|
888
|
+
root = rsMultiply(root, 0x02);
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
getRemainder (data) {
|
|
892
|
+
const result = coefficients.map(()=>0);
|
|
893
|
+
for (const b of data){
|
|
894
|
+
const factor = b ^ result.shift();
|
|
895
|
+
result.push(0);
|
|
896
|
+
coefficients.forEach((coef, i)=>{
|
|
897
|
+
result[i] ^= rsMultiply(coef, factor);
|
|
898
|
+
});
|
|
548
899
|
}
|
|
900
|
+
return result;
|
|
549
901
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function rsMultiply(x, y) {
|
|
905
|
+
if (x >>> 8 !== 0 || y >>> 8 !== 0) throw 'Byte out of range';
|
|
906
|
+
let z = 0;
|
|
907
|
+
for(let i = 7; i >= 0; i--){
|
|
908
|
+
z = z << 1 ^ (z >>> 7) * 0x11d;
|
|
909
|
+
z ^= (y >>> i & 1) * x;
|
|
555
910
|
}
|
|
911
|
+
if (z >>> 8 !== 0) throw 'Assertion error';
|
|
912
|
+
return z;
|
|
556
913
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
914
|
+
const MIN_VERSION = 1;
|
|
915
|
+
const MAX_VERSION = 40;
|
|
916
|
+
const PENALTY_N1 = 3;
|
|
917
|
+
const PENALTY_N2 = 3;
|
|
918
|
+
const PENALTY_N3 = 40;
|
|
919
|
+
const PENALTY_N4 = 10;
|
|
920
|
+
const ECC_CODEWORDS_PER_BLOCK = [
|
|
564
921
|
[
|
|
565
922
|
-1,
|
|
566
923
|
7,
|
|
@@ -733,8 +1090,8 @@ _define_property__(QrCode, "ECC_CODEWORDS_PER_BLOCK", [
|
|
|
733
1090
|
30,
|
|
734
1091
|
30
|
|
735
1092
|
]
|
|
736
|
-
]
|
|
737
|
-
|
|
1093
|
+
];
|
|
1094
|
+
const NUM_ERROR_CORRECTION_BLOCKS = [
|
|
738
1095
|
[
|
|
739
1096
|
-1,
|
|
740
1097
|
1,
|
|
@@ -907,193 +1264,10 @@ _define_property__(QrCode, "NUM_ERROR_CORRECTION_BLOCKS", [
|
|
|
907
1264
|
77,
|
|
908
1265
|
81
|
|
909
1266
|
]
|
|
910
|
-
]
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
class QrSegment {
|
|
915
|
-
static makeBytes(data) {
|
|
916
|
-
const bb = new BitBuffer();
|
|
917
|
-
for (const b of data)bb.appendBits(b, 8);
|
|
918
|
-
return new QrSegment(QrSegmentMode.BYTE, data.length, bb.array);
|
|
919
|
-
}
|
|
920
|
-
static makeNumeric(digits) {
|
|
921
|
-
if (!QrSegment.NUMERIC_REGEX.test(digits)) throw 'String contains non-numeric characters';
|
|
922
|
-
const bb = new BitBuffer();
|
|
923
|
-
for(let i = 0; i < digits.length;){
|
|
924
|
-
const n = Math.min(digits.length - i, 3);
|
|
925
|
-
bb.appendBits(parseInt(digits.substr(i, n), 10), 3 * n + 1);
|
|
926
|
-
i += n;
|
|
927
|
-
}
|
|
928
|
-
return new QrSegment(QrSegmentMode.NUMERIC, digits.length, bb.array);
|
|
929
|
-
}
|
|
930
|
-
static makeAlphanumeric(text) {
|
|
931
|
-
if (!QrSegment.ALPHANUMERIC_REGEX.test(text)) throw 'String contains unencodable characters in alphanumeric mode';
|
|
932
|
-
const bb = new BitBuffer();
|
|
933
|
-
let i;
|
|
934
|
-
for(i = 0; i + 2 <= text.length; i += 2){
|
|
935
|
-
let temp = 45 * QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i));
|
|
936
|
-
temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
|
|
937
|
-
bb.appendBits(temp, 11);
|
|
938
|
-
}
|
|
939
|
-
if (i < text.length) bb.appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
|
|
940
|
-
return new QrSegment(QrSegmentMode.ALPHANUMERIC, text.length, bb.array);
|
|
941
|
-
}
|
|
942
|
-
static makeSegments(text) {
|
|
943
|
-
if ('' === text) return [];
|
|
944
|
-
if (QrSegment.NUMERIC_REGEX.test(text)) return [
|
|
945
|
-
QrSegment.makeNumeric(text)
|
|
946
|
-
];
|
|
947
|
-
if (QrSegment.ALPHANUMERIC_REGEX.test(text)) return [
|
|
948
|
-
QrSegment.makeAlphanumeric(text)
|
|
949
|
-
];
|
|
950
|
-
return [
|
|
951
|
-
QrSegment.makeBytes(QrSegment.toUtf8ByteArray(text))
|
|
952
|
-
];
|
|
953
|
-
}
|
|
954
|
-
static makeEci(assignVal) {
|
|
955
|
-
const bb = new BitBuffer();
|
|
956
|
-
if (assignVal < 0) throw 'ECI assignment value out of range';
|
|
957
|
-
if (assignVal < 128) bb.appendBits(assignVal, 8);
|
|
958
|
-
else if (assignVal < 16384) {
|
|
959
|
-
bb.appendBits(2, 2);
|
|
960
|
-
bb.appendBits(assignVal, 14);
|
|
961
|
-
} else if (assignVal < 1000000) {
|
|
962
|
-
bb.appendBits(6, 3);
|
|
963
|
-
bb.appendBits(assignVal, 21);
|
|
964
|
-
} else throw 'ECI assignment value out of range';
|
|
965
|
-
return new QrSegment(QrSegmentMode.ECI, 0, bb.array);
|
|
966
|
-
}
|
|
967
|
-
getData() {
|
|
968
|
-
return this.bitData.slice();
|
|
969
|
-
}
|
|
970
|
-
static getTotalBits(segs, version) {
|
|
971
|
-
let result = 0;
|
|
972
|
-
for (const seg of segs){
|
|
973
|
-
const ccbits = seg.mode.numCharCountBits(version);
|
|
974
|
-
if (seg.numChars >= 1 << ccbits) return 1 / 0;
|
|
975
|
-
result += 4 + ccbits + seg.bitData.length;
|
|
976
|
-
}
|
|
977
|
-
return result;
|
|
978
|
-
}
|
|
979
|
-
static toUtf8ByteArray(str) {
|
|
980
|
-
str = encodeURI(str);
|
|
981
|
-
const result = [];
|
|
982
|
-
for(let i = 0; i < str.length; i++)if ('%' !== str.charAt(i)) result.push(str.charCodeAt(i));
|
|
983
|
-
else {
|
|
984
|
-
result.push(parseInt(str.substr(i + 1, 2), 16));
|
|
985
|
-
i += 2;
|
|
986
|
-
}
|
|
987
|
-
return result;
|
|
988
|
-
}
|
|
989
|
-
constructor(mode, numChars, bitData){
|
|
990
|
-
_define_property__(this, "mode", void 0);
|
|
991
|
-
_define_property__(this, "numChars", void 0);
|
|
992
|
-
_define_property__(this, "bitData", void 0);
|
|
993
|
-
if (numChars < 0) throw 'Invalid argument';
|
|
994
|
-
this.mode = mode;
|
|
995
|
-
this.numChars = numChars;
|
|
996
|
-
this.bitData = bitData.slice();
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
_define_property__(QrSegment, "NUMERIC_REGEX", /^[0-9]*$/);
|
|
1000
|
-
_define_property__(QrSegment, "ALPHANUMERIC_REGEX", /^[A-Z0-9 $%*+./:-]*$/);
|
|
1001
|
-
_define_property__(QrSegment, "ALPHANUMERIC_CHARSET", '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:');
|
|
1002
|
-
class ReedSolomonGenerator {
|
|
1003
|
-
getRemainder(data) {
|
|
1004
|
-
const result = this.coefficients.map((_)=>0);
|
|
1005
|
-
for (const b of data){
|
|
1006
|
-
const factor = b ^ result.shift();
|
|
1007
|
-
result.push(0);
|
|
1008
|
-
this.coefficients.forEach((coef, i)=>{
|
|
1009
|
-
result[i] ^= ReedSolomonGenerator.multiply(coef, factor);
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
return result;
|
|
1013
|
-
}
|
|
1014
|
-
static multiply(x, y) {
|
|
1015
|
-
if (x >>> 8 !== 0 || y >>> 8 !== 0) throw 'Byte out of range';
|
|
1016
|
-
let z = 0;
|
|
1017
|
-
for(let i = 7; i >= 0; i--){
|
|
1018
|
-
z = z << 1 ^ (z >>> 7) * 0x11d;
|
|
1019
|
-
z ^= (y >>> i & 1) * x;
|
|
1020
|
-
}
|
|
1021
|
-
if (z >>> 8 !== 0) throw 'Assertion error';
|
|
1022
|
-
return z;
|
|
1023
|
-
}
|
|
1024
|
-
constructor(degree){
|
|
1025
|
-
_define_property__(this, "coefficients", []);
|
|
1026
|
-
if (degree < 1 || degree > 255) throw 'Degree out of range';
|
|
1027
|
-
const coefs = this.coefficients;
|
|
1028
|
-
for(let i = 0; i < degree - 1; i++)coefs.push(0);
|
|
1029
|
-
coefs.push(1);
|
|
1030
|
-
let root = 1;
|
|
1031
|
-
for(let i = 0; i < degree; i++){
|
|
1032
|
-
for(let j = 0; j < coefs.length; j++){
|
|
1033
|
-
coefs[j] = ReedSolomonGenerator.multiply(coefs[j], root);
|
|
1034
|
-
if (j + 1 < coefs.length) coefs[j] ^= coefs[j + 1];
|
|
1035
|
-
}
|
|
1036
|
-
root = ReedSolomonGenerator.multiply(root, 0x02);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
class BitBuffer {
|
|
1041
|
-
appendBits(val, len) {
|
|
1042
|
-
if (len < 0 || len > 31 || val >>> len !== 0) throw 'Value out of range';
|
|
1043
|
-
for(let i = len - 1; i >= 0; i--)this.array.push(val >>> i & 1);
|
|
1044
|
-
}
|
|
1045
|
-
constructor(){
|
|
1046
|
-
_define_property__(this, "array", []);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
class QrCodeEcc {
|
|
1050
|
-
constructor(ordinal, formatBits){
|
|
1051
|
-
_define_property__(this, "ordinal", void 0);
|
|
1052
|
-
_define_property__(this, "formatBits", void 0);
|
|
1053
|
-
this.ordinal = ordinal;
|
|
1054
|
-
this.formatBits = formatBits;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
_define_property__(QrCodeEcc, "LOW", new QrCodeEcc(0, 1));
|
|
1058
|
-
_define_property__(QrCodeEcc, "MEDIUM", new QrCodeEcc(1, 0));
|
|
1059
|
-
_define_property__(QrCodeEcc, "QUARTILE", new QrCodeEcc(2, 3));
|
|
1060
|
-
_define_property__(QrCodeEcc, "HIGH", new QrCodeEcc(3, 2));
|
|
1061
|
-
class QrSegmentMode {
|
|
1062
|
-
numCharCountBits(ver) {
|
|
1063
|
-
return this.numBitsCharCount[Math.floor((ver + 7) / 17)];
|
|
1064
|
-
}
|
|
1065
|
-
constructor(modeBits, numBitsCharCount){
|
|
1066
|
-
_define_property__(this, "modeBits", void 0);
|
|
1067
|
-
_define_property__(this, "numBitsCharCount", void 0);
|
|
1068
|
-
this.modeBits = modeBits;
|
|
1069
|
-
this.numBitsCharCount = numBitsCharCount;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
_define_property__(QrSegmentMode, "NUMERIC", new QrSegmentMode(0x1, [
|
|
1073
|
-
10,
|
|
1074
|
-
12,
|
|
1075
|
-
14
|
|
1076
|
-
]));
|
|
1077
|
-
_define_property__(QrSegmentMode, "ALPHANUMERIC", new QrSegmentMode(0x2, [
|
|
1078
|
-
9,
|
|
1079
|
-
11,
|
|
1080
|
-
13
|
|
1081
|
-
]));
|
|
1082
|
-
_define_property__(QrSegmentMode, "BYTE", new QrSegmentMode(0x4, [
|
|
1083
|
-
8,
|
|
1084
|
-
16,
|
|
1085
|
-
16
|
|
1086
|
-
]));
|
|
1087
|
-
_define_property__(QrSegmentMode, "KANJI", new QrSegmentMode(0x8, [
|
|
1088
|
-
8,
|
|
1089
|
-
10,
|
|
1090
|
-
12
|
|
1091
|
-
]));
|
|
1092
|
-
_define_property__(QrSegmentMode, "ECI", new QrSegmentMode(0x7, [
|
|
1093
|
-
0,
|
|
1094
|
-
0,
|
|
1095
|
-
0
|
|
1096
|
-
]));
|
|
1267
|
+
];
|
|
1268
|
+
const NUMERIC_REGEX = /^[0-9]*$/;
|
|
1269
|
+
const ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+./:-]*$/;
|
|
1270
|
+
const ALPHANUMERIC_CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
1097
1271
|
const DEFAULT_SIZE = 128;
|
|
1098
1272
|
const BACKGROUND_COLOR_DEFAULT = '#ffffff';
|
|
1099
1273
|
const FOREGROUND_COLOR_DEFAULT = '#000000';
|
|
@@ -1119,14 +1293,16 @@ function createQR(text, qrSizeOrOptions, classNameLegacy, legacyOptions) {
|
|
|
1119
1293
|
isShowBackground: !!options.isShowBackground || false,
|
|
1120
1294
|
foregroundColor: 'string' == typeof options.foregroundColor ? options.foregroundColor : FOREGROUND_COLOR_DEFAULT,
|
|
1121
1295
|
backgroundColor: 'string' == typeof options.backgroundColor ? options.backgroundColor : BACKGROUND_COLOR_DEFAULT,
|
|
1296
|
+
backgroundPadding: 'number' == typeof options.backgroundPadding && Number.isFinite(options.backgroundPadding) && options.backgroundPadding >= 0 ? options.backgroundPadding : 20,
|
|
1297
|
+
backgroundRadius: 'number' == typeof options.backgroundRadius && Number.isFinite(options.backgroundRadius) && options.backgroundRadius >= 0 ? options.backgroundRadius : 36,
|
|
1122
1298
|
logoColor: 'string' == typeof options.logoColor ? options.logoColor : LOGO_COLOR_DEFAULT,
|
|
1123
1299
|
suffix: options.suffix ? options.suffix.toString() : '0',
|
|
1124
1300
|
logoData: 'string' == typeof options.logoData ? options.logoData : null,
|
|
1125
1301
|
ecc: 'number' == typeof options.ecc && eccLevels[options.ecc] ? options.ecc : 3,
|
|
1126
|
-
incTileSize: 'number' == typeof options.incTileSize ? options.incTileSize :
|
|
1302
|
+
incTileSize: 'number' == typeof options.incTileSize ? options.incTileSize : INC_TILE_SIZE
|
|
1127
1303
|
};
|
|
1128
|
-
const segments =
|
|
1129
|
-
const qrCode =
|
|
1304
|
+
const segments = makeSegments(text);
|
|
1305
|
+
const qrCode = encodeSegments(segments, eccLevels[fallbackOptions.ecc], 1, 40, -1, true);
|
|
1130
1306
|
const svgCode = convertSegmentsToSvgString(qrCode, fallbackOptions);
|
|
1131
1307
|
return svgCode;
|
|
1132
1308
|
}
|