Compare commits
No commits in common. "ae6e87aadc804020ff90ddb4ab37514dcf32fb24" and "adaec81731ce28758f74c2fb0a99d594c173baef" have entirely different histories.
ae6e87aadc
...
adaec81731
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
config.json
|
|
||||||
out/
|
|
292
gen.go
292
gen.go
@ -1,292 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"image/png"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode"
|
|
||||||
"github.com/boombuler/barcode/qr"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateKeys() (string, string) {
|
|
||||||
cmd := exec.Command("wg", "genkey")
|
|
||||||
var out1 bytes.Buffer
|
|
||||||
cmd.Stdout = &out1
|
|
||||||
cmd.Run()
|
|
||||||
|
|
||||||
private := strings.ReplaceAll(out1.String(), "\n", "")
|
|
||||||
|
|
||||||
cmd2 := exec.Command("wg", "pubkey")
|
|
||||||
cmd2.Stdin = strings.NewReader(private)
|
|
||||||
|
|
||||||
var out2 bytes.Buffer
|
|
||||||
cmd2.Stdout = &out2
|
|
||||||
|
|
||||||
cmd2.Run()
|
|
||||||
public := strings.ReplaceAll(out2.String(), "\n", "")
|
|
||||||
|
|
||||||
return private, public
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateConfigs(storage *Storage) {
|
|
||||||
os.MkdirAll("out/server", 0755)
|
|
||||||
os.MkdirAll("out/clients", 0755)
|
|
||||||
|
|
||||||
serverConfig := ""
|
|
||||||
serverConfig += "[Interface]\n"
|
|
||||||
serverConfig += fmt.Sprintf("ListenPort = %d\n", storage.Port)
|
|
||||||
serverConfig += fmt.Sprintf("Address = %s1/32,%s1/128\n", storage.Subnet, storage.Subnet6)
|
|
||||||
serverConfig += fmt.Sprintf("PrivateKey = %s\n", storage.Server.Private)
|
|
||||||
|
|
||||||
if len(storage.Server.IPTablesUp) > 0 {
|
|
||||||
serverConfig += fmt.Sprintf("PostUp = %s\n", strings.Join(storage.Server.IPTablesUp, "; "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(storage.Server.IPTablesDown) > 0 {
|
|
||||||
serverConfig += fmt.Sprintf("PostDown = %s\n", strings.Join(storage.Server.IPTablesDown, "; "))
|
|
||||||
}
|
|
||||||
|
|
||||||
serverConfig += "# ---------------------------\n\n"
|
|
||||||
|
|
||||||
for name, peer := range storage.Clients {
|
|
||||||
serverConfig += fmt.Sprintf("[Peer] # %s\n", name)
|
|
||||||
serverConfig += fmt.Sprintf("PublicKey = %s\n", peer.Public)
|
|
||||||
serverConfig += fmt.Sprintf("AllowedIPs = %s%d/32,%s%d/128\n", storage.Subnet, peer.IP, storage.Subnet6, peer.IP)
|
|
||||||
|
|
||||||
// serverConfig += fmt.Sprintf("Endpoint = %s:%d\n", storage.Server.EndPoint, storage.Port)
|
|
||||||
serverConfig += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
os.WriteFile("out/server/server.conf", []byte(serverConfig), 0644)
|
|
||||||
|
|
||||||
for name, peer := range storage.Clients {
|
|
||||||
peerConfig := "[Interface]\n"
|
|
||||||
peerConfig += fmt.Sprintf("Address = %s%d/32,%s%d/128\n", storage.Subnet, peer.IP, storage.Subnet6, peer.IP)
|
|
||||||
peerConfig += fmt.Sprintf("PrivateKey = %s\n", peer.Private)
|
|
||||||
peerConfig += fmt.Sprintf("DNS = 1.1.1.1\n")
|
|
||||||
peerConfig += "\n"
|
|
||||||
peerConfig += fmt.Sprintf("[Peer] # Server\n")
|
|
||||||
peerConfig += fmt.Sprintf("PublicKey = %s\n", storage.Server.Public)
|
|
||||||
if !peer.OnlySubnet {
|
|
||||||
peerConfig += fmt.Sprintf("AllowedIPs = 0.0.0.0/0, ::0/0\n")
|
|
||||||
} else {
|
|
||||||
peerConfig += fmt.Sprintf("AllowedIPs = %s0/24, %s0/68\n", storage.Subnet, storage.Subnet6)
|
|
||||||
}
|
|
||||||
peerConfig += fmt.Sprintf("Endpoint = %s:%d\n", storage.Server.EndPoint, storage.Port)
|
|
||||||
|
|
||||||
os.WriteFile(fmt.Sprintf("out/clients/%s.conf", name), []byte(peerConfig), 0644)
|
|
||||||
qrCode, _ := qr.Encode(peerConfig, qr.L, qr.Auto)
|
|
||||||
qrCode, _ = barcode.Scale(qrCode, 512, 512)
|
|
||||||
|
|
||||||
file, err := os.Create(fmt.Sprintf("out/clients/%s.png", name))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
png.Encode(file, qrCode)
|
|
||||||
|
|
||||||
file.Sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func initConfig(storage *Storage) {
|
|
||||||
var text string
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
|
||||||
fmt.Printf("Enter IPv4 subnet (%s): ", storage.Subnet)
|
|
||||||
scanner.Scan()
|
|
||||||
text = scanner.Text()
|
|
||||||
if len(text) > 0 {
|
|
||||||
storage.Subnet = text
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Enter IPv6 subnet (%s): ", storage.Subnet6)
|
|
||||||
scanner.Scan()
|
|
||||||
text = scanner.Text()
|
|
||||||
if len(text) > 0 {
|
|
||||||
storage.Subnet6 = text
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Enter Server Endpoint (%s): ", storage.Server.EndPoint)
|
|
||||||
scanner.Scan()
|
|
||||||
text = scanner.Text()
|
|
||||||
if len(text) > 0 {
|
|
||||||
storage.Server.EndPoint = text
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Enter Port (%d): ", storage.Port)
|
|
||||||
scanner.Scan()
|
|
||||||
text = scanner.Text()
|
|
||||||
if len(text) > 0 {
|
|
||||||
u, err := strconv.ParseUint(text, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
storage.Port = uint32(u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if !fileExists("config.json") {
|
|
||||||
err := ioutil.WriteFile("config.json", []byte("{}"), os.FileMode(0750))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Print(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile("config.json")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
storage := new(Storage)
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &storage)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if storage.Server.Private == "" || storage.Server.Public == "" {
|
|
||||||
pr, pu := generateKeys()
|
|
||||||
storage.Server.Private = pr
|
|
||||||
storage.Server.Public = pu
|
|
||||||
}
|
|
||||||
|
|
||||||
for storage.Subnet == "" || storage.Subnet6 == "" || storage.Server.EndPoint == "" {
|
|
||||||
initConfig(storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
if storage.Clients == nil {
|
|
||||||
storage.Clients = map[string]Client{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if storage.Server.IPTablesUp == nil {
|
|
||||||
storage.Server.IPTablesUp = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if storage.Server.IPTablesDown == nil {
|
|
||||||
storage.Server.IPTablesDown = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt.Printf("%+v\n", storage)
|
|
||||||
|
|
||||||
app := &cli.App{
|
|
||||||
Name: "wgconf",
|
|
||||||
Usage: "make an explosive entrance",
|
|
||||||
Version: "0.0.1",
|
|
||||||
Commands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "generate",
|
|
||||||
Aliases: []string{"g"},
|
|
||||||
Usage: "Generate the wireguard configs",
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
generateConfigs(storage)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Name: "add",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Add device",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "onlysubnet",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Only VPN subnet, not whole traffic",
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
name := c.Args().First()
|
|
||||||
if len(name) <= 0 {
|
|
||||||
log.Fatalln("Name not set!")
|
|
||||||
}
|
|
||||||
|
|
||||||
freeIP := 1
|
|
||||||
|
|
||||||
for _, peer := range storage.Clients {
|
|
||||||
if peer.IP >= freeIP {
|
|
||||||
freeIP = peer.IP + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private, public := generateKeys()
|
|
||||||
|
|
||||||
storage.Clients[name] = Client{
|
|
||||||
OnlySubnet: c.Bool("onlysubnet"),
|
|
||||||
Private: private,
|
|
||||||
Public: public,
|
|
||||||
IP: freeIP,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
fmt.Println("boom! I say!")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = app.Run(os.Args)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err = json.MarshalIndent(storage, "", " ")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile("config.json", data, 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileExists(filename string) bool {
|
|
||||||
info, err := os.Stat(filename)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !info.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
IP int `json:"ip"`
|
|
||||||
Private string `json:"private"`
|
|
||||||
Public string `json:"public"`
|
|
||||||
OnlySubnet bool `json:"onlysubnet"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
EndPoint string `json:"endpoint"`
|
|
||||||
Private string `json:"private"`
|
|
||||||
Public string `json:"public"`
|
|
||||||
IPTablesUp []string `json:"iptables-up"`
|
|
||||||
IPTablesDown []string `json:"iptables-down"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Storage struct {
|
|
||||||
Subnet string `json:"subnet"`
|
|
||||||
Port uint32 `json:"port"`
|
|
||||||
Server Server `json:"server"`
|
|
||||||
Clients map[string]Client `json:"clients"`
|
|
||||||
Subnet6 string `json:"subnet6"`
|
|
||||||
}
|
|
190
gen.js
Normal file
190
gen.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#!node
|
||||||
|
const fs = require("fs");
|
||||||
|
const p = require("path");
|
||||||
|
const qrimage = require("./qr/qr");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
|
||||||
|
const deleteRec = (path) => {
|
||||||
|
path = p.resolve(path);
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
if (fs.statSync(path).isDirectory()) {
|
||||||
|
fs.readdirSync(path).forEach(entry => deleteRec(p.join(path, entry)));
|
||||||
|
fs.rmdirSync(path);
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = fs.existsSync("config.json") ? JSON.parse(fs.readFileSync("config.json").toString()) : {};
|
||||||
|
|
||||||
|
const child = require("child_process");
|
||||||
|
const getPrivate = () => {
|
||||||
|
return child.execSync("wg genkey").toString().replace("\n", "").replace("\r", "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPublic = (private) => {
|
||||||
|
return child.execSync(`echo ${private} | wg pubkey`).toString().replace("\n", "").replace("\r", "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyPair = () => {
|
||||||
|
const private = getPrivate();
|
||||||
|
const public = getPublic(private);
|
||||||
|
return { private, public };
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientArray = (clients) => Object.keys(clients).map(key => ({ ...clients[key], name: key }))
|
||||||
|
|
||||||
|
const ipTablesPostUP = [
|
||||||
|
"iptables -A FORWARD -i %i -j ACCEPT",
|
||||||
|
"iptables -A FORWARD -o %i -j ACCEPT",
|
||||||
|
"iptables -t nat -A POSTROUTING -o %i -j MASQUERADE",
|
||||||
|
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE",
|
||||||
|
"iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE"
|
||||||
|
].join("; ");
|
||||||
|
|
||||||
|
const ipTablesPostDOWN = [
|
||||||
|
"iptables -D FORWARD -i %i -j ACCEPT",
|
||||||
|
"iptables -D FORWARD -o %i -j ACCEPT",
|
||||||
|
"iptables -t nat -D POSTROUTING -o %i -j MASQUERADE",
|
||||||
|
"iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE",
|
||||||
|
"iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE"
|
||||||
|
].join("; ");
|
||||||
|
|
||||||
|
function makeServer({ port, subnet, subnet6, server, clients }) {
|
||||||
|
const base = `[Interface] #Server
|
||||||
|
ListenPort = ${port}
|
||||||
|
Address = ${subnet}1/32,${subnet6}1/48
|
||||||
|
PrivateKey = ${server.private}
|
||||||
|
PostUp = ${ipTablesPostUP}
|
||||||
|
PostDown = ${ipTablesPostDOWN}
|
||||||
|
# ---------------------------
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
let peers = clientArray(clients).map(client => {
|
||||||
|
return `[Peer] #${client.name}
|
||||||
|
PublicKey = ${client.public}
|
||||||
|
AllowedIPs = ${subnet}${client.ip}/32,${subnet6}${client.ip}/128
|
||||||
|
` + (client.endpoint ? "\nEndpoint = " + client.endpoint + ":" + port : "");
|
||||||
|
}).join("\n\n");
|
||||||
|
|
||||||
|
fs.writeFileSync("out/server/server.conf", base + peers);
|
||||||
|
fs.writeFileSync("out/server/server.public", server.public);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeClients({ port, subnet, subnet6, server, clients }) {
|
||||||
|
clientArray(clients).map(client => {
|
||||||
|
const config = `[Interface]
|
||||||
|
ListenPort = ${port}
|
||||||
|
Address = ${subnet}${client.ip}/32, ${subnet6}${client.ip}/128
|
||||||
|
PrivateKey = ${client.private}
|
||||||
|
DNS = 1.1.1.1
|
||||||
|
|
||||||
|
[Peer] #Server
|
||||||
|
PublicKey = ${server.public}
|
||||||
|
AllowedIPs = 0.0.0.0/0, ::0/0
|
||||||
|
Endpoint = ${server.endpoint}:${port}
|
||||||
|
`
|
||||||
|
|
||||||
|
fs.writeFileSync(`out/clients/${client.name}.conf`, config);
|
||||||
|
fs.writeFileSync(`out/clients/${client.name}.public`, client.public);
|
||||||
|
var qr_svg = qrimage.image(config, { type: 'png' });
|
||||||
|
qr_svg.pipe(require('fs').createWriteStream(`out/clients/${client.name}.png`));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!config.subnet6) {
|
||||||
|
let sn = crypto.randomBytes(5).toString("hex");
|
||||||
|
let s1 = sn.substr(0, 2);
|
||||||
|
let s23 = sn.substr(2, 4);
|
||||||
|
let s45 = sn.substr(6, 4);
|
||||||
|
config.subnet6 = `fd${s1}:${s23}:${s45}::`;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (process.argv[2]) {
|
||||||
|
case "init": {
|
||||||
|
const hostname = process.argv[3];
|
||||||
|
let subnet = process.argv[4];
|
||||||
|
const port = Number(process.argv[5]) | 51823;
|
||||||
|
|
||||||
|
if (!hostname)
|
||||||
|
throw new Error("Hostname required");
|
||||||
|
|
||||||
|
if (!subnet) {
|
||||||
|
let randomIP1 = Math.floor(Math.random() * 256);
|
||||||
|
let randomIP2 = Math.floor(Math.random() * 256);
|
||||||
|
subnet = `10.${randomIP1}.${randomIP2}.`;
|
||||||
|
} else {
|
||||||
|
if (!subnet.endsWith("."))
|
||||||
|
subnet += ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
config.subnet = subnet;
|
||||||
|
config.port = port;
|
||||||
|
config.server = {
|
||||||
|
endpoint: hostname,
|
||||||
|
...keyPair()
|
||||||
|
}
|
||||||
|
config.clients = {};
|
||||||
|
console.log("Created new configuration");
|
||||||
|
console.log("Server Hostname:", hostname);
|
||||||
|
console.log(" Port:", port);
|
||||||
|
console.log(" Subnet:", subnet + "0/24");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "generate": {
|
||||||
|
deleteRec("./out");
|
||||||
|
fs.mkdirSync("./out")
|
||||||
|
fs.mkdirSync("./out/server")
|
||||||
|
fs.mkdirSync("./out/clients")
|
||||||
|
makeServer(config)
|
||||||
|
makeClients(config)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "add": {
|
||||||
|
const name = process.argv[3];
|
||||||
|
const endpoint = process.argv[4];
|
||||||
|
if (!name)
|
||||||
|
throw new Error("No name!");
|
||||||
|
if (config.clients[name]) {
|
||||||
|
throw new Error("A device with this name exists already!\n Remove with 'remove <name>'");
|
||||||
|
}
|
||||||
|
|
||||||
|
let freeIP = 2;
|
||||||
|
clientArray(config.clients).forEach(c => c.ip >= freeIP ? freeIP = c.ip + 1 : undefined);
|
||||||
|
config.clients[name] = {
|
||||||
|
ip: freeIP,
|
||||||
|
endpoint,
|
||||||
|
...keyPair()
|
||||||
|
}
|
||||||
|
console.log("Peer added:")
|
||||||
|
console.log(`IP: ${config.subnet}${freeIP}`)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "remove": {
|
||||||
|
const name = process.argv[3];
|
||||||
|
if (!name)
|
||||||
|
throw new Error("No name!");
|
||||||
|
delete config.clients[name];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
console.log("Wireguard VPN Config generator v0.1");
|
||||||
|
console.log("Usage:")
|
||||||
|
console.log("generate, add, remove")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
console.error(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync("config.json", JSON.stringify(config, undefined, " "));
|
9
go.mod
9
go.mod
@ -1,9 +0,0 @@
|
|||||||
module wg_generate
|
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
|
||||||
github.com/urfave/cli v1.22.5
|
|
||||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
|
||||||
)
|
|
18
go.sum
18
go.sum
@ -1,18 +0,0 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
|
||||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
|
|
||||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
19
qr/LICENSE
Normal file
19
qr/LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2013 Yandex LLC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
50
qr/crc32buffer.js
Normal file
50
qr/crc32buffer.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crc_table = [];
|
||||||
|
|
||||||
|
for (var n = 0; n < 256; n++) {
|
||||||
|
var c = crc_table[n] = Buffer.alloc(4);
|
||||||
|
c.writeUInt32BE(n, 0);
|
||||||
|
|
||||||
|
for (var k = 0; k < 8; k++) {
|
||||||
|
var b0 = c[0] & 1;
|
||||||
|
var b1 = c[1] & 1;
|
||||||
|
var b2 = c[2] & 1;
|
||||||
|
var b3 = c[3] & 1;
|
||||||
|
|
||||||
|
c[0] = (c[0] >> 1) ^ (b3 ? 0xed : 0);
|
||||||
|
c[1] = (c[1] >> 1) ^ (b3 ? 0xb8 : 0) ^ (b0 ? 0x80 : 0);
|
||||||
|
c[2] = (c[2] >> 1) ^ (b3 ? 0x83 : 0) ^ (b1 ? 0x80 : 0);
|
||||||
|
c[3] = (c[3] >> 1) ^ (b3 ? 0x20 : 0) ^ (b2 ? 0x80 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(c, buf) {
|
||||||
|
var l = buf.length;
|
||||||
|
for (var n = 0; n < l; n++) {
|
||||||
|
var e = crc_table[c[3] ^ buf[n]];
|
||||||
|
c[3] = e[3] ^ c[2];
|
||||||
|
c[2] = e[2] ^ c[1];
|
||||||
|
c[1] = e[1] ^ c[0];
|
||||||
|
c[0] = e[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function crc32(/* arguments */) {
|
||||||
|
var l = arguments.length;
|
||||||
|
var c = Buffer.alloc(4);
|
||||||
|
c.fill(0xff);
|
||||||
|
|
||||||
|
for (var i = 0; i < l; i++) {
|
||||||
|
update(c, Buffer.from(arguments[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
c[0] = c[0] ^ 0xff;
|
||||||
|
c[1] = c[1] ^ 0xff;
|
||||||
|
c[2] = c[2] ^ 0xff;
|
||||||
|
c[3] = c[3] ^ 0xff;
|
||||||
|
|
||||||
|
return c.readUInt32BE(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = crc32;
|
175
qr/encode.js
Normal file
175
qr/encode.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function pushBits(arr, n, value) {
|
||||||
|
for (var bit = 1 << (n - 1); bit; bit = bit >>> 1) {
|
||||||
|
arr.push(bit & value ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 8bit encode
|
||||||
|
function encode_8bit(data) {
|
||||||
|
var len = data.length;
|
||||||
|
var bits = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
pushBits(bits, 8, data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = {};
|
||||||
|
|
||||||
|
var d = [0, 1, 0, 0];
|
||||||
|
pushBits(d, 16, len);
|
||||||
|
res.data10 = res.data27 = d.concat(bits);
|
||||||
|
|
||||||
|
if (len < 256) {
|
||||||
|
var d = [0, 1, 0, 0];
|
||||||
|
pushBits(d, 8, len);
|
||||||
|
res.data1 = d.concat(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 alphanumeric encode
|
||||||
|
var ALPHANUM = (function (s) {
|
||||||
|
var res = {};
|
||||||
|
for (var i = 0; i < s.length; i++) {
|
||||||
|
res[s[i]] = i;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
})('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:');
|
||||||
|
|
||||||
|
function encode_alphanum(str) {
|
||||||
|
var len = str.length;
|
||||||
|
var bits = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i += 2) {
|
||||||
|
var b = 6;
|
||||||
|
var n = ALPHANUM[str[i]];
|
||||||
|
if (str[i + 1]) {
|
||||||
|
b = 11;
|
||||||
|
n = n * 45 + ALPHANUM[str[i + 1]];
|
||||||
|
}
|
||||||
|
pushBits(bits, b, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = {};
|
||||||
|
|
||||||
|
var d = [0, 0, 1, 0];
|
||||||
|
pushBits(d, 13, len);
|
||||||
|
res.data27 = d.concat(bits);
|
||||||
|
|
||||||
|
if (len < 2048) {
|
||||||
|
var d = [0, 0, 1, 0];
|
||||||
|
pushBits(d, 11, len);
|
||||||
|
res.data10 = d.concat(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < 512) {
|
||||||
|
var d = [0, 0, 1, 0];
|
||||||
|
pushBits(d, 9, len);
|
||||||
|
res.data1 = d.concat(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 numeric encode
|
||||||
|
function encode_numeric(str) {
|
||||||
|
var len = str.length;
|
||||||
|
var bits = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i += 3) {
|
||||||
|
var s = str.substr(i, 3);
|
||||||
|
var b = Math.ceil(s.length * 10 / 3);
|
||||||
|
pushBits(bits, b, parseInt(s, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = {};
|
||||||
|
|
||||||
|
var d = [0, 0, 0, 1];
|
||||||
|
pushBits(d, 14, len);
|
||||||
|
res.data27 = d.concat(bits);
|
||||||
|
|
||||||
|
if (len < 4096) {
|
||||||
|
var d = [0, 0, 0, 1];
|
||||||
|
pushBits(d, 12, len);
|
||||||
|
res.data10 = d.concat(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < 1024) {
|
||||||
|
var d = [0, 0, 0, 1];
|
||||||
|
pushBits(d, 10, len);
|
||||||
|
res.data1 = d.concat(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 url encode
|
||||||
|
function encode_url(str) {
|
||||||
|
var slash = str.indexOf('/', 8) + 1 || str.length;
|
||||||
|
var res = encode(str.slice(0, slash).toUpperCase(), false);
|
||||||
|
|
||||||
|
if (slash >= str.length) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path_res = encode(str.slice(slash), false);
|
||||||
|
|
||||||
|
res.data27 = res.data27.concat(path_res.data27);
|
||||||
|
|
||||||
|
if (res.data10 && path_res.data10) {
|
||||||
|
res.data10 = res.data10.concat(path_res.data10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.data1 && path_res.data1) {
|
||||||
|
res.data1 = res.data1.concat(path_res.data1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 Choose encode mode and generates struct with data for different version
|
||||||
|
function encode(data, parse_url) {
|
||||||
|
var str;
|
||||||
|
var t = typeof data;
|
||||||
|
|
||||||
|
if (t == 'string' || t == 'number') {
|
||||||
|
str = '' + data;
|
||||||
|
data = Buffer.from(str);
|
||||||
|
} else if (Buffer.isBuffer(data)) {
|
||||||
|
str = data.toString();
|
||||||
|
} else if (Array.isArray(data)) {
|
||||||
|
data = Buffer.from(data);
|
||||||
|
str = data.toString();
|
||||||
|
} else {
|
||||||
|
throw new Error("Bad data");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[0-9]+$/.test(str)) {
|
||||||
|
if (data.length > 7089) {
|
||||||
|
throw new Error("Too much data");
|
||||||
|
}
|
||||||
|
return encode_numeric(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[0-9A-Z \$%\*\+\.\/\:\-]+$/.test(str)) {
|
||||||
|
if (data.length > 4296) {
|
||||||
|
throw new Error("Too much data");
|
||||||
|
}
|
||||||
|
return encode_alphanum(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parse_url && /^https?:/i.test(str)) {
|
||||||
|
return encode_url(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length > 2953) {
|
||||||
|
throw new Error("Too much data");
|
||||||
|
}
|
||||||
|
return encode_8bit(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 export functions
|
||||||
|
module.exports = encode;
|
77
qr/errorcode.js
Normal file
77
qr/errorcode.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// {{{1 Galois Field Math
|
||||||
|
var GF256_BASE = 285;
|
||||||
|
|
||||||
|
var EXP_TABLE = [1];
|
||||||
|
var LOG_TABLE = [];
|
||||||
|
|
||||||
|
for (var i = 1; i < 256; i++) {
|
||||||
|
var n = EXP_TABLE[i - 1] << 1;
|
||||||
|
if (n > 255) n = n ^ GF256_BASE;
|
||||||
|
EXP_TABLE[i] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 255; i++) {
|
||||||
|
LOG_TABLE[EXP_TABLE[i]] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exp(k) {
|
||||||
|
while (k < 0) k += 255;
|
||||||
|
while (k > 255) k -= 255;
|
||||||
|
return EXP_TABLE[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(k) {
|
||||||
|
if (k < 1 || k > 255) {
|
||||||
|
throw Error('Bad log(' + k + ')');
|
||||||
|
}
|
||||||
|
return LOG_TABLE[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 Generator Polynomials
|
||||||
|
var POLYNOMIALS = [
|
||||||
|
[0], // a^0 x^0
|
||||||
|
[0, 0], // a^0 x^1 + a^0 x^0
|
||||||
|
[0, 25, 1], // a^0 x^2 + a^25 x^1 + a^1 x^0
|
||||||
|
// and so on...
|
||||||
|
];
|
||||||
|
|
||||||
|
function generatorPolynomial(num) {
|
||||||
|
if (POLYNOMIALS[num]) {
|
||||||
|
return POLYNOMIALS[num];
|
||||||
|
}
|
||||||
|
var prev = generatorPolynomial(num - 1);
|
||||||
|
var res = [];
|
||||||
|
|
||||||
|
res[0] = prev[0];
|
||||||
|
for (var i = 1; i <= num; i++) {
|
||||||
|
res[i] = log(exp(prev[i]) ^ exp(prev[i - 1] + num - 1));
|
||||||
|
}
|
||||||
|
POLYNOMIALS[num] = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 export functions
|
||||||
|
module.exports = function calculate_ec(msg, ec_len) {
|
||||||
|
// `msg` could be array or buffer
|
||||||
|
// convert `msg` to array
|
||||||
|
msg = [].slice.call(msg);
|
||||||
|
|
||||||
|
// Generator Polynomial
|
||||||
|
var poly = generatorPolynomial(ec_len);
|
||||||
|
|
||||||
|
for (var i = 0; i < ec_len; i++) msg.push(0);
|
||||||
|
while (msg.length > ec_len) {
|
||||||
|
if (!msg[0]) {
|
||||||
|
msg.shift();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var log_k = log(msg[0]);
|
||||||
|
for (var i = 0; i <= ec_len; i++) {
|
||||||
|
msg[i] = msg[i] ^ exp(poly[i] + log_k);
|
||||||
|
}
|
||||||
|
msg.shift();
|
||||||
|
}
|
||||||
|
return Buffer.from(msg);
|
||||||
|
}
|
352
qr/matrix.js
Normal file
352
qr/matrix.js
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// {{{1 Initialize matrix with zeros
|
||||||
|
function init(version) {
|
||||||
|
var N = version * 4 + 17;
|
||||||
|
var matrix = [];
|
||||||
|
var zeros = new Buffer(N);
|
||||||
|
zeros.fill(0);
|
||||||
|
zeros = [].slice.call(zeros);
|
||||||
|
for (var i = 0; i < N; i++) {
|
||||||
|
matrix[i] = zeros.slice();
|
||||||
|
}
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 Put finders into matrix
|
||||||
|
function fillFinders(matrix) {
|
||||||
|
var N = matrix.length;
|
||||||
|
for (var i = -3; i <= 3; i++) {
|
||||||
|
for (var j = -3; j <= 3; j++) {
|
||||||
|
var max = Math.max(i, j);
|
||||||
|
var min = Math.min(i, j);
|
||||||
|
var pixel = (max == 2 && min >= -2) || (min == -2 && max <= 2) ? 0x80 : 0x81;
|
||||||
|
matrix[3 + i][3 + j] = pixel;
|
||||||
|
matrix[3 + i][N - 4 + j] = pixel;
|
||||||
|
matrix[N - 4 + i][3 + j] = pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 8; i++) {
|
||||||
|
matrix[7][i] = matrix[i][7] =
|
||||||
|
matrix[7][N - i - 1] = matrix[i][N - 8] =
|
||||||
|
matrix[N - 8][i] = matrix[N - 1 - i][7] = 0x80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 Put align and timinig
|
||||||
|
function fillAlignAndTiming(matrix) {
|
||||||
|
var N = matrix.length;
|
||||||
|
if (N > 21) {
|
||||||
|
var len = N - 13;
|
||||||
|
var delta = Math.round(len / Math.ceil(len / 28));
|
||||||
|
if (delta % 2) delta++;
|
||||||
|
var res = [];
|
||||||
|
for (var p = len + 6; p > 10; p -= delta) {
|
||||||
|
res.unshift(p);
|
||||||
|
}
|
||||||
|
res.unshift(6);
|
||||||
|
for (var i = 0; i < res.length; i++) {
|
||||||
|
for (var j = 0; j < res.length; j++) {
|
||||||
|
var x = res[i], y = res[j];
|
||||||
|
if (matrix[x][y]) continue;
|
||||||
|
for (var r = -2; r <=2 ; r++) {
|
||||||
|
for (var c = -2; c <=2 ; c++) {
|
||||||
|
var max = Math.max(r, c);
|
||||||
|
var min = Math.min(r, c);
|
||||||
|
var pixel = (max == 1 && min >= -1) || (min == -1 && max <= 1) ? 0x80 : 0x81;
|
||||||
|
matrix[x + r][y + c] = pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 8; i < N - 8; i++) {
|
||||||
|
matrix[6][i] = matrix[i][6] = i % 2 ? 0x80 : 0x81;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 Fill reserved areas with zeroes
|
||||||
|
function fillStub(matrix) {
|
||||||
|
var N = matrix.length;
|
||||||
|
for (var i = 0; i < 8; i++) {
|
||||||
|
if (i != 6) {
|
||||||
|
matrix[8][i] = matrix[i][8] = 0x80;
|
||||||
|
}
|
||||||
|
matrix[8][N - 1 - i] = 0x80;
|
||||||
|
matrix[N - 1 - i][8] = 0x80;
|
||||||
|
}
|
||||||
|
matrix[8][8] = 0x80;
|
||||||
|
matrix[N - 8][8] = 0x81;
|
||||||
|
|
||||||
|
if (N < 45) return;
|
||||||
|
|
||||||
|
for (var i = N - 11; i < N - 8; i++) {
|
||||||
|
for (var j = 0; j < 6; j++) {
|
||||||
|
matrix[i][j] = matrix[j][i] = 0x80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 Fill reserved areas
|
||||||
|
var fillReserved = (function() {
|
||||||
|
var FORMATS = Array(32);
|
||||||
|
var VERSIONS = Array(40);
|
||||||
|
|
||||||
|
var gf15 = 0x0537;
|
||||||
|
var gf18 = 0x1f25;
|
||||||
|
var formats_mask = 0x5412;
|
||||||
|
|
||||||
|
for (var format = 0; format < 32; format++) {
|
||||||
|
var res = format << 10;
|
||||||
|
for (var i = 5; i > 0; i--) {
|
||||||
|
if (res >>> (9 + i)) {
|
||||||
|
res = res ^ (gf15 << (i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FORMATS[format] = (res | (format << 10)) ^ formats_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var version = 7; version <= 40; version++) {
|
||||||
|
var res = version << 12;
|
||||||
|
for (var i = 6; i > 0; i--) {
|
||||||
|
if (res >>> (11 + i)) {
|
||||||
|
res = res ^ (gf18 << (i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VERSIONS[version] = (res | (version << 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
var EC_LEVELS = { L: 1, M: 0, Q: 3, H: 2 };
|
||||||
|
|
||||||
|
return function fillReserved(matrix, ec_level, mask) {
|
||||||
|
var N = matrix.length;
|
||||||
|
var format = FORMATS[EC_LEVELS[ec_level] << 3 | mask];
|
||||||
|
function F(k) { return format >> k & 1 ? 0x81 : 0x80 };
|
||||||
|
for (var i = 0; i < 8; i++) {
|
||||||
|
matrix[8][N - 1 - i] = F(i);
|
||||||
|
if (i < 6) matrix[i][8] = F(i);
|
||||||
|
}
|
||||||
|
for (var i = 8; i < 15; i++) {
|
||||||
|
matrix[N - 15 + i][8] = F(i);
|
||||||
|
if (i > 8) matrix[8][14 - i] = F(i);
|
||||||
|
}
|
||||||
|
matrix[7][8] = F(6);
|
||||||
|
matrix[8][8] = F(7);
|
||||||
|
matrix[8][7] = F(8);
|
||||||
|
|
||||||
|
var version = VERSIONS[(N - 17)/4];
|
||||||
|
if (!version) return;
|
||||||
|
function V(k) { return version >> k & 1 ? 0x81 : 0x80 };
|
||||||
|
for (var i = 0; i < 6; i++) {
|
||||||
|
for (var j = 0; j < 3; j++) {
|
||||||
|
matrix[N - 11 + j][i] = matrix[i][N - 11 + j] = V(i * 3 + j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// {{{1 Fill data
|
||||||
|
var fillData = (function() {
|
||||||
|
var MASK_FUNCTIONS = [
|
||||||
|
function(i, j) { return (i + j) % 2 == 0 },
|
||||||
|
function(i, j) { return i % 2 == 0 },
|
||||||
|
function(i, j) { return j % 3 == 0 },
|
||||||
|
function(i, j) { return (i + j) % 3 == 0 },
|
||||||
|
function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0 },
|
||||||
|
function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0 },
|
||||||
|
function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0 },
|
||||||
|
function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0 }
|
||||||
|
];
|
||||||
|
|
||||||
|
return function fillData(matrix, data, mask) {
|
||||||
|
var N = matrix.length;
|
||||||
|
var row, col, dir = -1;
|
||||||
|
row = col = N - 1;
|
||||||
|
var mask_fn = MASK_FUNCTIONS[mask];
|
||||||
|
var len = data.blocks[data.blocks.length - 1].length;
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
for (var b = 0; b < data.blocks.length; b++) {
|
||||||
|
if (data.blocks[b].length <= i) continue;
|
||||||
|
put(data.blocks[b][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = data.ec_len;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
for (var b = 0; b < data.ec.length; b++) {
|
||||||
|
put(data.ec[b][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col > -1) {
|
||||||
|
do {
|
||||||
|
matrix[row][col] = mask_fn(row, col) ? 1 : 0;
|
||||||
|
} while (next());
|
||||||
|
}
|
||||||
|
|
||||||
|
function put(byte) {
|
||||||
|
for (var mask = 0x80; mask; mask = mask >> 1) {
|
||||||
|
var pixel = !!(mask & byte);
|
||||||
|
if (mask_fn(row, col)) pixel = !pixel;
|
||||||
|
matrix[row][col] = pixel ? 1 : 0;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
do {
|
||||||
|
if ((col % 2) ^ (col < 6)) {
|
||||||
|
if (dir < 0 && row == 0 || dir > 0 && row == N - 1) {
|
||||||
|
col--;
|
||||||
|
dir = -dir;
|
||||||
|
} else {
|
||||||
|
col++;
|
||||||
|
row += dir;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
col--;
|
||||||
|
}
|
||||||
|
if (col == 6) {
|
||||||
|
col--;
|
||||||
|
}
|
||||||
|
if (col < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} while (matrix[row][col] & 0xf0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// {{{1 Calculate penalty
|
||||||
|
function calculatePenalty(matrix) {
|
||||||
|
var N = matrix.length;
|
||||||
|
var penalty = 0;
|
||||||
|
// Rule 1
|
||||||
|
for (var i = 0; i < N; i++) {
|
||||||
|
var pixel = matrix[i][0] & 1;
|
||||||
|
var len = 1;
|
||||||
|
for (var j = 1; j < N; j++) {
|
||||||
|
var p = matrix[i][j] & 1;
|
||||||
|
if (p == pixel) {
|
||||||
|
len++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (len >= 5) {
|
||||||
|
penalty += len - 2;
|
||||||
|
}
|
||||||
|
pixel = p;
|
||||||
|
len = 1;
|
||||||
|
}
|
||||||
|
if (len >= 5) {
|
||||||
|
penalty += len - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var j = 0; j < N; j++) {
|
||||||
|
var pixel = matrix[0][j] & 1;
|
||||||
|
var len = 1;
|
||||||
|
for (var i = 1; i < N; i++) {
|
||||||
|
var p = matrix[i][j] & 1;
|
||||||
|
if (p == pixel) {
|
||||||
|
len++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (len >= 5) {
|
||||||
|
penalty += len - 2;
|
||||||
|
}
|
||||||
|
pixel = p;
|
||||||
|
len = 1;
|
||||||
|
}
|
||||||
|
if (len >= 5) {
|
||||||
|
penalty += len - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 2
|
||||||
|
for (var i = 0; i < N - 1; i++) {
|
||||||
|
for (var j = 0; j < N - 1; j++) {
|
||||||
|
var s = matrix[i][j] + matrix[i][j + 1] + matrix[i + 1][j] + matrix[i + 1][j + 1] & 7;
|
||||||
|
if (s == 0 || s == 4) {
|
||||||
|
penalty += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 3
|
||||||
|
function I(k) { return matrix[i][j + k] & 1 };
|
||||||
|
function J(k) { return matrix[i + k][j] & 1 };
|
||||||
|
for (var i = 0; i < N; i++) {
|
||||||
|
for (var j = 0; j < N; j++) {
|
||||||
|
if (j < N - 6 && I(0) && !I(1) && I(2) && I(3) && I(4) && !I(5) && I(6)) {
|
||||||
|
if (j >= 4 && !(I(-4) || I(-3) || I(-2) || I(-1))) {
|
||||||
|
penalty += 40;
|
||||||
|
}
|
||||||
|
if (j < N - 10 && !(I(7) || I(8) || I(9) || I(10))) {
|
||||||
|
penalty += 40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < N - 6 && J(0) && !J(1) && J(2) && J(3) && J(4) && !J(5) && J(6)) {
|
||||||
|
if (i >= 4 && !(J(-4) || J(-3) || J(-2) || J(-1))) {
|
||||||
|
penalty += 40;
|
||||||
|
}
|
||||||
|
if (i < N - 10 && !(J(7) || J(8) || J(9) || J(10))) {
|
||||||
|
penalty += 40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 4
|
||||||
|
var numDark = 0;
|
||||||
|
for (var i = 0; i < N; i++) {
|
||||||
|
for (var j = 0; j < N; j++) {
|
||||||
|
if (matrix[i][j] & 1) numDark++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
penalty += 10 * Math.floor(Math.abs(10 - 20 * numDark/(N * N)));
|
||||||
|
|
||||||
|
return penalty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 All-in-one function
|
||||||
|
function getMatrix(data) {
|
||||||
|
var matrix = init(data.version);
|
||||||
|
fillFinders(matrix);
|
||||||
|
fillAlignAndTiming(matrix);
|
||||||
|
fillStub(matrix);
|
||||||
|
|
||||||
|
var penalty = Infinity;
|
||||||
|
var bestMask = 0;
|
||||||
|
for (var mask = 0; mask < 8; mask++) {
|
||||||
|
fillData(matrix, data, mask);
|
||||||
|
fillReserved(matrix, data.ec_level, mask);
|
||||||
|
var p = calculatePenalty(matrix);
|
||||||
|
if (p < penalty) {
|
||||||
|
penalty = p;
|
||||||
|
bestMask = mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fillData(matrix, data, bestMask);
|
||||||
|
fillReserved(matrix, data.ec_level, bestMask);
|
||||||
|
|
||||||
|
return matrix.map(function(row) {
|
||||||
|
return row.map(function(cell) {
|
||||||
|
return cell & 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 export functions
|
||||||
|
module.exports = {
|
||||||
|
getMatrix: getMatrix,
|
||||||
|
init: init,
|
||||||
|
fillFinders: fillFinders,
|
||||||
|
fillAlignAndTiming: fillAlignAndTiming,
|
||||||
|
fillStub: fillStub,
|
||||||
|
fillReserved: fillReserved,
|
||||||
|
fillData: fillData,
|
||||||
|
calculatePenalty: calculatePenalty,
|
||||||
|
}
|
64
qr/png.js
Normal file
64
qr/png.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var zlib = require('zlib');
|
||||||
|
|
||||||
|
var crc32 = require('./crc32buffer');
|
||||||
|
|
||||||
|
var PNG_HEAD = new Buffer([137, 80, 78, 71, 13, 10, 26, 10]);
|
||||||
|
var PNG_IHDR = new Buffer([0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
|
var PNG_IDAT = new Buffer([0, 0, 0, 0, 73, 68, 65, 84]);
|
||||||
|
var PNG_IEND = new Buffer([0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]);
|
||||||
|
|
||||||
|
function png(bitmap, stream) {
|
||||||
|
stream.push(PNG_HEAD);
|
||||||
|
|
||||||
|
var IHDR = Buffer.concat([PNG_IHDR]);
|
||||||
|
IHDR.writeUInt32BE(bitmap.size, 8);
|
||||||
|
IHDR.writeUInt32BE(bitmap.size, 12);
|
||||||
|
IHDR.writeUInt32BE(crc32(IHDR.slice(4, -4)), 21);
|
||||||
|
stream.push(IHDR);
|
||||||
|
|
||||||
|
var IDAT = Buffer.concat([
|
||||||
|
PNG_IDAT,
|
||||||
|
zlib.deflateSync(bitmap.data, { level: 9 }),
|
||||||
|
new Buffer(4)
|
||||||
|
]);
|
||||||
|
IDAT.writeUInt32BE(IDAT.length - 12, 0);
|
||||||
|
IDAT.writeUInt32BE(crc32(IDAT.slice(4, -4)), IDAT.length - 4);
|
||||||
|
stream.push(IDAT);
|
||||||
|
|
||||||
|
stream.push(PNG_IEND);
|
||||||
|
stream.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bitmap(matrix, size, margin) {
|
||||||
|
var N = matrix.length;
|
||||||
|
var X = (N + 2 * margin) * size;
|
||||||
|
var data = new Buffer((X + 1) * X);
|
||||||
|
data.fill(255);
|
||||||
|
for (var i = 0; i < X; i++) {
|
||||||
|
data[i * (X + 1)] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < N; i++) {
|
||||||
|
for (var j = 0; j < N; j++) {
|
||||||
|
if (matrix[i][j]) {
|
||||||
|
var offset = ((margin + i) * (X + 1) + (margin + j)) * size + 1;
|
||||||
|
data.fill(0, offset, offset + size);
|
||||||
|
for (var c = 1; c < size; c++) {
|
||||||
|
data.copy(data, offset + c * (X + 1), offset, offset + size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
size: X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bitmap: bitmap,
|
||||||
|
png: png
|
||||||
|
}
|
180
qr/qr-base.js
Normal file
180
qr/qr-base.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var encode = require('./encode');
|
||||||
|
var calculateEC = require('./errorcode');
|
||||||
|
var matrix = require('./matrix');
|
||||||
|
|
||||||
|
function _deepCopy(obj) {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
var EC_LEVELS = ['L', 'M', 'Q', 'H'];
|
||||||
|
|
||||||
|
// {{{1 Versions
|
||||||
|
var versions = [
|
||||||
|
[], // there is no version 0
|
||||||
|
// total number of codewords, (number of ec codewords, number of blocks) * ( L, M, Q, H )
|
||||||
|
[26, 7, 1, 10, 1, 13, 1, 17, 1],
|
||||||
|
[44, 10, 1, 16, 1, 22, 1, 28, 1],
|
||||||
|
[70, 15, 1, 26, 1, 36, 2, 44, 2],
|
||||||
|
[100, 20, 1, 36, 2, 52, 2, 64, 4],
|
||||||
|
[134, 26, 1, 48, 2, 72, 4, 88, 4], // 5
|
||||||
|
[172, 36, 2, 64, 4, 96, 4, 112, 4],
|
||||||
|
[196, 40, 2, 72, 4, 108, 6, 130, 5],
|
||||||
|
[242, 48, 2, 88, 4, 132, 6, 156, 6],
|
||||||
|
[292, 60, 2, 110, 5, 160, 8, 192, 8],
|
||||||
|
[346, 72, 4, 130, 5, 192, 8, 224, 8], // 10
|
||||||
|
[404, 80, 4, 150, 5, 224, 8, 264, 11],
|
||||||
|
[466, 96, 4, 176, 8, 260, 10, 308, 11],
|
||||||
|
[532, 104, 4, 198, 9, 288, 12, 352, 16],
|
||||||
|
[581, 120, 4, 216, 9, 320, 16, 384, 16],
|
||||||
|
[655, 132, 6, 240, 10, 360, 12, 432, 18], // 15
|
||||||
|
[733, 144, 6, 280, 10, 408, 17, 480, 16],
|
||||||
|
[815, 168, 6, 308, 11, 448, 16, 532, 19],
|
||||||
|
[901, 180, 6, 338, 13, 504, 18, 588, 21],
|
||||||
|
[991, 196, 7, 364, 14, 546, 21, 650, 25],
|
||||||
|
[1085, 224, 8, 416, 16, 600, 20, 700, 25], // 20
|
||||||
|
[1156, 224, 8, 442, 17, 644, 23, 750, 25],
|
||||||
|
[1258, 252, 9, 476, 17, 690, 23, 816, 34],
|
||||||
|
[1364, 270, 9, 504, 18, 750, 25, 900, 30],
|
||||||
|
[1474, 300, 10, 560, 20, 810, 27, 960, 32],
|
||||||
|
[1588, 312, 12, 588, 21, 870, 29, 1050, 35], // 25
|
||||||
|
[1706, 336, 12, 644, 23, 952, 34, 1110, 37],
|
||||||
|
[1828, 360, 12, 700, 25, 1020, 34, 1200, 40],
|
||||||
|
[1921, 390, 13, 728, 26, 1050, 35, 1260, 42],
|
||||||
|
[2051, 420, 14, 784, 28, 1140, 38, 1350, 45],
|
||||||
|
[2185, 450, 15, 812, 29, 1200, 40, 1440, 48], // 30
|
||||||
|
[2323, 480, 16, 868, 31, 1290, 43, 1530, 51],
|
||||||
|
[2465, 510, 17, 924, 33, 1350, 45, 1620, 54],
|
||||||
|
[2611, 540, 18, 980, 35, 1440, 48, 1710, 57],
|
||||||
|
[2761, 570, 19, 1036, 37, 1530, 51, 1800, 60],
|
||||||
|
[2876, 570, 19, 1064, 38, 1590, 53, 1890, 63], // 35
|
||||||
|
[3034, 600, 20, 1120, 40, 1680, 56, 1980, 66],
|
||||||
|
[3196, 630, 21, 1204, 43, 1770, 59, 2100, 70],
|
||||||
|
[3362, 660, 22, 1260, 45, 1860, 62, 2220, 74],
|
||||||
|
[3532, 720, 24, 1316, 47, 1950, 65, 2310, 77],
|
||||||
|
[3706, 750, 25, 1372, 49, 2040, 68, 2430, 81] // 40
|
||||||
|
];
|
||||||
|
|
||||||
|
versions = versions.map(function(v, index) {
|
||||||
|
if (!index) return {};
|
||||||
|
|
||||||
|
var res = {
|
||||||
|
}
|
||||||
|
for (var i = 1; i < 8; i += 2) {
|
||||||
|
var length = v[0] - v[i];
|
||||||
|
var num_template = v[i+1];
|
||||||
|
var ec_level = EC_LEVELS[(i/2)|0];
|
||||||
|
var level = {
|
||||||
|
version: index,
|
||||||
|
ec_level: ec_level,
|
||||||
|
data_len: length,
|
||||||
|
ec_len: v[i] / num_template,
|
||||||
|
blocks: [],
|
||||||
|
ec: []
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k = num_template, n = length; k > 0; k--) {
|
||||||
|
var block = (n / k)|0;
|
||||||
|
level.blocks.push(block);
|
||||||
|
n -= block;
|
||||||
|
|
||||||
|
}
|
||||||
|
res[ec_level] = level;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
// {{{1 Get version template
|
||||||
|
function getTemplate(message, ec_level) {
|
||||||
|
var i = 1;
|
||||||
|
var len;
|
||||||
|
|
||||||
|
if (message.data1) {
|
||||||
|
len = Math.ceil(message.data1.length / 8);
|
||||||
|
} else {
|
||||||
|
i = 10;
|
||||||
|
}
|
||||||
|
for (/* i */; i < 10; i++) {
|
||||||
|
var version = versions[i][ec_level];
|
||||||
|
if (version.data_len >= len) {
|
||||||
|
return _deepCopy(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.data10) {
|
||||||
|
len = Math.ceil(message.data10.length / 8);
|
||||||
|
} else {
|
||||||
|
i = 27;
|
||||||
|
}
|
||||||
|
for (/* i */; i < 27; i++) {
|
||||||
|
var version = versions[i][ec_level];
|
||||||
|
if (version.data_len >= len) {
|
||||||
|
return _deepCopy(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = Math.ceil(message.data27.length / 8);
|
||||||
|
for (/* i */; i < 41; i++) {
|
||||||
|
var version = versions[i][ec_level];
|
||||||
|
if (version.data_len >= len) {
|
||||||
|
return _deepCopy(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Too much data");
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 Fill template
|
||||||
|
function fillTemplate(message, template) {
|
||||||
|
var blocks = new Buffer(template.data_len);
|
||||||
|
blocks.fill(0);
|
||||||
|
|
||||||
|
if (template.version < 10) {
|
||||||
|
message = message.data1;
|
||||||
|
} else if (template.version < 27) {
|
||||||
|
message = message.data10;
|
||||||
|
} else {
|
||||||
|
message = message.data27;
|
||||||
|
}
|
||||||
|
|
||||||
|
var len = message.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i += 8) {
|
||||||
|
var b = 0;
|
||||||
|
for (var j = 0; j < 8; j++) {
|
||||||
|
b = (b << 1) | (message[i + j] ? 1 : 0);
|
||||||
|
}
|
||||||
|
blocks[i / 8] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pad = 236;
|
||||||
|
for (var i = Math.ceil((len + 4) / 8); i < blocks.length; i++) {
|
||||||
|
blocks[i] = pad;
|
||||||
|
pad = (pad == 236) ? 17 : 236;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = 0;
|
||||||
|
template.blocks = template.blocks.map(function(n) {
|
||||||
|
var b = blocks.slice(offset, offset + n);
|
||||||
|
offset += n;
|
||||||
|
template.ec.push(calculateEC(b, template.ec_len));
|
||||||
|
return b;
|
||||||
|
});
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 All-in-one
|
||||||
|
function QR(text, ec_level, parse_url) {
|
||||||
|
ec_level = EC_LEVELS.indexOf(ec_level) > -1 ? ec_level : 'M';
|
||||||
|
var message = encode(text, parse_url);
|
||||||
|
var data = fillTemplate(message, getTemplate(message, ec_level));
|
||||||
|
return matrix.getMatrix(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{1 export functions
|
||||||
|
module.exports = {
|
||||||
|
QR: QR,
|
||||||
|
getTemplate: getTemplate,
|
||||||
|
fillTemplate: fillTemplate,
|
||||||
|
}
|
121
qr/qr.js
Normal file
121
qr/qr.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Readable = require('stream').Readable;
|
||||||
|
|
||||||
|
var QR = require('./qr-base').QR;
|
||||||
|
var png = require('./png');
|
||||||
|
var vector = require('./vector');
|
||||||
|
|
||||||
|
var fn_noop = function() {};
|
||||||
|
|
||||||
|
var BITMAP_OPTIONS = {
|
||||||
|
parse_url: false,
|
||||||
|
ec_level: 'M',
|
||||||
|
size: 5,
|
||||||
|
margin: 4,
|
||||||
|
customize: null
|
||||||
|
};
|
||||||
|
|
||||||
|
var VECTOR_OPTIONS = {
|
||||||
|
parse_url: false,
|
||||||
|
ec_level: 'M',
|
||||||
|
margin: 1,
|
||||||
|
size: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
function get_options(options, force_type) {
|
||||||
|
if (typeof options === 'string') {
|
||||||
|
options = { 'ec_level': options }
|
||||||
|
} else {
|
||||||
|
options = options || {};
|
||||||
|
}
|
||||||
|
var _options = {
|
||||||
|
type: String(force_type || options.type || 'png').toLowerCase()
|
||||||
|
};
|
||||||
|
|
||||||
|
var defaults = _options.type == 'png' ? BITMAP_OPTIONS : VECTOR_OPTIONS;
|
||||||
|
|
||||||
|
for (var k in defaults) {
|
||||||
|
_options[k] = k in options ? options[k] : defaults[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function qr_image(text, options) {
|
||||||
|
options = get_options(options);
|
||||||
|
|
||||||
|
var matrix = QR(text, options.ec_level, options.parse_url);
|
||||||
|
var stream = new Readable();
|
||||||
|
stream._read = fn_noop;
|
||||||
|
|
||||||
|
switch (options.type) {
|
||||||
|
case 'svg':
|
||||||
|
case 'pdf':
|
||||||
|
case 'eps':
|
||||||
|
process.nextTick(function() {
|
||||||
|
vector[options.type](matrix, stream, options.margin, options.size);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'svgpath':
|
||||||
|
// deprecated, use svg_object method
|
||||||
|
process.nextTick(function() {
|
||||||
|
var obj = vector.svg_object(matrix, options.margin, options.size);
|
||||||
|
stream.push(obj.path);
|
||||||
|
stream.push(null);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'png':
|
||||||
|
default:
|
||||||
|
process.nextTick(function() {
|
||||||
|
var bitmap = png.bitmap(matrix, options.size, options.margin);
|
||||||
|
if (options.customize) {
|
||||||
|
options.customize(bitmap);
|
||||||
|
}
|
||||||
|
png.png(bitmap, stream);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
function qr_image_sync(text, options) {
|
||||||
|
options = get_options(options);
|
||||||
|
|
||||||
|
var matrix = QR(text, options.ec_level, options.parse_url);
|
||||||
|
var stream = [];
|
||||||
|
var result;
|
||||||
|
|
||||||
|
switch (options.type) {
|
||||||
|
case 'svg':
|
||||||
|
case 'pdf':
|
||||||
|
case 'eps':
|
||||||
|
vector[options.type](matrix, stream, options.margin, options.size);
|
||||||
|
result = stream.filter(Boolean).join('');
|
||||||
|
break;
|
||||||
|
case 'png':
|
||||||
|
default:
|
||||||
|
var bitmap = png.bitmap(matrix, options.size, options.margin);
|
||||||
|
if (options.customize) {
|
||||||
|
options.customize(bitmap);
|
||||||
|
}
|
||||||
|
png.png(bitmap, stream);
|
||||||
|
result = Buffer.concat(stream.filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function svg_object(text, options) {
|
||||||
|
options = get_options(options, 'svg');
|
||||||
|
|
||||||
|
var matrix = QR(text, options.ec_level);
|
||||||
|
return vector.svg_object(matrix, options.margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
matrix: QR,
|
||||||
|
image: qr_image,
|
||||||
|
imageSync: qr_image_sync,
|
||||||
|
svgObject: svg_object
|
||||||
|
};
|
260
qr/vector.js
Normal file
260
qr/vector.js
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function matrix2path(matrix) {
|
||||||
|
var N = matrix.length;
|
||||||
|
var filled = [];
|
||||||
|
for (var row = -1; row <= N; row++) {
|
||||||
|
filled[row] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = [];
|
||||||
|
for (var row = 0; row < N; row++) {
|
||||||
|
for (var col = 0; col < N; col++) {
|
||||||
|
if (filled[row][col]) continue;
|
||||||
|
filled[row][col] = 1;
|
||||||
|
if (isDark(row, col)) {
|
||||||
|
if (!isDark(row - 1, col)) {
|
||||||
|
path.push(plot(row, col, 'right'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isDark(row, col - 1)) {
|
||||||
|
path.push(plot(row, col, 'down'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
|
||||||
|
function isDark(row, col) {
|
||||||
|
if (row < 0 || col < 0 || row >= N || col >= N) return false;
|
||||||
|
return !!matrix[row][col];
|
||||||
|
}
|
||||||
|
|
||||||
|
function plot(row0, col0, dir) {
|
||||||
|
filled[row0][col0] = 1;
|
||||||
|
var res = [];
|
||||||
|
res.push(['M', col0, row0 ]);
|
||||||
|
var row = row0;
|
||||||
|
var col = col0;
|
||||||
|
var len = 0;
|
||||||
|
do {
|
||||||
|
switch (dir) {
|
||||||
|
case 'right':
|
||||||
|
filled[row][col] = 1;
|
||||||
|
if (isDark(row, col)) {
|
||||||
|
filled[row - 1][col] = 1;
|
||||||
|
if (isDark(row - 1, col)) {
|
||||||
|
res.push(['h', len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'up';
|
||||||
|
} else {
|
||||||
|
len++;
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(['h', len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'down';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
filled[row - 1][col - 1] = 1;
|
||||||
|
if (isDark(row - 1, col - 1)) {
|
||||||
|
filled[row][col - 1] = 1;
|
||||||
|
if (isDark(row, col - 1)) {
|
||||||
|
res.push(['h', -len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'down';
|
||||||
|
} else {
|
||||||
|
len++;
|
||||||
|
col--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(['h', -len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'up';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
filled[row][col - 1] = 1;
|
||||||
|
if (isDark(row, col - 1)) {
|
||||||
|
filled[row][col] = 1;
|
||||||
|
if (isDark(row, col)) {
|
||||||
|
res.push(['v', len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'right';
|
||||||
|
} else {
|
||||||
|
len++;
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(['v', len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'left';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'up':
|
||||||
|
filled[row - 1][col] = 1;
|
||||||
|
if (isDark(row - 1, col)) {
|
||||||
|
filled[row - 1][col - 1] = 1;
|
||||||
|
if (isDark(row - 1, col - 1)) {
|
||||||
|
res.push(['v', -len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'left';
|
||||||
|
} else {
|
||||||
|
len++;
|
||||||
|
row--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(['v', -len]);
|
||||||
|
len = 0;
|
||||||
|
dir = 'right';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (row != row0 || col != col0);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushSVGPath(matrix, stream, margin) {
|
||||||
|
matrix2path(matrix).forEach(function(subpath) {
|
||||||
|
var res = '';
|
||||||
|
for (var k = 0; k < subpath.length; k++) {
|
||||||
|
var item = subpath[k];
|
||||||
|
switch (item[0]) {
|
||||||
|
case 'M':
|
||||||
|
res += 'M' + (item[1] + margin) + ' ' + (item[2] + margin);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res += item.join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += 'z';
|
||||||
|
stream.push(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function SVG_object(matrix, margin) {
|
||||||
|
var stream = [];
|
||||||
|
pushSVGPath(matrix, stream, margin);
|
||||||
|
|
||||||
|
var result = {
|
||||||
|
size: matrix.length + 2 * margin,
|
||||||
|
path: stream.filter(Boolean).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SVG(matrix, stream, margin, size) {
|
||||||
|
var X = matrix.length + 2 * margin;
|
||||||
|
stream.push('<svg xmlns="http://www.w3.org/2000/svg" ');
|
||||||
|
if (size > 0) {
|
||||||
|
var XY = X * size;
|
||||||
|
stream.push('width="' + XY + '" height="' + XY + '" ');
|
||||||
|
}
|
||||||
|
stream.push('viewBox="0 0 ' + X + ' ' + X + '">');
|
||||||
|
stream.push('<path d="');
|
||||||
|
pushSVGPath(matrix, stream, margin);
|
||||||
|
stream.push('"/></svg>');
|
||||||
|
stream.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EPS(matrix, stream, margin) {
|
||||||
|
var N = matrix.length;
|
||||||
|
var scale = 9;
|
||||||
|
var X = (N + 2 * margin) * scale;
|
||||||
|
stream.push([
|
||||||
|
'%!PS-Adobe-3.0 EPSF-3.0',
|
||||||
|
'%%BoundingBox: 0 0 ' + X + ' ' + X,
|
||||||
|
'/h { 0 rlineto } bind def',
|
||||||
|
'/v { 0 exch neg rlineto } bind def',
|
||||||
|
'/M { neg ' + (N + margin) + ' add moveto } bind def',
|
||||||
|
'/z { closepath } bind def',
|
||||||
|
scale + ' ' + scale + ' scale',
|
||||||
|
''
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
matrix2path(matrix).forEach(function(subpath) {
|
||||||
|
var res = '';
|
||||||
|
for (var k = 0; k < subpath.length; k++) {
|
||||||
|
var item = subpath[k];
|
||||||
|
switch (item[0]) {
|
||||||
|
case 'M':
|
||||||
|
res += (item[1] + margin) + ' ' + item[2] + ' M ';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res += item[1] + ' ' + item[0] + ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += 'z\n';
|
||||||
|
stream.push(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.push('fill\n%%EOF\n');
|
||||||
|
stream.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PDF(matrix, stream, margin) {
|
||||||
|
// TODO deflate
|
||||||
|
var N = matrix.length;
|
||||||
|
var scale = 9;
|
||||||
|
var X = (N + 2 * margin) * scale;
|
||||||
|
var data = [
|
||||||
|
'%PDF-1.0\n\n',
|
||||||
|
'1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n',
|
||||||
|
'2 0 obj << /Type /Pages /Count 1 /Kids [ 3 0 R ] >> endobj\n',
|
||||||
|
];
|
||||||
|
data.push('3 0 obj << /Type /Page /Parent 2 0 R /Resources <<>> ' +
|
||||||
|
'/Contents 4 0 R /MediaBox [ 0 0 ' + X + ' ' + X + ' ] >> endobj\n');
|
||||||
|
|
||||||
|
var path = scale + ' 0 0 ' + scale + ' 0 0 cm\n';
|
||||||
|
path += matrix2path(matrix).map(function(subpath) {
|
||||||
|
var res = '';
|
||||||
|
var x, y;
|
||||||
|
for (var k = 0; k < subpath.length; k++) {
|
||||||
|
var item = subpath[k];
|
||||||
|
switch (item[0]) {
|
||||||
|
case 'M':
|
||||||
|
x = item[1] + margin;
|
||||||
|
y = N - item[2] + margin;
|
||||||
|
res += x + ' ' + y + ' m ';
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
x += item[1];
|
||||||
|
res += x + ' ' + y + ' l ';
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
y -= item[1];
|
||||||
|
res += x + ' ' + y + ' l ';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += 'h';
|
||||||
|
return res;
|
||||||
|
}).join('\n');
|
||||||
|
path += '\nf\n';
|
||||||
|
data.push('4 0 obj << /Length ' + path.length + ' >> stream\n' +
|
||||||
|
path + 'endstream\nendobj\n');
|
||||||
|
|
||||||
|
var xref = 'xref\n0 5\n0000000000 65535 f \n';
|
||||||
|
for (var i = 1, l = data[0].length; i < 5; i++) {
|
||||||
|
xref += ('0000000000' + l).substr(-10) + ' 00000 n \n';
|
||||||
|
l += data[i].length;
|
||||||
|
}
|
||||||
|
data.push(
|
||||||
|
xref,
|
||||||
|
'trailer << /Root 1 0 R /Size 5 >>\n',
|
||||||
|
'startxref\n' + l + '\n%%EOF\n'
|
||||||
|
);
|
||||||
|
stream.push(data.join(''));
|
||||||
|
stream.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
svg: SVG,
|
||||||
|
eps: EPS,
|
||||||
|
pdf: PDF,
|
||||||
|
svg_object: SVG_object
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user