@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/src/figures.ts ADDED
@@ -0,0 +1,106 @@
1
+ import { FINDER_PATTERN_SIZE } from './constants.ts';
2
+
3
+ /** A connected group of modules stored in its smallest bounding box. */
4
+ export interface Figure {
5
+ x: number;
6
+ y: number;
7
+ pixels: boolean[][];
8
+ }
9
+
10
+ export function isFinderPattern(figure: Figure, size: number): boolean {
11
+ const { x, y, pixels } = figure;
12
+ const width = pixels[0]?.length ?? 0;
13
+ const height = pixels.length;
14
+ const right = x + width - 1;
15
+ const bottom = y + height - 1;
16
+
17
+ const finderRegions: Array<[number, number, number, number]> = [
18
+ [0, 0, FINDER_PATTERN_SIZE - 1, FINDER_PATTERN_SIZE - 1],
19
+ [size - FINDER_PATTERN_SIZE, 0, size - 1, FINDER_PATTERN_SIZE - 1],
20
+ [0, size - FINDER_PATTERN_SIZE, FINDER_PATTERN_SIZE - 1, size - 1],
21
+ ];
22
+
23
+ for (const [rx1, ry1, rx2, ry2] of finderRegions) {
24
+ if (x >= rx1 && y >= ry1 && right <= rx2 && bottom <= ry2) {
25
+ return true;
26
+ }
27
+ }
28
+
29
+ return false;
30
+ }
31
+
32
+ export function extractFigures(image: boolean[][]): Figure[] {
33
+ const height = image.length;
34
+ const width = image[0]?.length ?? 0;
35
+
36
+ const visited: boolean[][] = Array.from({ length: height }, () => Array(width).fill(false));
37
+
38
+ const figures: Figure[] = [];
39
+
40
+ const directions = [
41
+ [0, -1], // up
42
+ [1, 0], // right
43
+ [0, 1], // down
44
+ [-1, 0], // left
45
+ ];
46
+
47
+ function isInside(x: number, y: number): boolean {
48
+ return y >= 0 && y < height && x >= 0 && x < width;
49
+ }
50
+
51
+ for (let y = 0; y < height; y++) {
52
+ for (let x = 0; x < width; x++) {
53
+ if (visited[y][x]) continue;
54
+ if (!image[y][x]) continue;
55
+
56
+ const cells: Array<[number, number]> = [[x, y]];
57
+
58
+ visited[y][x] = true;
59
+
60
+ let minX = x;
61
+ let maxX = x;
62
+ let minY = y;
63
+ let maxY = y;
64
+
65
+ for (let head = 0; head < cells.length; head++) {
66
+ const [cx, cy] = cells[head];
67
+
68
+ minX = Math.min(minX, cx);
69
+ maxX = Math.max(maxX, cx);
70
+ minY = Math.min(minY, cy);
71
+ maxY = Math.max(maxY, cy);
72
+
73
+ for (const [dx, dy] of directions) {
74
+ const nx = cx + dx;
75
+ const ny = cy + dy;
76
+
77
+ if (!isInside(nx, ny)) continue;
78
+ if (visited[ny][nx]) continue;
79
+ if (!image[ny][nx]) continue;
80
+
81
+ visited[ny][nx] = true;
82
+ cells.push([nx, ny]);
83
+ }
84
+ }
85
+
86
+ const figureWidth = maxX - minX + 1;
87
+ const figureHeight = maxY - minY + 1;
88
+
89
+ const pixels: boolean[][] = Array.from({ length: figureHeight }, () =>
90
+ Array(figureWidth).fill(false),
91
+ );
92
+
93
+ for (const [cellX, cellY] of cells) {
94
+ pixels[cellY - minY][cellX - minX] = true;
95
+ }
96
+
97
+ figures.push({
98
+ x: minX,
99
+ y: minY,
100
+ pixels,
101
+ });
102
+ }
103
+ }
104
+
105
+ return figures;
106
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { convertSegmentsToSvgString, INC_TILE_SIZE } from './svg';
2
- import { QrSegment, QrCode } from './qr';
3
- import { QrOptions } from './types';
4
- import Ecc = QrCode.Ecc;
1
+ import { convertSegmentsToSvgString, INC_TILE_SIZE } from './svg.ts';
2
+ import { makeSegments, encodeSegments, QrCodeEcc } from './qr.ts';
3
+ import type { QrOptions, RequiredQrOptions } from './types.ts';
4
+ import { BACKGROUND_PADDING, BACKGROUND_RADIUS } from './constants.ts';
5
5
 
6
6
  /** Default width and height of QR code */
7
7
  const DEFAULT_SIZE = 128;
@@ -20,7 +20,7 @@ const LOGO_COLOR_DEFAULT = '#07f';
20
20
  * @param text String to encode
21
21
  * @param options QR code options
22
22
  */
23
- function createQR(text: string, options?: QrOptions): string;
23
+ export function createQR(text: string, options?: QrOptions): string;
24
24
 
25
25
  /**
26
26
  * Legacy interface
@@ -30,16 +30,21 @@ function createQR(text: string, options?: QrOptions): string;
30
30
  * @param className SVG element class name
31
31
  * @param options Options of the QR code
32
32
  */
33
- function createQR(text: string, qrSize: number, className: string, options?: QrOptions): string;
33
+ export function createQR(
34
+ text: string,
35
+ qrSize: number,
36
+ className: string,
37
+ options?: QrOptions,
38
+ ): string;
34
39
 
35
40
  /**
36
41
  * Implementation
37
42
  */
38
- function createQR(
43
+ export function createQR(
39
44
  text: string,
40
45
  qrSizeOrOptions?: number | QrOptions,
41
46
  classNameLegacy?: string,
42
- legacyOptions?: QrOptions
47
+ legacyOptions?: QrOptions,
43
48
  ): string {
44
49
  if (typeof text !== 'string') {
45
50
  throw new TypeError('Enter text for encoding');
@@ -50,46 +55,59 @@ function createQR(
50
55
  ...(typeof qrSizeOrOptions === 'object' && qrSizeOrOptions !== null ? qrSizeOrOptions : {}),
51
56
  ...(typeof legacyOptions === 'object' && legacyOptions !== null ? legacyOptions : {}),
52
57
  qrSize:
53
- typeof qrSizeOrOptions === 'object' && qrSizeOrOptions !== null && typeof qrSizeOrOptions.qrSize === 'number'
58
+ typeof qrSizeOrOptions === 'object' &&
59
+ qrSizeOrOptions !== null &&
60
+ typeof qrSizeOrOptions.qrSize === 'number'
54
61
  ? qrSizeOrOptions.qrSize
55
62
  : (qrSizeOrOptions as number),
56
63
  className:
57
- typeof qrSizeOrOptions === 'object' && qrSizeOrOptions !== null && typeof qrSizeOrOptions.className === 'string'
64
+ typeof qrSizeOrOptions === 'object' &&
65
+ qrSizeOrOptions !== null &&
66
+ typeof qrSizeOrOptions.className === 'string'
58
67
  ? qrSizeOrOptions.className
59
- : classNameLegacy
68
+ : classNameLegacy,
60
69
  };
61
70
 
62
71
  // ECC levels mapping
63
- const eccLevels = [
64
- QrCode.Ecc.LOW,
65
- QrCode.Ecc.MEDIUM,
66
- QrCode.Ecc.QUARTILE,
67
- QrCode.Ecc.HIGH
68
- ];
72
+ const eccLevels = [QrCodeEcc.LOW, QrCodeEcc.MEDIUM, QrCodeEcc.QUARTILE, QrCodeEcc.HIGH];
69
73
 
70
74
  // Fallback undefined options
71
- const fallbackOptions: Required<QrOptions> = {
75
+ const fallbackOptions: RequiredQrOptions = {
72
76
  qrSize: typeof options.qrSize === 'number' ? options.qrSize : DEFAULT_SIZE,
73
77
  className: typeof options.className === 'string' ? options.className : classNameLegacy || '',
74
78
  isShowLogo: !!options.isShowLogo || false,
75
79
  isShowBackground: !!options.isShowBackground || false,
76
- foregroundColor: typeof options.foregroundColor === 'string' ? options.foregroundColor : FOREGROUND_COLOR_DEFAULT,
77
- backgroundColor: typeof options.backgroundColor === 'string' ? options.backgroundColor : BACKGROUND_COLOR_DEFAULT,
80
+ foregroundColor:
81
+ typeof options.foregroundColor === 'string'
82
+ ? options.foregroundColor
83
+ : FOREGROUND_COLOR_DEFAULT,
84
+ backgroundColor:
85
+ typeof options.backgroundColor === 'string'
86
+ ? options.backgroundColor
87
+ : BACKGROUND_COLOR_DEFAULT,
88
+ backgroundPadding:
89
+ typeof options.backgroundPadding === 'number' &&
90
+ Number.isFinite(options.backgroundPadding) &&
91
+ options.backgroundPadding >= 0
92
+ ? options.backgroundPadding
93
+ : BACKGROUND_PADDING,
94
+ backgroundRadius:
95
+ typeof options.backgroundRadius === 'number' &&
96
+ Number.isFinite(options.backgroundRadius) &&
97
+ options.backgroundRadius >= 0
98
+ ? options.backgroundRadius
99
+ : BACKGROUND_RADIUS,
78
100
  logoColor: typeof options.logoColor === 'string' ? options.logoColor : LOGO_COLOR_DEFAULT,
79
101
  suffix: options.suffix ? options.suffix.toString() : '0',
80
102
  logoData: typeof options.logoData === 'string' ? options.logoData : null,
81
- ecc: (typeof options.ecc === 'number' && eccLevels[options.ecc] ? options.ecc : 3),
103
+ ecc: typeof options.ecc === 'number' && eccLevels[options.ecc] ? options.ecc : 3,
82
104
  incTileSize: typeof options.incTileSize === 'number' ? options.incTileSize : INC_TILE_SIZE,
83
105
  };
84
106
 
85
107
  // Code generation
86
- const segments: QrSegment[] = QrSegment.makeSegments(text);
87
- const qrCode: QrCode = QrCode.encodeSegments(segments, eccLevels[fallbackOptions.ecc], 1, 40, -1, true);
108
+ const segments = makeSegments(text);
109
+ const qrCode = encodeSegments(segments, eccLevels[fallbackOptions.ecc], 1, 40, -1, true);
88
110
  const svgCode = convertSegmentsToSvgString(qrCode, fallbackOptions);
89
111
 
90
112
  return svgCode;
91
113
  }
92
-
93
- export default {
94
- createQR
95
- };