@vkontakte/vk-qr 2.1.1 → 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/LICENSE +1 -1
- package/README.md +22 -3
- 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 +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1311 -0
- package/dist/index.js.map +1 -0
- package/dist/qr.d.ts +71 -0
- package/dist/qr.d.ts.map +1 -0
- package/dist/svg-path.d.ts +8 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg.d.ts +6 -0
- package/dist/svg.d.ts.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +55 -80
- package/src/constants.ts +9 -0
- package/src/figures.ts +106 -0
- package/src/index.ts +45 -27
- package/src/qr.ts +711 -837
- package/src/svg-path.ts +254 -0
- package/src/svg.ts +132 -257
- package/src/types.ts +30 -12
- package/dist/cjs/index.js +0 -1185
- package/dist/es/index.js +0 -1183
- package/dist/types/index.d.ts +0 -20
- package/dist/types/qr.d.ts +0 -87
- package/dist/types/svg.d.ts +0 -9
- package/dist/types/types.d.ts +0 -51
- package/dist/umd/qrCodeGenerator.js +0 -1191
- package/dist/umd/qrCodeGenerator.min.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,1311 @@
|
|
|
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;
|
|
57
|
+
}
|
|
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
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
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;
|
|
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;
|
|
233
|
+
}
|
|
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}`);
|
|
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) {
|
|
331
|
+
let scale = '';
|
|
332
|
+
let position = (size - 21) / 2 * 100 + 700 - 10;
|
|
333
|
+
if (size <= 25) {
|
|
334
|
+
scale = 'scale(0.85)';
|
|
335
|
+
position += 50;
|
|
336
|
+
}
|
|
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 `
|
|
345
|
+
<image
|
|
346
|
+
preserveAspectRatio="xMidYMid slice" clip-path="url(#logo-mask-${options.suffix})"
|
|
347
|
+
style="width: 750px; height: 750px;" width="750" height="750"
|
|
348
|
+
transform="translate(${position},${position}) ${scale}"
|
|
349
|
+
xlink:href="${options.logoData}"
|
|
350
|
+
/>
|
|
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 `
|
|
378
|
+
<rect
|
|
379
|
+
x="${-padding}"
|
|
380
|
+
y="${-padding}"
|
|
381
|
+
width="${size}"
|
|
382
|
+
height="${size}"
|
|
383
|
+
rx="${radius}"
|
|
384
|
+
fill="${options.backgroundColor}"
|
|
385
|
+
/>`;
|
|
386
|
+
}
|
|
387
|
+
function renderDefinitions(options) {
|
|
388
|
+
return `<defs>
|
|
389
|
+
<g
|
|
390
|
+
id="point-${options.suffix}"
|
|
391
|
+
fill="${options.foregroundColor}"
|
|
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>
|
|
396
|
+
<g id="vk_logo-${options.suffix}">
|
|
397
|
+
<path
|
|
398
|
+
fill="${options.logoColor}"
|
|
399
|
+
d="M83.3334 363.333C83.3334 231.34 83.3334 165.343 124.338 124.338C165.343 83.3333 231.34 \
|
|
400
|
+
83.3333 363.333 83.3333H386.667C518.66 83.3333 584.657 83.3333 625.662 124.338C666.667 165.343 \
|
|
401
|
+
666.667 231.34 666.667 363.333V386.667C666.667 518.66 666.667 584.657 625.662 625.662C584.657 \
|
|
402
|
+
666.667 518.66 666.667 386.667 666.667H363.333C231.34 666.667 165.343 666.667 124.338 625.662C83.3334 \
|
|
403
|
+
584.657 83.3334 518.66 83.3334 386.667V363.333Z"
|
|
404
|
+
/>
|
|
405
|
+
<path
|
|
406
|
+
fill="#FFF"
|
|
407
|
+
d="M394.907 508.681C263.293 508.681 183.442 417.365 180.313 265.625H246.972C249.05 377.09 \
|
|
408
|
+
299.763 424.323 338.638 434.036V265.625H402.519V361.82C440.016 357.664 479.264 313.909 \
|
|
409
|
+
492.456 265.625H555.333C545.27 325 502.543 368.754 472.353 386.803C502.566 401.395 551.177 \
|
|
410
|
+
439.593 569.926 508.681H500.815C486.222 462.498 450.476 426.728 402.542 421.872V508.681H394.907Z"
|
|
411
|
+
/>
|
|
412
|
+
</g>
|
|
413
|
+
<clipPath id="logo-mask-${options.suffix}">
|
|
414
|
+
<rect x="0" y="0" width="750" height="750" />
|
|
415
|
+
</clipPath>
|
|
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
|
|
434
|
+
}
|
|
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
|
+
]
|
|
476
|
+
}
|
|
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;
|
|
491
|
+
}
|
|
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);
|
|
505
|
+
}
|
|
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());
|
|
531
|
+
}
|
|
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);
|
|
539
|
+
}
|
|
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);
|
|
546
|
+
}
|
|
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]);
|
|
558
|
+
}
|
|
559
|
+
const drawFormatBits = (ecl, msk)=>{
|
|
560
|
+
const data = ecl.formatBits << 3 | msk;
|
|
561
|
+
let rem = data;
|
|
562
|
+
for(let i = 0; i < 10; i++)rem = rem << 1 ^ (rem >>> 9) * 0x537;
|
|
563
|
+
const bits = (data << 10 | rem) ^ 0x5412;
|
|
564
|
+
if (bits >>> 15 !== 0) throw 'Assertion error';
|
|
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;
|
|
577
|
+
for(let i = 0; i < 12; i++)rem = rem << 1 ^ (rem >>> 11) * 0x1f25;
|
|
578
|
+
const bits = version << 12 | rem;
|
|
579
|
+
if (bits >>> 18 !== 0) throw 'Assertion error';
|
|
580
|
+
for(let i = 0; i < 18; i++){
|
|
581
|
+
const color = getBit(bits, i);
|
|
582
|
+
const a = size - 11 + i % 3;
|
|
583
|
+
const b = Math.floor(i / 3);
|
|
584
|
+
setFunctionModule(a, b, color);
|
|
585
|
+
setFunctionModule(b, a, color);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
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
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
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
|
+
}
|
|
617
|
+
}
|
|
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;
|
|
634
|
+
}
|
|
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);
|
|
652
|
+
return result;
|
|
653
|
+
}
|
|
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';
|
|
686
|
+
}
|
|
687
|
+
if (!isFunction[y][x] && invert) modules[y][x] = !modules[y][x];
|
|
688
|
+
}
|
|
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;
|
|
713
|
+
}
|
|
714
|
+
addRunToHistory(runX, runHistory);
|
|
715
|
+
if (color) addRunToHistory(0, runHistory);
|
|
716
|
+
if (hasFinderLikePattern(runHistory)) result += PENALTY_N3;
|
|
717
|
+
}
|
|
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;
|
|
739
|
+
}
|
|
740
|
+
addRunToHistory(runY, runHistory);
|
|
741
|
+
if (color) addRunToHistory(0, runHistory);
|
|
742
|
+
if (hasFinderLikePattern(runHistory)) result += PENALTY_N3;
|
|
743
|
+
}
|
|
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;
|
|
747
|
+
}
|
|
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));
|
|
778
|
+
}
|
|
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;
|
|
791
|
+
}
|
|
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;
|
|
821
|
+
}
|
|
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);
|
|
832
|
+
}
|
|
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);
|
|
874
|
+
}
|
|
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
|
+
});
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
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;
|
|
910
|
+
}
|
|
911
|
+
if (z >>> 8 !== 0) throw 'Assertion error';
|
|
912
|
+
return z;
|
|
913
|
+
}
|
|
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 = [
|
|
921
|
+
[
|
|
922
|
+
-1,
|
|
923
|
+
7,
|
|
924
|
+
10,
|
|
925
|
+
15,
|
|
926
|
+
20,
|
|
927
|
+
26,
|
|
928
|
+
18,
|
|
929
|
+
20,
|
|
930
|
+
24,
|
|
931
|
+
30,
|
|
932
|
+
18,
|
|
933
|
+
20,
|
|
934
|
+
24,
|
|
935
|
+
26,
|
|
936
|
+
30,
|
|
937
|
+
22,
|
|
938
|
+
24,
|
|
939
|
+
28,
|
|
940
|
+
30,
|
|
941
|
+
28,
|
|
942
|
+
28,
|
|
943
|
+
28,
|
|
944
|
+
28,
|
|
945
|
+
30,
|
|
946
|
+
30,
|
|
947
|
+
26,
|
|
948
|
+
28,
|
|
949
|
+
30,
|
|
950
|
+
30,
|
|
951
|
+
30,
|
|
952
|
+
30,
|
|
953
|
+
30,
|
|
954
|
+
30,
|
|
955
|
+
30,
|
|
956
|
+
30,
|
|
957
|
+
30,
|
|
958
|
+
30,
|
|
959
|
+
30,
|
|
960
|
+
30,
|
|
961
|
+
30,
|
|
962
|
+
30
|
|
963
|
+
],
|
|
964
|
+
[
|
|
965
|
+
-1,
|
|
966
|
+
10,
|
|
967
|
+
16,
|
|
968
|
+
26,
|
|
969
|
+
18,
|
|
970
|
+
24,
|
|
971
|
+
16,
|
|
972
|
+
18,
|
|
973
|
+
22,
|
|
974
|
+
22,
|
|
975
|
+
26,
|
|
976
|
+
30,
|
|
977
|
+
22,
|
|
978
|
+
22,
|
|
979
|
+
24,
|
|
980
|
+
24,
|
|
981
|
+
28,
|
|
982
|
+
28,
|
|
983
|
+
26,
|
|
984
|
+
26,
|
|
985
|
+
26,
|
|
986
|
+
26,
|
|
987
|
+
28,
|
|
988
|
+
28,
|
|
989
|
+
28,
|
|
990
|
+
28,
|
|
991
|
+
28,
|
|
992
|
+
28,
|
|
993
|
+
28,
|
|
994
|
+
28,
|
|
995
|
+
28,
|
|
996
|
+
28,
|
|
997
|
+
28,
|
|
998
|
+
28,
|
|
999
|
+
28,
|
|
1000
|
+
28,
|
|
1001
|
+
28,
|
|
1002
|
+
28,
|
|
1003
|
+
28,
|
|
1004
|
+
28,
|
|
1005
|
+
28
|
|
1006
|
+
],
|
|
1007
|
+
[
|
|
1008
|
+
-1,
|
|
1009
|
+
13,
|
|
1010
|
+
22,
|
|
1011
|
+
18,
|
|
1012
|
+
26,
|
|
1013
|
+
18,
|
|
1014
|
+
24,
|
|
1015
|
+
18,
|
|
1016
|
+
22,
|
|
1017
|
+
20,
|
|
1018
|
+
24,
|
|
1019
|
+
28,
|
|
1020
|
+
26,
|
|
1021
|
+
24,
|
|
1022
|
+
20,
|
|
1023
|
+
30,
|
|
1024
|
+
24,
|
|
1025
|
+
28,
|
|
1026
|
+
28,
|
|
1027
|
+
26,
|
|
1028
|
+
30,
|
|
1029
|
+
28,
|
|
1030
|
+
30,
|
|
1031
|
+
30,
|
|
1032
|
+
30,
|
|
1033
|
+
30,
|
|
1034
|
+
28,
|
|
1035
|
+
30,
|
|
1036
|
+
30,
|
|
1037
|
+
30,
|
|
1038
|
+
30,
|
|
1039
|
+
30,
|
|
1040
|
+
30,
|
|
1041
|
+
30,
|
|
1042
|
+
30,
|
|
1043
|
+
30,
|
|
1044
|
+
30,
|
|
1045
|
+
30,
|
|
1046
|
+
30,
|
|
1047
|
+
30,
|
|
1048
|
+
30
|
|
1049
|
+
],
|
|
1050
|
+
[
|
|
1051
|
+
-1,
|
|
1052
|
+
17,
|
|
1053
|
+
28,
|
|
1054
|
+
22,
|
|
1055
|
+
16,
|
|
1056
|
+
22,
|
|
1057
|
+
28,
|
|
1058
|
+
26,
|
|
1059
|
+
26,
|
|
1060
|
+
24,
|
|
1061
|
+
28,
|
|
1062
|
+
24,
|
|
1063
|
+
28,
|
|
1064
|
+
22,
|
|
1065
|
+
24,
|
|
1066
|
+
24,
|
|
1067
|
+
30,
|
|
1068
|
+
28,
|
|
1069
|
+
28,
|
|
1070
|
+
26,
|
|
1071
|
+
28,
|
|
1072
|
+
30,
|
|
1073
|
+
24,
|
|
1074
|
+
30,
|
|
1075
|
+
30,
|
|
1076
|
+
30,
|
|
1077
|
+
30,
|
|
1078
|
+
30,
|
|
1079
|
+
30,
|
|
1080
|
+
30,
|
|
1081
|
+
30,
|
|
1082
|
+
30,
|
|
1083
|
+
30,
|
|
1084
|
+
30,
|
|
1085
|
+
30,
|
|
1086
|
+
30,
|
|
1087
|
+
30,
|
|
1088
|
+
30,
|
|
1089
|
+
30,
|
|
1090
|
+
30,
|
|
1091
|
+
30
|
|
1092
|
+
]
|
|
1093
|
+
];
|
|
1094
|
+
const NUM_ERROR_CORRECTION_BLOCKS = [
|
|
1095
|
+
[
|
|
1096
|
+
-1,
|
|
1097
|
+
1,
|
|
1098
|
+
1,
|
|
1099
|
+
1,
|
|
1100
|
+
1,
|
|
1101
|
+
1,
|
|
1102
|
+
2,
|
|
1103
|
+
2,
|
|
1104
|
+
2,
|
|
1105
|
+
2,
|
|
1106
|
+
4,
|
|
1107
|
+
4,
|
|
1108
|
+
4,
|
|
1109
|
+
4,
|
|
1110
|
+
4,
|
|
1111
|
+
6,
|
|
1112
|
+
6,
|
|
1113
|
+
6,
|
|
1114
|
+
6,
|
|
1115
|
+
7,
|
|
1116
|
+
8,
|
|
1117
|
+
8,
|
|
1118
|
+
9,
|
|
1119
|
+
9,
|
|
1120
|
+
10,
|
|
1121
|
+
12,
|
|
1122
|
+
12,
|
|
1123
|
+
12,
|
|
1124
|
+
13,
|
|
1125
|
+
14,
|
|
1126
|
+
15,
|
|
1127
|
+
16,
|
|
1128
|
+
17,
|
|
1129
|
+
18,
|
|
1130
|
+
19,
|
|
1131
|
+
19,
|
|
1132
|
+
20,
|
|
1133
|
+
21,
|
|
1134
|
+
22,
|
|
1135
|
+
24,
|
|
1136
|
+
25
|
|
1137
|
+
],
|
|
1138
|
+
[
|
|
1139
|
+
-1,
|
|
1140
|
+
1,
|
|
1141
|
+
1,
|
|
1142
|
+
1,
|
|
1143
|
+
2,
|
|
1144
|
+
2,
|
|
1145
|
+
4,
|
|
1146
|
+
4,
|
|
1147
|
+
4,
|
|
1148
|
+
5,
|
|
1149
|
+
5,
|
|
1150
|
+
5,
|
|
1151
|
+
8,
|
|
1152
|
+
9,
|
|
1153
|
+
9,
|
|
1154
|
+
10,
|
|
1155
|
+
10,
|
|
1156
|
+
11,
|
|
1157
|
+
13,
|
|
1158
|
+
14,
|
|
1159
|
+
16,
|
|
1160
|
+
17,
|
|
1161
|
+
17,
|
|
1162
|
+
18,
|
|
1163
|
+
20,
|
|
1164
|
+
21,
|
|
1165
|
+
23,
|
|
1166
|
+
25,
|
|
1167
|
+
26,
|
|
1168
|
+
28,
|
|
1169
|
+
29,
|
|
1170
|
+
31,
|
|
1171
|
+
33,
|
|
1172
|
+
35,
|
|
1173
|
+
37,
|
|
1174
|
+
38,
|
|
1175
|
+
40,
|
|
1176
|
+
43,
|
|
1177
|
+
45,
|
|
1178
|
+
47,
|
|
1179
|
+
49
|
|
1180
|
+
],
|
|
1181
|
+
[
|
|
1182
|
+
-1,
|
|
1183
|
+
1,
|
|
1184
|
+
1,
|
|
1185
|
+
2,
|
|
1186
|
+
2,
|
|
1187
|
+
4,
|
|
1188
|
+
4,
|
|
1189
|
+
6,
|
|
1190
|
+
6,
|
|
1191
|
+
8,
|
|
1192
|
+
8,
|
|
1193
|
+
8,
|
|
1194
|
+
10,
|
|
1195
|
+
12,
|
|
1196
|
+
16,
|
|
1197
|
+
12,
|
|
1198
|
+
17,
|
|
1199
|
+
16,
|
|
1200
|
+
18,
|
|
1201
|
+
21,
|
|
1202
|
+
20,
|
|
1203
|
+
23,
|
|
1204
|
+
23,
|
|
1205
|
+
25,
|
|
1206
|
+
27,
|
|
1207
|
+
29,
|
|
1208
|
+
34,
|
|
1209
|
+
34,
|
|
1210
|
+
35,
|
|
1211
|
+
38,
|
|
1212
|
+
40,
|
|
1213
|
+
43,
|
|
1214
|
+
45,
|
|
1215
|
+
48,
|
|
1216
|
+
51,
|
|
1217
|
+
53,
|
|
1218
|
+
56,
|
|
1219
|
+
59,
|
|
1220
|
+
62,
|
|
1221
|
+
65,
|
|
1222
|
+
68
|
|
1223
|
+
],
|
|
1224
|
+
[
|
|
1225
|
+
-1,
|
|
1226
|
+
1,
|
|
1227
|
+
1,
|
|
1228
|
+
2,
|
|
1229
|
+
4,
|
|
1230
|
+
4,
|
|
1231
|
+
4,
|
|
1232
|
+
5,
|
|
1233
|
+
6,
|
|
1234
|
+
8,
|
|
1235
|
+
8,
|
|
1236
|
+
11,
|
|
1237
|
+
11,
|
|
1238
|
+
16,
|
|
1239
|
+
16,
|
|
1240
|
+
18,
|
|
1241
|
+
16,
|
|
1242
|
+
19,
|
|
1243
|
+
21,
|
|
1244
|
+
25,
|
|
1245
|
+
25,
|
|
1246
|
+
25,
|
|
1247
|
+
34,
|
|
1248
|
+
30,
|
|
1249
|
+
32,
|
|
1250
|
+
35,
|
|
1251
|
+
37,
|
|
1252
|
+
40,
|
|
1253
|
+
42,
|
|
1254
|
+
45,
|
|
1255
|
+
48,
|
|
1256
|
+
51,
|
|
1257
|
+
54,
|
|
1258
|
+
57,
|
|
1259
|
+
60,
|
|
1260
|
+
63,
|
|
1261
|
+
66,
|
|
1262
|
+
70,
|
|
1263
|
+
74,
|
|
1264
|
+
77,
|
|
1265
|
+
81
|
|
1266
|
+
]
|
|
1267
|
+
];
|
|
1268
|
+
const NUMERIC_REGEX = /^[0-9]*$/;
|
|
1269
|
+
const ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+./:-]*$/;
|
|
1270
|
+
const ALPHANUMERIC_CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
1271
|
+
const DEFAULT_SIZE = 128;
|
|
1272
|
+
const BACKGROUND_COLOR_DEFAULT = '#ffffff';
|
|
1273
|
+
const FOREGROUND_COLOR_DEFAULT = '#000000';
|
|
1274
|
+
const LOGO_COLOR_DEFAULT = '#07f';
|
|
1275
|
+
function createQR(text, qrSizeOrOptions, classNameLegacy, legacyOptions) {
|
|
1276
|
+
if ('string' != typeof text) throw new TypeError('Enter text for encoding');
|
|
1277
|
+
const options = {
|
|
1278
|
+
...'object' == typeof qrSizeOrOptions && null !== qrSizeOrOptions ? qrSizeOrOptions : {},
|
|
1279
|
+
...'object' == typeof legacyOptions && null !== legacyOptions ? legacyOptions : {},
|
|
1280
|
+
qrSize: 'object' == typeof qrSizeOrOptions && null !== qrSizeOrOptions && 'number' == typeof qrSizeOrOptions.qrSize ? qrSizeOrOptions.qrSize : qrSizeOrOptions,
|
|
1281
|
+
className: 'object' == typeof qrSizeOrOptions && null !== qrSizeOrOptions && 'string' == typeof qrSizeOrOptions.className ? qrSizeOrOptions.className : classNameLegacy
|
|
1282
|
+
};
|
|
1283
|
+
const eccLevels = [
|
|
1284
|
+
QrCodeEcc.LOW,
|
|
1285
|
+
QrCodeEcc.MEDIUM,
|
|
1286
|
+
QrCodeEcc.QUARTILE,
|
|
1287
|
+
QrCodeEcc.HIGH
|
|
1288
|
+
];
|
|
1289
|
+
const fallbackOptions = {
|
|
1290
|
+
qrSize: 'number' == typeof options.qrSize ? options.qrSize : DEFAULT_SIZE,
|
|
1291
|
+
className: 'string' == typeof options.className ? options.className : classNameLegacy || '',
|
|
1292
|
+
isShowLogo: !!options.isShowLogo || false,
|
|
1293
|
+
isShowBackground: !!options.isShowBackground || false,
|
|
1294
|
+
foregroundColor: 'string' == typeof options.foregroundColor ? options.foregroundColor : FOREGROUND_COLOR_DEFAULT,
|
|
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,
|
|
1298
|
+
logoColor: 'string' == typeof options.logoColor ? options.logoColor : LOGO_COLOR_DEFAULT,
|
|
1299
|
+
suffix: options.suffix ? options.suffix.toString() : '0',
|
|
1300
|
+
logoData: 'string' == typeof options.logoData ? options.logoData : null,
|
|
1301
|
+
ecc: 'number' == typeof options.ecc && eccLevels[options.ecc] ? options.ecc : 3,
|
|
1302
|
+
incTileSize: 'number' == typeof options.incTileSize ? options.incTileSize : INC_TILE_SIZE
|
|
1303
|
+
};
|
|
1304
|
+
const segments = makeSegments(text);
|
|
1305
|
+
const qrCode = encodeSegments(segments, eccLevels[fallbackOptions.ecc], 1, 40, -1, true);
|
|
1306
|
+
const svgCode = convertSegmentsToSvgString(qrCode, fallbackOptions);
|
|
1307
|
+
return svgCode;
|
|
1308
|
+
}
|
|
1309
|
+
export { createQR };
|
|
1310
|
+
|
|
1311
|
+
//# sourceMappingURL=index.js.map
|