qrstylelib 1.0.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/example.js +135 -0
- package/package.json +38 -0
- package/src/core/analyzer.ts +55 -0
- package/src/core/qr/qrcodegen.ts +1032 -0
- package/src/default.ts +27 -0
- package/src/export.ts +56 -0
- package/src/frame-inset.ts +153 -0
- package/src/index.ts +669 -0
- package/src/renderer/shapes.ts +118 -0
- package/src/types.ts +104 -0
- package/test/readability.test.ts +71 -0
- package/test.js +160 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/renderer/shapes.ts
|
|
2
|
+
|
|
3
|
+
// Exporting static path strings.
|
|
4
|
+
// These paths are designed for a 24x24 viewBox.
|
|
5
|
+
// When used in <use>, the viewBox will be preserved, and width/height will handle scaling.
|
|
6
|
+
export const shapes = {
|
|
7
|
+
"inner-eye-classy": {
|
|
8
|
+
viewBox: "0 0 24 24",
|
|
9
|
+
d: "M4 0H20H24V4V20C24 22.2091 22.2091 24 20 24H4H0V20V4C0 1.79086 1.79086 0 4 0Z",
|
|
10
|
+
},
|
|
11
|
+
"inner-eye-dot": {
|
|
12
|
+
viewBox: "0 0 24 24",
|
|
13
|
+
d: "M24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12Z",
|
|
14
|
+
},
|
|
15
|
+
"inner-eye-dots": {
|
|
16
|
+
viewBox: "0 0 24 24",
|
|
17
|
+
d: "M8 4C8 6.20914 6.20914 8 4 8C1.79086 8 0 6.20914 0 4C0 1.79086 1.79086 0 4 0C6.20914 0 8 1.79086 8 4Z M16 4C16 6.20914 14.2091 8 12 8C9.79086 8 8 6.20914 8 4C8 1.79086 9.79086 0 12 0C14.2091 0 16 1.79086 16 4Z M24 4C24 6.20914 22.2091 8 20 8C17.7909 8 16 6.20914 16 4C16 1.79086 17.7909 0 20 0C22.2091 0 24 1.79086 24 4Z M24 12C24 14.2091 22.2091 16 20 16C17.7909 16 16 14.2091 16 12C16 9.79086 17.7909 8 20 8C22.2091 8 24 9.79086 24 12Z M24 20C24 22.2091 22.2091 24 20 24C17.7909 24 16 22.2091 16 20C16 17.7909 17.7909 16 20 16C22.2091 16 24 17.7909 24 20Z M16 20C16 22.2091 14.2091 24 12 24C9.79086 24 8 22.2091 8 20C8 17.7909 9.79086 16 12 16C14.2091 16 16 17.7909 16 20Z M8 20C8 22.2091 6.20914 24 4 24C1.79086 24 0 22.2091 0 20C0 17.7909 1.79086 16 4 16C6.20914 16 8 17.7909 8 20Z M8 12C8 14.2091 6.20914 16 4 16C1.79086 16 0 14.2091 0 12C0 9.79086 1.79086 8 4 8C6.20914 8 8 9.79086 8 12Z M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z",
|
|
18
|
+
},
|
|
19
|
+
"inner-eye-extra-classy": {
|
|
20
|
+
viewBox: "0 0 24 24",
|
|
21
|
+
d: "M8 0H16H24V8V16C24 20.4183 20.4183 24 16 24H8H0V16V8C0 3.58172 3.58172 0 8 0Z",
|
|
22
|
+
},
|
|
23
|
+
"inner-eye-extra-rounded": {
|
|
24
|
+
viewBox: "0 0 24 24",
|
|
25
|
+
d: "M0 8C0 3.58172 3.58172 0 8 0H16C20.4183 0 24 3.58172 24 8V16C24 20.4183 20.4183 24 16 24H8C3.58172 24 0 20.4183 0 16V8Z",
|
|
26
|
+
},
|
|
27
|
+
"inner-eye-rounded": {
|
|
28
|
+
viewBox: "0 0 24 24",
|
|
29
|
+
d: "M0 4C0 1.79086 1.79086 0 4 0H20C22.2091 0 24 1.79086 24 4V20C24 22.2091 22.2091 24 20 24H4C1.79086 24 0 22.2091 0 20V4Z",
|
|
30
|
+
},
|
|
31
|
+
"inner-eye-square": {
|
|
32
|
+
viewBox: "0 0 24 24",
|
|
33
|
+
d: "M0 0H24V24H0V0Z",
|
|
34
|
+
},
|
|
35
|
+
"outer-eye-classy": {
|
|
36
|
+
viewBox: "0 0 24 24",
|
|
37
|
+
d: "M24 0V22C24 23.1046 23.1046 24 22 24H0V2C0 0.895431 0.895431 0 2 0H24ZM3.5 3.5V20.5H20.5V3.5H3.5Z",
|
|
38
|
+
},
|
|
39
|
+
"outer-eye-dot": {
|
|
40
|
+
viewBox: "0 0 24 24",
|
|
41
|
+
d: "M12 0C18.6274 0 24 5.37258 24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0ZM12 3.5C7.30558 3.5 3.5 7.30558 3.5 12C3.5 16.6944 7.30558 20.5 12 20.5C16.6944 20.5 20.5 16.6944 20.5 12C20.5 7.30558 16.6944 3.5 12 3.5Z",
|
|
42
|
+
},
|
|
43
|
+
"outer-eye-dots": {
|
|
44
|
+
viewBox: "0 0 24 24",
|
|
45
|
+
d: "M1.71429 20.5714C2.66106 20.5714 3.42857 21.3389 3.42857 22.2857C3.42857 23.2325 2.66106 24 1.71429 24C0.767512 24 0 23.2325 0 22.2857C0 21.3389 0.767512 20.5714 1.71429 20.5714Z M5.14286 20.5714C6.08963 20.5714 6.85714 21.3389 6.85714 22.2857C6.85714 23.2325 6.08963 24 5.14286 24C4.19608 24 3.42857 23.2325 3.42857 22.2857C3.42857 21.3389 4.19608 20.5714 5.14286 20.5714Z M8.57143 20.5714C9.5182 20.5714 10.2857 21.3389 10.2857 22.2857C10.2857 23.2325 9.5182 24 8.57143 24C7.62466 24 6.85714 23.2325 6.85714 22.2857C6.85714 21.3389 7.62466 20.5714 8.57143 20.5714Z M12 20.5714C12.9468 20.5714 13.7143 21.3389 13.7143 22.2857C13.7143 23.2325 12.9468 24 12 24C11.0532 24 10.2857 23.2325 10.2857 22.2857C10.2857 21.3389 11.0532 20.5714 12 20.5714Z M15.4286 20.5714C16.3753 20.5714 17.1429 21.3389 17.1429 22.2857C17.1429 23.2325 16.3753 24 15.4286 24C14.4818 24 13.7143 23.2325 13.7143 22.2857C13.7143 21.3389 14.4818 20.5714 15.4286 20.5714Z M18.8571 20.5714C19.8039 20.5714 20.5714 21.3389 20.5714 22.2857C20.5714 23.2325 19.8039 24 18.8571 24C17.9104 24 17.1429 23.2325 17.1429 22.2857C17.1429 21.3389 17.9104 20.5714 18.8571 20.5714Z M22.2857 20.5714C23.2325 20.5714 24 21.3389 24 22.2857C24 23.2325 23.2325 24 22.2857 24C21.3389 24 20.5714 23.2325 20.5714 22.2857C20.5714 21.3389 21.3389 20.5714 22.2857 20.5714Z M1.71429 17.1429C2.66106 17.1429 3.42857 17.9104 3.42857 18.8571C3.42857 19.8039 2.66106 20.5714 1.71429 20.5714C0.767512 20.5714 0 19.8039 0 18.8571C0 17.9104 0.767512 17.1429 1.71429 17.1429Z M22.2857 17.1429C23.2325 17.1429 24 17.9104 24 18.8571C24 19.8039 23.2325 20.5714 22.2857 20.5714C21.3389 20.5714 20.5714 19.8039 20.5714 18.8571C20.5714 17.9104 21.3389 17.1429 22.2857 17.1429Z M1.71429 13.7143C2.66106 13.7143 3.42857 14.4818 3.42857 15.4286C3.42857 16.3753 2.66106 17.1429 1.71429 17.1429C0.767512 17.1429 0 16.3753 0 15.4286C0 14.4818 0.767512 13.7143 1.71429 13.7143Z M22.2857 13.7143C23.2325 13.7143 24 14.4818 24 15.4286C24 16.3753 23.2325 17.1429 22.2857 17.1429C21.3389 17.1429 20.5714 16.3753 20.5714 15.4286C20.5714 14.4818 21.3389 13.7143 22.2857 13.7143Z M1.71429 10.2857C2.66106 10.2857 3.42857 11.0532 3.42857 12C3.42857 12.9468 2.66106 13.7143 1.71429 13.7143C0.767512 13.7143 0 12.9468 0 12C0 11.0532 0.767512 10.2857 1.71429 10.2857Z M22.2857 10.2857C23.2325 10.2857 24 11.0532 24 12C24 12.9468 23.2325 13.7143 22.2857 13.7143C21.3389 13.7143 20.5714 12.9468 20.5714 12C20.5714 11.0532 21.3389 10.2857 22.2857 10.2857Z M1.71429 6.85714C2.66106 6.85714 3.42857 7.62466 3.42857 8.57143C3.42857 9.5182 2.66106 10.2857 1.71429 10.2857C0.767512 10.2857 0 9.5182 0 8.57143C0 7.62466 0.767512 6.85714 1.71429 6.85714Z M22.2857 6.85714C23.2325 6.85714 24 7.62466 24 8.57143C24 9.5182 23.2325 10.2857 22.2857 10.2857C21.3389 10.2857 20.5714 9.5182 20.5714 8.57143C20.5714 7.62466 21.3389 6.85714 22.2857 6.85714Z M1.71429 3.42857C2.66106 3.42857 3.42857 4.19608 3.42857 5.14286C3.42857 6.08963 2.66106 6.85714 1.71429 6.85714C0.767512 6.85714 0 6.08963 0 5.14286C0 4.19608 0.767512 3.42857 1.71429 3.42857Z M22.2857 3.42857C23.2325 3.42857 24 4.19608 24 5.14286C24 6.08963 23.2325 6.85714 22.2857 6.85714C21.3389 6.85714 20.5714 6.08963 20.5714 5.14286C20.5714 4.19608 21.3389 3.42857 22.2857 3.42857Z M1.71429 0C2.66106 0 3.42857 0.767512 3.42857 1.71429C3.42857 2.66106 2.66106 3.42857 1.71429 3.42857C0.767512 3.42857 0 2.66106 0 1.71429C0 0.767512 0.767512 0 1.71429 0Z M5.14286 0C6.08963 0 6.85714 0.767512 6.85714 1.71429C6.85714 2.66106 6.08963 3.42857 5.14286 3.42857C4.19608 3.42857 3.42857 2.66106 3.42857 1.71429C3.42857 0.767512 4.19608 0 5.14286 0Z M8.57143 0C9.5182 0 10.2857 0.767512 10.2857 1.71429C10.2857 2.66106 9.5182 3.42857 8.57143 3.42857C7.62466 3.42857 6.85714 2.66106 6.85714 1.71429C6.85714 0.767512 7.62466 0 8.57143 0Z M12 0C12.9468 0 13.7143 0.767512 13.7143 1.71429C13.7143 2.66106 12.9468 3.42857 12 3.42857C11.0532 3.42857 10.2857 2.66106 10.2857 1.71429C10.2857 0.767512 11.0532 0 12 0Z M15.4286 0C16.3753 0 17.1429 0.767512 17.1429 1.71429C17.1429 2.66106 16.3753 3.42857 15.4286 3.42857C14.4818 3.42857 13.7143 2.66106 13.7143 1.71429C13.7143 0.767512 14.4818 0 15.4286 0Z M18.8571 0C19.8039 0 20.5714 0.767512 20.5714 1.71429C20.5714 2.66106 19.8039 3.42857 18.8571 3.42857C17.9104 3.42857 17.1429 2.66106 17.1429 1.71429C17.1429 0.767512 17.9104 0 18.8571 0Z M22.2857 0C23.2325 0 24 0.767512 24 1.71429C24 2.66106 23.2325 3.42857 22.2857 3.42857C21.3389 3.42857 20.5714 2.66106 20.5714 1.71429C20.5714 0.767512 21.3389 0 22.2857 0Z",
|
|
46
|
+
},
|
|
47
|
+
"outer-eye-extra-classy": {
|
|
48
|
+
viewBox: "0 0 24 24",
|
|
49
|
+
d: "M24 0V20C24 22.2091 22.2091 24 20 24H0V4C2.57702e-07 1.79086 1.79086 0 4 0H24ZM3.5 3.5V20.5H20.5V3.5H3.5Z",
|
|
50
|
+
},
|
|
51
|
+
"outer-eye-extra-rounded": {
|
|
52
|
+
viewBox: "0 0 24 24",
|
|
53
|
+
d: "M16 0C20.4183 0 24 3.58172 24 8V16C24 20.4183 20.4183 24 16 24H8C3.58172 24 1.28855e-07 20.4183 0 16V8C0 3.58172 3.58172 1.28851e-07 8 0H16ZM9 3.5C5.96243 3.5 3.5 5.96243 3.5 9V15C3.5 18.0376 5.96243 20.5 9 20.5H15C18.0376 20.5 20.5 18.0376 20.5 15V9C20.5 5.96243 18.0376 3.5 15 3.5H9Z",
|
|
54
|
+
},
|
|
55
|
+
"outer-eye-rounded": {
|
|
56
|
+
viewBox: "0 0 24 24",
|
|
57
|
+
d: "M20 0C22.2091 2.57702e-07 24 1.79086 24 4V20C24 22.2091 22.2091 24 20 24H4C1.79086 24 6.44266e-08 22.2091 0 20V4C2.57702e-07 1.79086 1.79086 6.44266e-08 4 0H20ZM3.5 3.5V20.5H20.5V3.5H3.5Z",
|
|
58
|
+
},
|
|
59
|
+
"outer-eye-square": {
|
|
60
|
+
viewBox: "0 0 24 24",
|
|
61
|
+
d: "M24 24H0V0H24V24ZM3.5 3.5V20.5H20.5V3.5H3.5Z",
|
|
62
|
+
},
|
|
63
|
+
"dots-classy": {
|
|
64
|
+
viewBox: "0 0 24 24",
|
|
65
|
+
d: "M16 4C16 1.79086 17.7909 0 20 0H24V4C24 6.20914 22.2091 8 20 8H16V4Z M4 0C1.79086 0 0 1.79086 0 4V8V24H16H20C22.2091 24 24 22.2091 24 20V16H16V8L8 8V0H4Z",
|
|
66
|
+
},
|
|
67
|
+
"dots-classy-rounded": {
|
|
68
|
+
viewBox: "0 0 24 24",
|
|
69
|
+
d: "M16 4C16 1.79086 17.7909 0 20 0H24V4C24 6.20914 22.2091 8 20 8H16V4Z M8 0C3.58172 5.15422e-07 0 3.58172 0 8V24H16C20.4183 24 24 20.4183 24 16H16V8L8 8V0Z",
|
|
70
|
+
},
|
|
71
|
+
"dots-extra-rounded": {
|
|
72
|
+
viewBox: "0 0 24 24",
|
|
73
|
+
d: "M16 4.00003C16 1.79089 17.7909 2.47955e-05 20 2.47955e-05C22.2092 2.47955e-05 24 1.79089 24 4.00003C24 6.20916 22.2092 8.00003 20 8.00003C17.7909 8.00003 16 6.20916 16 4.00003Z M8 4V8C12.4183 8 16 11.5817 16 16H20C22.2091 16 24 17.7909 24 20C24 22.2091 22.2091 24 20 24H8C3.58172 24 0 20.4183 0 16V4C0 1.79086 1.79086 0 4 0C6.20914 0 8 1.79086 8 4Z",
|
|
74
|
+
},
|
|
75
|
+
dots: {
|
|
76
|
+
viewBox: "0 0 24 24",
|
|
77
|
+
d: "M16 20C16 17.7909 17.7909 16 20 16C22.2091 16 24 17.7909 24 20C24 22.2091 22.2091 24 20 24C17.7909 24 16 22.2091 16 20Z M8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12Z M16 4C16 1.79086 17.7909 0 20 0C22.2091 0 24 1.79086 24 4C24 6.20914 22.2091 8 20 8C17.7909 8 16 6.20914 16 4Z M8 20C8 17.7909 9.79086 16 12 16C14.2091 16 16 17.7909 16 20C16 22.2091 14.2091 24 12 24C9.79086 24 8 22.2091 8 20Z M0 4C0 1.79086 1.79086 0 4 0C6.20914 0 8 1.79086 8 4C8 6.20914 6.20914 8 4 8C1.79086 8 0 6.20914 0 4Z M0 12C0 9.79086 1.79086 8 4 8C6.20914 8 8 9.79086 8 12C8 14.2091 6.20914 16 4 16C1.79086 16 0 14.2091 0 12Z M0 20C0 17.7909 1.79086 16 4 16C6.20914 16 8 17.7909 8 20C8 22.2091 6.20914 24 4 24C1.79086 24 0 22.2091 0 20Z",
|
|
78
|
+
},
|
|
79
|
+
"dots-rounded": {
|
|
80
|
+
viewBox: "0 0 24 24",
|
|
81
|
+
d: "M16 4C16 1.79086 17.7908 0 20 0C22.2091 0 24 1.79086 24 4C24 6.20914 22.2091 8 20 8C17.7908 8 16 6.20914 16 4Z M3.99976 24.0001C1.79062 24.0001 -0.000244141 22.2092 -0.000244141 20.0001L-1.35708e-05 12L-0.000244141 4.00007C-0.000244141 1.79093 1.79062 7.00951e-05 3.99976 7.00951e-05C6.20889 7.00951e-05 7.99976 1.79093 7.99976 4.00007L7.99999 8H12C14.2091 8 16 9.79086 16 12L15.9998 16.0001H19.9998C22.2089 16.0001 23.9998 17.7909 23.9998 20.0001C23.9998 22.2092 22.2089 24.0001 19.9998 24.0001L12 24L3.99976 24.0001Z",
|
|
82
|
+
},
|
|
83
|
+
"dots-square": {
|
|
84
|
+
viewBox: "0 0 24 24",
|
|
85
|
+
d: "M16 0H24V8H16V0Z M24 24V16H16V8L8 8V0H0V8V24H16H24Z",
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const QRShapes = {
|
|
90
|
+
// Inner Eye
|
|
91
|
+
INNER_EYE_CLASSY: "inner-eye-classy",
|
|
92
|
+
INNER_EYE_DOT: "inner-eye-dot",
|
|
93
|
+
INNER_EYE_DOTS: "inner-eye-dots",
|
|
94
|
+
INNER_EYE_EXTRA_CLASSY: "inner-eye-extra-classy",
|
|
95
|
+
INNER_EYE_EXTRA_ROUNDED: "inner-eye-extra-rounded",
|
|
96
|
+
INNER_EYE_ROUNDED: "inner-eye-rounded",
|
|
97
|
+
INNER_EYE_SQUARE: "inner-eye-square",
|
|
98
|
+
|
|
99
|
+
// Outer Eye
|
|
100
|
+
OUTER_EYE_CLASSY: "outer-eye-classy",
|
|
101
|
+
OUTER_EYE_DOT: "outer-eye-dot",
|
|
102
|
+
OUTER_EYE_DOTS: "outer-eye-dots",
|
|
103
|
+
OUTER_EYE_EXTRA_CLASSY: "outer-eye-extra-classy",
|
|
104
|
+
OUTER_EYE_EXTRA_ROUNDED: "outer-eye-extra-rounded",
|
|
105
|
+
OUTER_EYE_ROUNDED: "outer-eye-rounded",
|
|
106
|
+
OUTER_EYE_SQUARE: "outer-eye-square",
|
|
107
|
+
|
|
108
|
+
// Dots
|
|
109
|
+
DOTS_CLASSY: "dots-classy",
|
|
110
|
+
DOTS_CLASSY_ROUNDED: "dots-classy-rounded",
|
|
111
|
+
DOTS_EXTRA_ROUNDED: "dots-extra-rounded",
|
|
112
|
+
DOTS: "dots",
|
|
113
|
+
DOTS_ROUNDED: "dots-rounded",
|
|
114
|
+
DOTS_SQUARE: "dots-square",
|
|
115
|
+
|
|
116
|
+
// Custom
|
|
117
|
+
CUSTOM_ICON: "custom-icon",
|
|
118
|
+
} as const;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { QRShapes, shapes } from "./renderer/shapes";
|
|
2
|
+
|
|
3
|
+
export type QRErrorCorrectionLevel = "L" | "M" | "Q" | "H";
|
|
4
|
+
|
|
5
|
+
export type ModuleType =
|
|
6
|
+
| "data"
|
|
7
|
+
| "pos-finder"
|
|
8
|
+
| "pos-separator"
|
|
9
|
+
| "alignment"
|
|
10
|
+
| "timing"
|
|
11
|
+
| "dark-module"
|
|
12
|
+
| "version";
|
|
13
|
+
|
|
14
|
+
export interface QRCell {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
isDark: boolean;
|
|
18
|
+
type: ModuleType;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type QRMatrix = QRCell[][];
|
|
22
|
+
|
|
23
|
+
export type TypeNumber = number;
|
|
24
|
+
|
|
25
|
+
// Allowed shapes + custom-icon support
|
|
26
|
+
export type QRShapesType = keyof typeof shapes | "custom-icon";
|
|
27
|
+
|
|
28
|
+
// QR Generation specific types (mapped to Nayuki's library later)
|
|
29
|
+
export type ErrorCorrectionLevel = "L" | "M" | "Q" | "H";
|
|
30
|
+
export type Mode = "Numeric" | "Alphanumeric" | "Byte" | "Kanji";
|
|
31
|
+
export type Gradient = {
|
|
32
|
+
type: "linear" | "radial";
|
|
33
|
+
rotation?: number; // у градусах, для linear
|
|
34
|
+
colorStops: {
|
|
35
|
+
offset: string; // "0%", "100%"
|
|
36
|
+
color: string;
|
|
37
|
+
}[];
|
|
38
|
+
};
|
|
39
|
+
// Logo/Image configuration
|
|
40
|
+
export type QrImage = {
|
|
41
|
+
source: string; // url or base64
|
|
42
|
+
width: number; // size in modules (e.g., 0.5 or 1 or 5)
|
|
43
|
+
height: number;
|
|
44
|
+
x?: number; // Position in modules. If omitted, calculated automatically (center)
|
|
45
|
+
y?: number;
|
|
46
|
+
excludeDots?: boolean; // If true, we won't draw QR dots under this image
|
|
47
|
+
opacity?: number; // 0.0 – 1.0 (default 1)
|
|
48
|
+
preserveAspectRatio?: string; // SVG preserveAspectRatio attr (default "xMidYMid meet")
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type QrPart = {
|
|
52
|
+
type?: QRShapesType;
|
|
53
|
+
color?: string;
|
|
54
|
+
gradient?: Gradient;
|
|
55
|
+
customPath?: string;
|
|
56
|
+
customViewBox?: string;
|
|
57
|
+
scale?: number; // 0.1 ... 1.0 (Density)
|
|
58
|
+
isSingle?: boolean; // Тільки для innerEye/cornersDot: малювати один великий елемент замість 3х3
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Frame: decorative image that wraps the QR code
|
|
62
|
+
export type QrFrame = {
|
|
63
|
+
source: string; // URL or base64 of the frame image
|
|
64
|
+
width: number; // Total frame width in output pixels
|
|
65
|
+
height: number; // Total frame height in output pixels
|
|
66
|
+
// Where the QR sits inside the frame (in output pixels).
|
|
67
|
+
// If omitted entirely, QR is centered with 10% margin on each side.
|
|
68
|
+
// If only width/height are given (no x/y), QR is auto-centered.
|
|
69
|
+
inset?: {
|
|
70
|
+
x?: number; // Left edge of QR area (auto-centered if omitted)
|
|
71
|
+
y?: number; // Top edge of QR area (auto-centered if omitted)
|
|
72
|
+
width: number; // Width of QR area inside frame
|
|
73
|
+
height: number; // Height of QR area inside frame
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// The Main Config
|
|
78
|
+
export interface Options {
|
|
79
|
+
data: string;
|
|
80
|
+
width?: number; // Output width in pixels (e.g. 1000)
|
|
81
|
+
height?: number; // Output height in pixels
|
|
82
|
+
margin?: number; // Padding in modules (default 4)
|
|
83
|
+
borderRadius?: number; // Corner radius in pixels for the entire QR code
|
|
84
|
+
|
|
85
|
+
backgroundOptions?: {
|
|
86
|
+
color?: string;
|
|
87
|
+
gradient?: Gradient;
|
|
88
|
+
image?: string; // URL for background image
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
images?: QrImage[]; // Logos (Array)
|
|
92
|
+
frame?: QrFrame; // Decorative frame around the QR code
|
|
93
|
+
|
|
94
|
+
qrOptions?: {
|
|
95
|
+
typeNumber?: TypeNumber;
|
|
96
|
+
mode?: Mode;
|
|
97
|
+
errorCorrectionLevel?: ErrorCorrectionLevel;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Granular styling
|
|
101
|
+
dotsOptions?: QrPart; // The main data
|
|
102
|
+
cornersDotOptions?: QrPart; // Inner Eye (Ball)
|
|
103
|
+
cornersSquareOptions?: QrPart; // Outer Eye (Frame)
|
|
104
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { QRCodeGenerate } from "../src/index";
|
|
3
|
+
import sharp from "sharp";
|
|
4
|
+
import jsQR from "jsqr";
|
|
5
|
+
|
|
6
|
+
// Helper: Scans SVG string
|
|
7
|
+
async function scanSVG(svgString: string) {
|
|
8
|
+
// 1. Convert SVG to "raw" pixel buffer (Raw RGBA)
|
|
9
|
+
const { data, info } = await sharp(Buffer.from(svgString))
|
|
10
|
+
.ensureAlpha() // Ensure there's an alpha channel
|
|
11
|
+
.raw() // Get byte array [R, G, B, A, R, G, B, A...]
|
|
12
|
+
.toBuffer({ resolveWithObject: true });
|
|
13
|
+
|
|
14
|
+
// 2. Use jsQR library for reading
|
|
15
|
+
const code = jsQR(new Uint8ClampedArray(data), info.width, info.height);
|
|
16
|
+
|
|
17
|
+
return code ? code.data : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("QR Reliability Test", () => {
|
|
21
|
+
it("should be readable with hearts shape", async () => {
|
|
22
|
+
const text = "https://github.com/my-lib";
|
|
23
|
+
|
|
24
|
+
const svg = QRCodeGenerate({
|
|
25
|
+
text: text,
|
|
26
|
+
shape: "heart", // Testing our hearts
|
|
27
|
+
color: "#000000",
|
|
28
|
+
bgColor: "#FFFFFF",
|
|
29
|
+
padding: 4,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const scannedText = await scanSVG(svg);
|
|
33
|
+
|
|
34
|
+
// Check: does what we scanned equal what we wrote
|
|
35
|
+
expect(scannedText).toBe(text);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should be readable with custom W icon and High Error Correction", async () => {
|
|
39
|
+
const text = "https://wiki.org";
|
|
40
|
+
|
|
41
|
+
const path1 =
|
|
42
|
+
"M15.029 34.623c.521.266.399 1.034-.178 1.143a12.592 12.592 0 0 1-6.551-.472C3.466 33.608 0 29.065 0 23.719V13.291c0-1.482 1.012-2.783 2.466-3.17l5.055-1.336a.623.623 0 0 1 .785.592v14.335c0 4.742 2.73 8.86 6.729 10.905l-.006.006ZM24.905 35.295a12.52 12.52 0 0 0 4.153-2.431c2.546-2.244 4.147-5.516 4.147-9.152V4.521c0-1.488 1.012-2.782 2.466-3.17l5.054-1.33a.623.623 0 0 1 .786.593v23.104c0 5.347-3.466 9.89-8.3 11.577-1.3.454-2.693.702-4.153.702-1.46 0-2.852-.248-4.147-.696l-.006-.006Z"; // ...shortened
|
|
43
|
+
const path2 =
|
|
44
|
+
"M24.906 35.295a12.548 12.548 0 0 1-4.153-2.425c-2.545-2.244-4.152-5.517-4.152-9.152V8.906c0-1.482 1.012-2.782 2.466-3.17L24.12 4.4a.623.623 0 0 1 .785.592v18.72c0 3.635 1.608 6.901 4.153 9.152a12.564 12.564 0 0 1-4.153 2.431Z"; // ...shortened
|
|
45
|
+
const fullWLogo = path1 + " " + path2; // Just concatenating
|
|
46
|
+
|
|
47
|
+
const svg = QRCodeGenerate({
|
|
48
|
+
text: text,
|
|
49
|
+
shape: "custom-icon",
|
|
50
|
+
color: "#000000",
|
|
51
|
+
bgColor: "#FFFFFF",
|
|
52
|
+
padding: 2,
|
|
53
|
+
customIconPath: fullWLogo,
|
|
54
|
+
customIconViewBox: "0 0 41 36",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const scannedText = await scanSVG(svg);
|
|
58
|
+
expect(scannedText).toBe(text);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("fails on low contrast", async () => {
|
|
62
|
+
const svg = QRCodeGenerate({
|
|
63
|
+
text: "fail",
|
|
64
|
+
color: "#FFFFFF", // White on white
|
|
65
|
+
bgColor: "#FFFFFF",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const scannedText = await scanSVG(svg);
|
|
69
|
+
expect(scannedText).toBeNull(); // Should not be readable
|
|
70
|
+
});
|
|
71
|
+
});
|
package/test.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { QRCodeGenerate } from "./dist/index.js";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
function fileToDataURI(relativePath) {
|
|
6
|
+
const absolutePath = path.resolve(relativePath);
|
|
7
|
+
const ext = path.extname(absolutePath).slice(1).toLowerCase();
|
|
8
|
+
const mimeMap = {
|
|
9
|
+
png: "image/png",
|
|
10
|
+
jpg: "image/jpeg",
|
|
11
|
+
jpeg: "image/jpeg",
|
|
12
|
+
gif: "image/gif",
|
|
13
|
+
svg: "image/svg+xml",
|
|
14
|
+
webp: "image/webp",
|
|
15
|
+
};
|
|
16
|
+
const mime = mimeMap[ext] || "application/octet-stream";
|
|
17
|
+
const data = fs.readFileSync(absolutePath).toString("base64");
|
|
18
|
+
return `data:${mime};base64,${data}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const placeholderImg = fileToDataURI("assets/placeholder.png");
|
|
22
|
+
const frameImg = fileToDataURI("assets/frames/frame.png");
|
|
23
|
+
const bg1Img = fileToDataURI("assets/bg/bg1.jpg");
|
|
24
|
+
|
|
25
|
+
const path1 =
|
|
26
|
+
"M15.029 34.623c.521.266.399 1.034-.178 1.143a12.592 12.592 0 0 1-6.551-.472C3.466 33.608 0 29.065 0 23.719V13.291c0-1.482 1.012-2.783 2.466-3.17l5.055-1.336a.623.623 0 0 1 .785.592v14.335c0 4.742 2.73 8.86 6.729 10.905l-.006.006ZM24.905 35.295a12.52 12.52 0 0 0 4.153-2.431c2.546-2.244 4.147-5.516 4.147-9.152V4.521c0-1.488 1.012-2.782 2.466-3.17l5.054-1.33a.623.623 0 0 1 .786.593v23.104c0 5.347-3.466 9.89-8.3 11.577-1.3.454-2.693.702-4.153.702-1.46 0-2.852-.248-4.147-.696l-.006-.006Z"; // ...shortened
|
|
27
|
+
const path2 =
|
|
28
|
+
"M24.906 35.295a12.548 12.548 0 0 1-4.153-2.425c-2.545-2.244-4.152-5.517-4.152-9.152V8.906c0-1.482 1.012-2.782 2.466-3.17L24.12 4.4a.623.623 0 0 1 .785.592v18.72c0 3.635 1.608 6.901 4.153 9.152a12.564 12.564 0 0 1-4.153 2.431Z"; // ...shortened
|
|
29
|
+
const fullWLogo = path1 + " " + path2; // Just concatenating
|
|
30
|
+
|
|
31
|
+
(async () => {
|
|
32
|
+
const {
|
|
33
|
+
svg: svgWithFrame,
|
|
34
|
+
matrixSize,
|
|
35
|
+
eyeZones,
|
|
36
|
+
getMaxPos,
|
|
37
|
+
} = await QRCodeGenerate({
|
|
38
|
+
data: "https://wiki.org",
|
|
39
|
+
// margin: 1,
|
|
40
|
+
// width: 500,
|
|
41
|
+
// height: 500,
|
|
42
|
+
// borderRadius: 10,
|
|
43
|
+
// frame: {
|
|
44
|
+
// source: frameImg,
|
|
45
|
+
// width: 1000,
|
|
46
|
+
// height: 1000,
|
|
47
|
+
// inset: { width: 500, height: 500 },
|
|
48
|
+
// },
|
|
49
|
+
// // --- Background ---
|
|
50
|
+
// background: {
|
|
51
|
+
// // Dark background so white dots are visible
|
|
52
|
+
// color: "#1a1a2e",
|
|
53
|
+
// // Uncomment to use a full-bleed background image instead:
|
|
54
|
+
// image: bg1Img,
|
|
55
|
+
// // gradient: {
|
|
56
|
+
// // type: "linear",
|
|
57
|
+
// // rotation: 90,
|
|
58
|
+
// // colorStops: [
|
|
59
|
+
// // { offset: "0%", color: "#021ffa" },
|
|
60
|
+
// // { offset: "100%", color: "#ed0909" },
|
|
61
|
+
// // ],
|
|
62
|
+
// // },
|
|
63
|
+
// },
|
|
64
|
+
|
|
65
|
+
// // --- Images overlaid INSIDE the QR code (logos, icons, etc.) ---
|
|
66
|
+
// // Multiple images are supported. Use excludeDots:true to clear dots underneath.
|
|
67
|
+
// // Coordinates are in QR modules (same unit as the matrix grid).
|
|
68
|
+
// images: [
|
|
69
|
+
// {
|
|
70
|
+
// // Center logo — inline SVG data URI (works without network)
|
|
71
|
+
// // A simple WiFi icon as SVG data URI
|
|
72
|
+
// source:
|
|
73
|
+
// "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%2316213e'/%3E%3Cpath d='M12 15.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zm0-4a5.5 5.5 0 0 1 4.95 3.07l-1.6 1.6A3.5 3.5 0 0 0 12 14a3.5 3.5 0 0 0-3.35 2.17l-1.6-1.6A5.5 5.5 0 0 1 12 11.5zm0-4a9.5 9.5 0 0 1 8.49 5.24l-1.6 1.6A7.5 7.5 0 0 0 12 10a7.5 7.5 0 0 0-6.89 4.34l-1.6-1.6A9.5 9.5 0 0 1 12 7.5z' fill='%2300d4ff'/%3E%3C/svg%3E",
|
|
74
|
+
// width: 5,
|
|
75
|
+
// height: 5,
|
|
76
|
+
// excludeDots: true,
|
|
77
|
+
// },
|
|
78
|
+
// // Second image — also inline SVG (a small star badge)
|
|
79
|
+
// {
|
|
80
|
+
// source:
|
|
81
|
+
// "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%23edf505'/%3E%3Cpolygon points='12,4 14.5,9.5 21,10.3 16.5,14.6 17.8,21 12,17.8 6.2,21 7.5,14.6 3,10.3 9.5,9.5' fill='%231a1a2e'/%3E%3C/svg%3E",
|
|
82
|
+
// width: 3,
|
|
83
|
+
// height: 3,
|
|
84
|
+
// excludeDots: true,
|
|
85
|
+
// },
|
|
86
|
+
// {
|
|
87
|
+
// source:
|
|
88
|
+
// "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%23edf505'/%3E%3Cpolygon points='12,4 14.5,9.5 21,10.3 16.5,14.6 17.8,21 12,17.8 6.2,21 7.5,14.6 3,10.3 9.5,9.5' fill='%231a1a2e'/%3E%3C/svg%3E",
|
|
89
|
+
// width: 3,
|
|
90
|
+
// height: 3,
|
|
91
|
+
// excludeDots: true,
|
|
92
|
+
// },
|
|
93
|
+
// {
|
|
94
|
+
// source:
|
|
95
|
+
// "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%23edf505'/%3E%3Cpolygon points='12,4 14.5,9.5 21,10.3 16.5,14.6 17.8,21 12,17.8 6.2,21 7.5,14.6 3,10.3 9.5,9.5' fill='%231a1a2e'/%3E%3C/svg%3E",
|
|
96
|
+
// width: 3,
|
|
97
|
+
// height: 3,
|
|
98
|
+
// excludeDots: true,
|
|
99
|
+
// },
|
|
100
|
+
// {
|
|
101
|
+
// // Center logo — inline SVG data URI (works without network)
|
|
102
|
+
// // A simple WiFi icon as SVG data URI
|
|
103
|
+
// source:
|
|
104
|
+
// "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%2316213e'/%3E%3Cpath d='M12 15.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zm0-4a5.5 5.5 0 0 1 4.95 3.07l-1.6 1.6A3.5 3.5 0 0 0 12 14a3.5 3.5 0 0 0-3.35 2.17l-1.6-1.6A5.5 5.5 0 0 1 12 11.5zm0-4a9.5 9.5 0 0 1 8.49 5.24l-1.6 1.6A7.5 7.5 0 0 0 12 10a7.5 7.5 0 0 0-6.89 4.34l-1.6-1.6A9.5 9.5 0 0 1 12 7.5z' fill='%2300d4ff'/%3E%3C/svg%3E",
|
|
105
|
+
// width: 5,
|
|
106
|
+
// height: 5,
|
|
107
|
+
// x: 18,
|
|
108
|
+
// y: 20,
|
|
109
|
+
// excludeDots: true,
|
|
110
|
+
// },
|
|
111
|
+
// ],
|
|
112
|
+
|
|
113
|
+
// // 1. DOTS — use a simple square shape with visible color
|
|
114
|
+
// dotsOptions: {
|
|
115
|
+
// // shape: "square",
|
|
116
|
+
// color: "#e0e0e0",
|
|
117
|
+
// scale: 0.85,
|
|
118
|
+
// // To use custom-icon shape, uncomment below and comment out shape/color above:
|
|
119
|
+
// shape: "qr-duck",
|
|
120
|
+
// // color: "white",
|
|
121
|
+
// },
|
|
122
|
+
|
|
123
|
+
// // 2. CORNER SQUARE (outer eye frame)
|
|
124
|
+
// cornersSquareOptions: {
|
|
125
|
+
// shape: "qr-duck",
|
|
126
|
+
// color: "black",
|
|
127
|
+
// gradient: {
|
|
128
|
+
// type: "linear",
|
|
129
|
+
// rotation: 180,
|
|
130
|
+
// colorStops: [
|
|
131
|
+
// { offset: "0%", color: "#EEAECA" },
|
|
132
|
+
// { offset: "100%", color: "#00D4FF" },
|
|
133
|
+
// ],
|
|
134
|
+
// },
|
|
135
|
+
// },
|
|
136
|
+
|
|
137
|
+
// // 3. CORNER DOT (inner eye ball)
|
|
138
|
+
// cornersDotOptions: {
|
|
139
|
+
// shape: "",
|
|
140
|
+
// // customIconPath: fullWLogo,
|
|
141
|
+
// // customIconViewBox: "0 0 41 36",
|
|
142
|
+
// gradient: {
|
|
143
|
+
// type: "linear",
|
|
144
|
+
// rotation: 90,
|
|
145
|
+
// colorStops: [
|
|
146
|
+
// { offset: "0%", color: "#edf505" },
|
|
147
|
+
// { offset: "100%", color: "#1aebd9" },
|
|
148
|
+
// ],
|
|
149
|
+
// },
|
|
150
|
+
// isSingle: true,
|
|
151
|
+
// scale: 1.2,
|
|
152
|
+
// },
|
|
153
|
+
});
|
|
154
|
+
fs.writeFileSync("output-frame.svg", svgWithFrame);
|
|
155
|
+
console.log(
|
|
156
|
+
`Frame demo written to output-frame.svg (matrixSize: ${matrixSize})`,
|
|
157
|
+
);
|
|
158
|
+
console.log("Eye zones:", JSON.stringify(eyeZones));
|
|
159
|
+
console.log("Max pos for 5x5 image:", getMaxPos(5, 5));
|
|
160
|
+
})();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ESNext", "DOM"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"moduleResolution": "node",
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"],
|
|
14
|
+
"exclude": ["node_modules", "**/*.test.ts"]
|
|
15
|
+
}
|