632218b6e87ab9969684c9c7b75ef4f367977876
[crowdnode.js/.git] / lib / qr.js
1 "use strict";
2
3 let Qr = module.exports;
4
5 let Fs = require("fs").promises;
6
7 let QrCode = require("qrcode-svg");
8
9 /**
10  * @typedef QrOpts
11  * @property {String} [background]
12  * @property {String} [color]
13  * @property {String} [ecl]
14  * @property {Number} [height]
15  * @property {Number} [indent]
16  * @property {Number} [padding]
17  * @property {"mini" | "micro"} [size]
18  * @property {Number} [width]
19  */
20
21 /**
22  * @param {String} data
23  * @param {QrOpts} opts
24  */
25 Qr._create = function (data, opts) {
26   return new QrCode({
27     content: data,
28     padding: opts?.padding || 4,
29     width: opts?.width || 256,
30     height: opts?.height || 256,
31     color: opts?.color || "#000000",
32     background: opts?.background || "#ffffff",
33     ecl: opts?.ecl || "M",
34   });
35 };
36
37 /**
38  * @type {Object.<String, String>}
39  */
40 let microMap = {
41   // top-left, top-right, bottom-left, bottom-right
42   0b0000: " ",
43   //
44   0b0001: "▗",
45   //
46   0b0010: "▖",
47   //
48   0b0011: "▄",
49   //
50   0b0100: "▝",
51   //
52   0b0101: "▐",
53   //
54   0b0110: "▞",
55   //
56   0b0111: "▟",
57   //
58   0b1000: "▘",
59   //
60   0b1001: "▚",
61   //
62   0b1010: "▌",
63   //
64   0b1011: "▙",
65   //
66   0b1100: "▀",
67   //
68   0b1101: "▜",
69   //
70   0b1110: "▛",
71   //
72   0b1111: "█",
73 };
74
75 /**
76  * @param {String} data
77  * @param {QrOpts} opts
78  */
79 Qr.microAscii = function (data, opts) {
80   let qrcode = Qr._create(data, opts);
81   let indent = opts?.indent ?? 4;
82   let modules = qrcode.qrcode.modules;
83
84   let ascii = ``.padStart(indent, " ");
85   let length = modules.length;
86   for (let y = 0; y < length; y += 2) {
87     for (let x = 0; x < length; x += 2) {
88       let count = 0;
89       // qr codes can be odd numbers
90       if (x >= length) {
91         ascii += microMap[count];
92         continue;
93       }
94       if (modules[x][y]) {
95         count += 8;
96       }
97       if (modules[x][y + 1]) {
98         count += 2;
99       }
100
101       if (x + 1 >= length) {
102         ascii += microMap[count];
103         continue;
104       }
105       if (modules[x + 1][y]) {
106         count += 4;
107       }
108       if (modules[x + 1][y + 1]) {
109         count += 1;
110       }
111       ascii += microMap[count];
112     }
113     ascii += `\n`.padEnd(indent, " ");
114   }
115   return " ".padEnd(indent - 1, " ") + ascii.trim();
116 };
117
118 /**
119  * @param {String} data
120  * @param {QrOpts} opts
121  */
122 Qr.ascii = function (data, opts) {
123   if ("micro" === opts.size) {
124     return Qr.microAscii(data, opts);
125   }
126
127   let qrcode = Qr._create(data, opts);
128   let indent = opts?.indent ?? 4;
129   let modules = qrcode.qrcode.modules;
130
131   let ascii = ``.padStart(indent - 1, " ");
132   let length = modules.length;
133   for (let y = 0; y < length; y += 1) {
134     for (let x = 0; x < length; x += 1) {
135       let block = "  ";
136       if (modules[x][y]) {
137         block = "██";
138       }
139       ascii += block;
140     }
141     ascii += `\n`.padEnd(indent, " ");
142   }
143   return ascii;
144 };
145
146 /**
147  * @param {String} data
148  * @param {QrOpts} opts
149  */
150 Qr.svg = function (data, opts) {
151   let qrcode = Qr._create(data, opts);
152   return qrcode.svg();
153 };
154
155 /**
156  * @param {String} filepath
157  * @param {String} data
158  * @param {QrOpts} opts
159  */
160 Qr.save = async function (filepath, data, opts) {
161   let qrcode = Qr.svg(data, opts);
162   await Fs.writeFile(filepath, qrcode, "utf8");
163 };