Compare commits

..

No commits in common. "ae6e87aadc804020ff90ddb4ab37514dcf32fb24" and "adaec81731ce28758f74c2fb0a99d594c173baef" have entirely different histories.

14 changed files with 1488 additions and 321 deletions

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
config.json
out/

292
gen.go
View File

@ -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
View 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
View File

@ -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
View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}