Output QR Code to Make Login Easier
This commit is contained in:
parent
12302ba1bf
commit
a82765244e
|
@ -23,13 +23,22 @@ const (
|
||||||
callbackStoreURL = "https://login.cloudflareaccess.org/"
|
callbackStoreURL = "https://login.cloudflareaccess.org/"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
outputQrcodeFlag = &cli.BoolFlag{
|
||||||
|
Name: "output-qr-code",
|
||||||
|
Usage: "Output a QR Code containing a link to tunnel login authorization workflow",
|
||||||
|
Aliases: []string{"qr"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func buildLoginSubcommand(hidden bool) *cli.Command {
|
func buildLoginSubcommand(hidden bool) *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "login",
|
Name: "login",
|
||||||
Action: cliutil.ConfiguredAction(login),
|
Action: cliutil.ConfiguredAction(login),
|
||||||
Usage: "Generate a configuration file with your login details",
|
Usage: "Generate a configuration file with your login details",
|
||||||
ArgsUsage: " ",
|
UsageText: "cloudflared tunnel [tunnel command options] login [subcommand options]",
|
||||||
Hidden: hidden,
|
Hidden: hidden,
|
||||||
|
Flags: []cli.Flag{outputQrcodeFlag},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +66,7 @@ func login(c *cli.Context) error {
|
||||||
callbackStoreURL,
|
callbackStoreURL,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
c.Bool("output-qr-code"),
|
||||||
log,
|
log,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -19,6 +19,7 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/lucas-clemente/quic-go v0.24.0
|
github.com/lucas-clemente/quic-go v0.24.0
|
||||||
github.com/mattn/go-colorable v0.1.8
|
github.com/mattn/go-colorable v0.1.8
|
||||||
|
github.com/mdp/qrterminal/v3 v3.0.0
|
||||||
github.com/miekg/dns v1.1.45
|
github.com/miekg/dns v1.1.45
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
@ -93,6 +94,7 @@ require (
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
rsc.io/qr v0.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
||||||
|
|
9
go.sum
9
go.sum
|
@ -399,8 +399,10 @@ github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSI
|
||||||
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
|
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM=
|
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM=
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
|
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
@ -408,6 +410,10 @@ github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuuj
|
||||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c=
|
||||||
|
github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ=
|
||||||
|
github.com/mdp/qrterminal/v3 v3.0.0 h1:ywQqLRBXWTktytQNDKFjhAvoGkLVN3J2tAFZ0kMd9xQ=
|
||||||
|
github.com/mdp/qrterminal/v3 v3.0.0/go.mod h1:NJpfAs7OAm77Dy8EkWrtE4aq+cE6McoLXlBqXQEwvE0=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk=
|
github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk=
|
||||||
|
@ -732,6 +738,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -1093,6 +1100,8 @@ k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lV
|
||||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||||
|
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
|
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
|
||||||
|
|
|
@ -218,7 +218,7 @@ func getTokensFromEdge(appURL *url.URL, appTokenPath, orgTokenPath string, useHo
|
||||||
// this weird parameter is the resource name (token) and the key/value
|
// this weird parameter is the resource name (token) and the key/value
|
||||||
// we want to send to the transfer service. the key is token and the value
|
// we want to send to the transfer service. the key is token and the value
|
||||||
// is blank (basically just the id generated in the transfer service)
|
// is blank (basically just the id generated in the transfer service)
|
||||||
resourceData, err := RunTransfer(appURL, keyName, keyName, "", true, useHostOnly, log)
|
resourceData, err := RunTransfer(appURL, keyName, keyName, "", true, useHostOnly, false, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to run transfer service")
|
return "", errors.Wrap(err, "failed to run transfer service")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mdp/qrterminal/v3"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
@ -25,7 +26,7 @@ const (
|
||||||
// The "dance" we refer to is building a HTTP request, opening that in a browser waiting for
|
// The "dance" we refer to is building a HTTP request, opening that in a browser waiting for
|
||||||
// the user to complete an action, while it long polls in the background waiting for an
|
// the user to complete an action, while it long polls in the background waiting for an
|
||||||
// action to be completed to download the resource.
|
// action to be completed to download the resource.
|
||||||
func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, log *zerolog.Logger) ([]byte, error) {
|
func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, outputQrCode bool, log *zerolog.Logger) ([]byte, error) {
|
||||||
encrypterClient, err := NewEncrypter("cloudflared_priv.pem", "cloudflared_pub.pem")
|
encrypterClient, err := NewEncrypter("cloudflared_priv.pem", "cloudflared_pub.pem")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -37,10 +38,16 @@ func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEn
|
||||||
|
|
||||||
// See AUTH-1423 for why we use stderr (the way git wraps ssh)
|
// See AUTH-1423 for why we use stderr (the way git wraps ssh)
|
||||||
err = OpenBrowser(requestURL)
|
err = OpenBrowser(requestURL)
|
||||||
|
qrcode := ""
|
||||||
|
if outputQrCode {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
qrterminal.GenerateHalfBlock(requestURL, qrterminal.H, buf)
|
||||||
|
qrcode = fmt.Sprintf("%s\n", buf.String())
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Please open the following URL and log in with your Cloudflare account:\n\n%s\n\nLeave cloudflared running to download the %s automatically.\n", requestURL, resourceName)
|
fmt.Fprintf(os.Stderr, "Please open the following URL and log in with your Cloudflare account:\n\n%s%s\n\nLeave cloudflared running to download the %s automatically.\n", qrcode, requestURL, resourceName)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(os.Stderr, "A browser window should have opened at the following URL:\n\n%s\n\nIf the browser failed to open, please visit the URL above directly in your browser.\n", requestURL)
|
fmt.Fprintf(os.Stderr, "A browser window should have opened at the following URL:\n\n%s%s\n\nIf the browser failed to open, please visit the URL above directly in your browser.\n", qrcode, requestURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
var resourceData []byte
|
var resourceData []byte
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source $HOME/.config/goreleaser.env
|
||||||
|
|
||||||
|
if [ -x "$(command -v goreleaser)" ]; then
|
||||||
|
goreleaser @$
|
||||||
|
else
|
||||||
|
GO_SRC="/app"
|
||||||
|
docker run --rm \
|
||||||
|
-v $PWD:$GO_SRC \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-w $GO_SRC \
|
||||||
|
-e GITHUB_TOKEN=$GITHUB_TOKEN \
|
||||||
|
-e DOCKER_USERNAME=$DOCKER_USERNAME \
|
||||||
|
-e DOCKER_PASSWORD=$DOCKER_PASSWORD \
|
||||||
|
goreleaser/goreleaser $@
|
||||||
|
fi
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# This is an example goreleaser.yaml file with some sane defaults.
|
||||||
|
# Make sure to check the documentation at http://goreleaser.com
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
# you may remove this if you don't use vgo
|
||||||
|
- go mod download
|
||||||
|
# you may remove this if you don't need go generate
|
||||||
|
- go generate ./...
|
||||||
|
builds:
|
||||||
|
-
|
||||||
|
main: ./cmd/qrterminal/main.go
|
||||||
|
binary: qrterminal
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- 386
|
||||||
|
- arm
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- darwin
|
||||||
|
- windows
|
||||||
|
ignore:
|
||||||
|
- goos: darwin
|
||||||
|
goarch: 386
|
||||||
|
- goos: windows
|
||||||
|
goarch: arm
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- replacements:
|
||||||
|
386: i386
|
||||||
|
amd64: x86_64
|
||||||
|
checksum:
|
||||||
|
name_template: 'checksums.txt'
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ .Tag }}-next"
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^docs:'
|
||||||
|
- '^test:'
|
||||||
|
brew:
|
||||||
|
name: qrterminal
|
||||||
|
github:
|
||||||
|
owner: mdp
|
||||||
|
name: homebrew-tap
|
||||||
|
commit_author:
|
||||||
|
name: mdp
|
||||||
|
email: m@mdp.im
|
||||||
|
homepage: https://github.com/mdp/qrterminal
|
||||||
|
description: 'Create and display QR codes on the command line'
|
||||||
|
dockers:
|
||||||
|
-
|
||||||
|
image_templates:
|
||||||
|
- "mpercival/qrterminal:latest"
|
||||||
|
- "mpercival/qrterminal:{{ .Tag }}"
|
|
@ -0,0 +1,4 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- tip
|
|
@ -0,0 +1,27 @@
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
Adjust go.mod to include required version string
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
Add goreleaser and release to Homebrew and Github
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
Add a command line tool and QuietZone around QRCode
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
Add go.mod
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
Update to add a quiet zone border to the QR Code - #5 and fixed by [WindomZ](https://github.com/WindomZ) #8
|
||||||
|
|
||||||
|
- This can be configured with the `QuietZone int` option
|
||||||
|
- Defaults to 4 'pixels' wide to match the QR Code spec
|
||||||
|
- This alters the size of the barcode considerably and is therefore a breaking change, resulting in a bump to v1.0.0
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
Fix direction of the qr code #6 by (https://github.com/mattn)
|
|
@ -0,0 +1,3 @@
|
||||||
|
FROM scratch
|
||||||
|
COPY qrterminal /
|
||||||
|
ENTRYPOINT ["/qrterminal"]
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright 2019 Mark Percival <m@mdp.im>
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,13 @@
|
||||||
|
APP ?= "./cmd/qrterminal"
|
||||||
|
|
||||||
|
build:
|
||||||
|
@go build "$(APP)"
|
||||||
|
|
||||||
|
release:
|
||||||
|
./.goreleaser release --rm-dist
|
||||||
|
|
||||||
|
reltest:
|
||||||
|
./.goreleaser release --snapshot --rm-dist --skip-publish
|
||||||
|
|
||||||
|
test:
|
||||||
|
@go test -cover
|
|
@ -0,0 +1,103 @@
|
||||||
|
# QRCode Terminal
|
||||||
|
|
||||||
|
[![Build Status](https://api.travis-ci.org/mdp/qrterminal.svg)](https://travis-ci.org/mdp/qrterminal)
|
||||||
|
|
||||||
|
A golang library for generating QR codes in the terminal.
|
||||||
|
|
||||||
|
Originally this was a port of the [NodeJS version](https://github.com/gtanner/qrcode-terminal). Recently it's been updated to allow for smaller code generation using ASCII 'half blocks'
|
||||||
|
|
||||||
|
## Example
|
||||||
|
Full size ASCII block QR Code:
|
||||||
|
<img src="https://user-images.githubusercontent.com/2868/37992336-0ba06b56-31d1-11e8-9d32-5c6bb008dc74.png" alt="alt text" width="225" height="225">
|
||||||
|
|
||||||
|
Smaller 'half blocks' in the terminal:
|
||||||
|
<img src="https://user-images.githubusercontent.com/2868/37992371-243d4238-31d1-11e8-92f8-e34a794b21af.png" alt="alt text" width="225" height="225">
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
For command line usage [see below](https://github.com/mdp/qrterminal#command-line), or grab the binary from the [releases page](https://github.com/mdp/qrterminal/releases)
|
||||||
|
|
||||||
|
As a library in an application
|
||||||
|
|
||||||
|
`go get github.com/mdp/qrterminal/v3`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/mdp/qrterminal/v3"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Generate a 'dense' qrcode with the 'Low' level error correction and write it to Stdout
|
||||||
|
qrterminal.Generate("https://github.com/mdp/qrterminal", qrterminal.L, os.Stdout)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### More complicated
|
||||||
|
|
||||||
|
Large Inverted barcode with medium redundancy and a 1 pixel border
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/mdp/qrterminal/v3"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := qrterminal.Config{
|
||||||
|
Level: qrterminal.M,
|
||||||
|
Writer: os.Stdout,
|
||||||
|
BlackChar: qrterminal.WHITE,
|
||||||
|
WhiteChar: qrterminal.BLACK,
|
||||||
|
QuietZone: 1,
|
||||||
|
}
|
||||||
|
qrterminal.GenerateWithConfig("https://github.com/mdp/qrterminal", config)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
HalfBlock barcode with medium redundancy
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/mdp/qrterminal/v3"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := qrterminal.Config{
|
||||||
|
HalfBlocks: true,
|
||||||
|
Level: qrterminal.M,
|
||||||
|
Writer: os.Stdout,
|
||||||
|
}
|
||||||
|
qrterminal.Generate("https://github.com/mdp/qrterminal", config)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Command Line
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
|
||||||
|
OSX: `brew install mdp/tap/qrterminal`
|
||||||
|
|
||||||
|
Others: Download from the [releases page](https://github.com/mdp/qrterminal/releases)
|
||||||
|
|
||||||
|
Source: `go get -u github.com/mdp/qrterminal/v3/cmd/qrterminal`
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
Print out a basic QR code in your terminal:
|
||||||
|
`qrterminal https://github.com/mdp/qrterminal`
|
||||||
|
|
||||||
|
Using 'medium' error correction:
|
||||||
|
`qrterminal https://github.com/mdp/qrterminal -l M`
|
||||||
|
|
||||||
|
Or just use Docker: `docker run --rm mpercival/qrterminal:latest 'https://github.com/mdp/qrterminal'`
|
||||||
|
|
||||||
|
### Contributors/Credits:
|
||||||
|
|
||||||
|
- [Mark Percival](https://github.com/mdp)
|
||||||
|
- [Matthew Kennerly](https://github.com/mtkennerly)
|
||||||
|
- [Viric](https://github.com/viric)
|
||||||
|
- [WindomZ](https://github.com/WindomZ)
|
||||||
|
- [mattn](https://github.com/mattn)
|
|
@ -0,0 +1,153 @@
|
||||||
|
package qrterminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"rsc.io/qr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WHITE = "\033[47m \033[0m"
|
||||||
|
const BLACK = "\033[40m \033[0m"
|
||||||
|
|
||||||
|
// Use ascii blocks to form the QR Code
|
||||||
|
const BLACK_WHITE = "▄"
|
||||||
|
const BLACK_BLACK = " "
|
||||||
|
const WHITE_BLACK = "▀"
|
||||||
|
const WHITE_WHITE = "█"
|
||||||
|
|
||||||
|
// Level - the QR Code's redundancy level
|
||||||
|
const H = qr.H
|
||||||
|
const M = qr.M
|
||||||
|
const L = qr.L
|
||||||
|
|
||||||
|
// default is 4-pixel-wide white quiet zone
|
||||||
|
const QUIET_ZONE = 4
|
||||||
|
|
||||||
|
//Config for generating a barcode
|
||||||
|
type Config struct {
|
||||||
|
Level qr.Level
|
||||||
|
Writer io.Writer
|
||||||
|
HalfBlocks bool
|
||||||
|
BlackChar string
|
||||||
|
BlackWhiteChar string
|
||||||
|
WhiteChar string
|
||||||
|
WhiteBlackChar string
|
||||||
|
QuietZone int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) writeFullBlocks(w io.Writer, code *qr.Code) {
|
||||||
|
white := c.WhiteChar
|
||||||
|
black := c.BlackChar
|
||||||
|
|
||||||
|
// Frame the barcode in a 1 pixel border
|
||||||
|
w.Write([]byte(stringRepeat(stringRepeat(white,
|
||||||
|
code.Size+c.QuietZone*2)+"\n", c.QuietZone))) // top border
|
||||||
|
for i := 0; i <= code.Size; i++ {
|
||||||
|
w.Write([]byte(stringRepeat(white, c.QuietZone))) // left border
|
||||||
|
for j := 0; j <= code.Size; j++ {
|
||||||
|
if code.Black(j, i) {
|
||||||
|
w.Write([]byte(black))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(white))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte(stringRepeat(white, c.QuietZone-1) + "\n")) // right border
|
||||||
|
}
|
||||||
|
w.Write([]byte(stringRepeat(stringRepeat(white,
|
||||||
|
code.Size+c.QuietZone*2)+"\n", c.QuietZone-1))) // bottom border
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) writeHalfBlocks(w io.Writer, code *qr.Code) {
|
||||||
|
ww := c.WhiteChar
|
||||||
|
bb := c.BlackChar
|
||||||
|
wb := c.WhiteBlackChar
|
||||||
|
bw := c.BlackWhiteChar
|
||||||
|
// Frame the barcode in a 4 pixel border
|
||||||
|
// top border
|
||||||
|
if c.QuietZone%2 != 0 {
|
||||||
|
w.Write([]byte(stringRepeat(bw, code.Size+c.QuietZone*2) + "\n"))
|
||||||
|
w.Write([]byte(stringRepeat(stringRepeat(ww,
|
||||||
|
code.Size+c.QuietZone*2)+"\n", c.QuietZone/2)))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(stringRepeat(stringRepeat(ww,
|
||||||
|
code.Size+c.QuietZone*2)+"\n", c.QuietZone/2)))
|
||||||
|
}
|
||||||
|
for i := 0; i <= code.Size; i += 2 {
|
||||||
|
w.Write([]byte(stringRepeat(ww, c.QuietZone))) // left border
|
||||||
|
for j := 0; j <= code.Size; j++ {
|
||||||
|
next_black := false
|
||||||
|
if i+1 < code.Size {
|
||||||
|
next_black = code.Black(j, i+1)
|
||||||
|
}
|
||||||
|
curr_black := code.Black(j, i)
|
||||||
|
if curr_black && next_black {
|
||||||
|
w.Write([]byte(bb))
|
||||||
|
} else if curr_black && !next_black {
|
||||||
|
w.Write([]byte(bw))
|
||||||
|
} else if !curr_black && !next_black {
|
||||||
|
w.Write([]byte(ww))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(wb))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte(stringRepeat(ww, c.QuietZone-1) + "\n")) // right border
|
||||||
|
}
|
||||||
|
// bottom border
|
||||||
|
if c.QuietZone%2 == 0 {
|
||||||
|
w.Write([]byte(stringRepeat(stringRepeat(ww,
|
||||||
|
code.Size+c.QuietZone*2)+"\n", c.QuietZone/2-1)))
|
||||||
|
w.Write([]byte(stringRepeat(wb, code.Size+c.QuietZone*2) + "\n"))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(stringRepeat(stringRepeat(ww,
|
||||||
|
code.Size+c.QuietZone*2)+"\n", c.QuietZone/2)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringRepeat(s string, count int) string {
|
||||||
|
if count <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Repeat(s, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateWithConfig expects a string to encode and a config
|
||||||
|
func GenerateWithConfig(text string, config Config) {
|
||||||
|
if config.QuietZone < 1 {
|
||||||
|
config.QuietZone = 1 // at least 1-pixel-wide white quiet zone
|
||||||
|
}
|
||||||
|
w := config.Writer
|
||||||
|
code, _ := qr.Encode(text, config.Level)
|
||||||
|
if config.HalfBlocks {
|
||||||
|
config.writeHalfBlocks(w, code)
|
||||||
|
} else {
|
||||||
|
config.writeFullBlocks(w, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a QR Code and write it out to io.Writer
|
||||||
|
func Generate(text string, l qr.Level, w io.Writer) {
|
||||||
|
config := Config{
|
||||||
|
Level: l,
|
||||||
|
Writer: w,
|
||||||
|
BlackChar: BLACK,
|
||||||
|
WhiteChar: WHITE,
|
||||||
|
QuietZone: QUIET_ZONE,
|
||||||
|
}
|
||||||
|
GenerateWithConfig(text, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a QR Code with half blocks and write it out to io.Writer
|
||||||
|
func GenerateHalfBlock(text string, l qr.Level, w io.Writer) {
|
||||||
|
config := Config{
|
||||||
|
Level: l,
|
||||||
|
Writer: w,
|
||||||
|
HalfBlocks: true,
|
||||||
|
BlackChar: BLACK_BLACK,
|
||||||
|
WhiteBlackChar: WHITE_BLACK,
|
||||||
|
WhiteChar: WHITE_WHITE,
|
||||||
|
BlackWhiteChar: BLACK_WHITE,
|
||||||
|
QuietZone: QUIET_ZONE,
|
||||||
|
}
|
||||||
|
GenerateWithConfig(text, config)
|
||||||
|
}
|
|
@ -232,6 +232,9 @@ github.com/mattn/go-runewidth
|
||||||
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/matttproud/golang_protobuf_extensions/pbutil
|
github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||||
|
# github.com/mdp/qrterminal/v3 v3.0.0
|
||||||
|
## explicit
|
||||||
|
github.com/mdp/qrterminal/v3
|
||||||
# github.com/miekg/dns v1.1.45
|
# github.com/miekg/dns v1.1.45
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/miekg/dns
|
github.com/miekg/dns
|
||||||
|
@ -548,6 +551,11 @@ gopkg.in/yaml.v2
|
||||||
# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/yaml.v3
|
gopkg.in/yaml.v3
|
||||||
|
# rsc.io/qr v0.2.0
|
||||||
|
## explicit
|
||||||
|
rsc.io/qr
|
||||||
|
rsc.io/qr/coding
|
||||||
|
rsc.io/qr/gf256
|
||||||
# zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
# zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
zombiezen.com/go/capnproto2
|
zombiezen.com/go/capnproto2
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,3 @@
|
||||||
|
Basic QR encoder.
|
||||||
|
|
||||||
|
go get [-u] rsc.io/qr
|
|
@ -0,0 +1,815 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package coding implements low-level QR coding details.
|
||||||
|
package coding // import "rsc.io/qr/coding"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"rsc.io/qr/gf256"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Field is the field for QR error correction.
|
||||||
|
var Field = gf256.NewField(0x11d, 2)
|
||||||
|
|
||||||
|
// A Version represents a QR version.
|
||||||
|
// The version specifies the size of the QR code:
|
||||||
|
// a QR code with version v has 4v+17 pixels on a side.
|
||||||
|
// Versions number from 1 to 40: the larger the version,
|
||||||
|
// the more information the code can store.
|
||||||
|
type Version int
|
||||||
|
|
||||||
|
const MinVersion = 1
|
||||||
|
const MaxVersion = 40
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
return strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) sizeClass() int {
|
||||||
|
if v <= 9 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if v <= 26 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataBytes returns the number of data bytes that can be
|
||||||
|
// stored in a QR code with the given version and level.
|
||||||
|
func (v Version) DataBytes(l Level) int {
|
||||||
|
vt := &vtab[v]
|
||||||
|
lev := &vt.level[l]
|
||||||
|
return vt.bytes - lev.nblock*lev.check
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding implements a QR data encoding scheme.
|
||||||
|
// The implementations--Numeric, Alphanumeric, and String--specify
|
||||||
|
// the character set and the mapping from UTF-8 to code bits.
|
||||||
|
// The more restrictive the mode, the fewer code bits are needed.
|
||||||
|
type Encoding interface {
|
||||||
|
Check() error
|
||||||
|
Bits(v Version) int
|
||||||
|
Encode(b *Bits, v Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bits struct {
|
||||||
|
b []byte
|
||||||
|
nbit int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Reset() {
|
||||||
|
b.b = b.b[:0]
|
||||||
|
b.nbit = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Bits() int {
|
||||||
|
return b.nbit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Bytes() []byte {
|
||||||
|
if b.nbit%8 != 0 {
|
||||||
|
panic("fractional byte")
|
||||||
|
}
|
||||||
|
return b.b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Append(p []byte) {
|
||||||
|
if b.nbit%8 != 0 {
|
||||||
|
panic("fractional byte")
|
||||||
|
}
|
||||||
|
b.b = append(b.b, p...)
|
||||||
|
b.nbit += 8 * len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Write(v uint, nbit int) {
|
||||||
|
for nbit > 0 {
|
||||||
|
n := nbit
|
||||||
|
if n > 8 {
|
||||||
|
n = 8
|
||||||
|
}
|
||||||
|
if b.nbit%8 == 0 {
|
||||||
|
b.b = append(b.b, 0)
|
||||||
|
} else {
|
||||||
|
m := -b.nbit & 7
|
||||||
|
if n > m {
|
||||||
|
n = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.nbit += n
|
||||||
|
sh := uint(nbit - n)
|
||||||
|
b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7))
|
||||||
|
v -= v >> sh << sh
|
||||||
|
nbit -= n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Num is the encoding for numeric data.
|
||||||
|
// The only valid characters are the decimal digits 0 through 9.
|
||||||
|
type Num string
|
||||||
|
|
||||||
|
func (s Num) String() string {
|
||||||
|
return fmt.Sprintf("Num(%#q)", string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Num) Check() error {
|
||||||
|
for _, c := range s {
|
||||||
|
if c < '0' || '9' < c {
|
||||||
|
return fmt.Errorf("non-numeric string %#q", string(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var numLen = [3]int{10, 12, 14}
|
||||||
|
|
||||||
|
func (s Num) Bits(v Version) int {
|
||||||
|
return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Num) Encode(b *Bits, v Version) {
|
||||||
|
b.Write(1, 4)
|
||||||
|
b.Write(uint(len(s)), numLen[v.sizeClass()])
|
||||||
|
var i int
|
||||||
|
for i = 0; i+3 <= len(s); i += 3 {
|
||||||
|
w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0')
|
||||||
|
b.Write(w, 10)
|
||||||
|
}
|
||||||
|
switch len(s) - i {
|
||||||
|
case 1:
|
||||||
|
w := uint(s[i] - '0')
|
||||||
|
b.Write(w, 4)
|
||||||
|
case 2:
|
||||||
|
w := uint(s[i]-'0')*10 + uint(s[i+1]-'0')
|
||||||
|
b.Write(w, 7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alpha is the encoding for alphanumeric data.
|
||||||
|
// The valid characters are 0-9A-Z$%*+-./: and space.
|
||||||
|
type Alpha string
|
||||||
|
|
||||||
|
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
||||||
|
|
||||||
|
func (s Alpha) String() string {
|
||||||
|
return fmt.Sprintf("Alpha(%#q)", string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Alpha) Check() error {
|
||||||
|
for _, c := range s {
|
||||||
|
if strings.IndexRune(alphabet, c) < 0 {
|
||||||
|
return fmt.Errorf("non-alphanumeric string %#q", string(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var alphaLen = [3]int{9, 11, 13}
|
||||||
|
|
||||||
|
func (s Alpha) Bits(v Version) int {
|
||||||
|
return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Alpha) Encode(b *Bits, v Version) {
|
||||||
|
b.Write(2, 4)
|
||||||
|
b.Write(uint(len(s)), alphaLen[v.sizeClass()])
|
||||||
|
var i int
|
||||||
|
for i = 0; i+2 <= len(s); i += 2 {
|
||||||
|
w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 +
|
||||||
|
uint(strings.IndexRune(alphabet, rune(s[i+1])))
|
||||||
|
b.Write(w, 11)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < len(s) {
|
||||||
|
w := uint(strings.IndexRune(alphabet, rune(s[i])))
|
||||||
|
b.Write(w, 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the encoding for 8-bit data. All bytes are valid.
|
||||||
|
type String string
|
||||||
|
|
||||||
|
func (s String) String() string {
|
||||||
|
return fmt.Sprintf("String(%#q)", string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s String) Check() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringLen = [3]int{8, 16, 16}
|
||||||
|
|
||||||
|
func (s String) Bits(v Version) int {
|
||||||
|
return 4 + stringLen[v.sizeClass()] + 8*len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s String) Encode(b *Bits, v Version) {
|
||||||
|
b.Write(4, 4)
|
||||||
|
b.Write(uint(len(s)), stringLen[v.sizeClass()])
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
b.Write(uint(s[i]), 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Pixel describes a single pixel in a QR code.
|
||||||
|
type Pixel uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Black Pixel = 1 << iota
|
||||||
|
Invert
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p Pixel) Offset() uint {
|
||||||
|
return uint(p >> 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OffsetPixel(o uint) Pixel {
|
||||||
|
return Pixel(o << 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PixelRole) Pixel() Pixel {
|
||||||
|
return Pixel(r << 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pixel) Role() PixelRole {
|
||||||
|
return PixelRole(p>>2) & 15
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pixel) String() string {
|
||||||
|
s := p.Role().String()
|
||||||
|
if p&Black != 0 {
|
||||||
|
s += "+black"
|
||||||
|
}
|
||||||
|
if p&Invert != 0 {
|
||||||
|
s += "+invert"
|
||||||
|
}
|
||||||
|
s += "+" + strconv.FormatUint(uint64(p.Offset()), 10)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PixelRole describes the role of a QR pixel.
|
||||||
|
type PixelRole uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ PixelRole = iota
|
||||||
|
Position // position squares (large)
|
||||||
|
Alignment // alignment squares (small)
|
||||||
|
Timing // timing strip between position squares
|
||||||
|
Format // format metadata
|
||||||
|
PVersion // version pattern
|
||||||
|
Unused // unused pixel
|
||||||
|
Data // data bit
|
||||||
|
Check // error correction check bit
|
||||||
|
Extra
|
||||||
|
)
|
||||||
|
|
||||||
|
var roles = []string{
|
||||||
|
"",
|
||||||
|
"position",
|
||||||
|
"alignment",
|
||||||
|
"timing",
|
||||||
|
"format",
|
||||||
|
"pversion",
|
||||||
|
"unused",
|
||||||
|
"data",
|
||||||
|
"check",
|
||||||
|
"extra",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PixelRole) String() string {
|
||||||
|
if Position <= r && r <= Check {
|
||||||
|
return roles[r]
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Level represents a QR error correction level.
|
||||||
|
// From least to most tolerant of errors, they are L, M, Q, H.
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
L Level = iota
|
||||||
|
M
|
||||||
|
Q
|
||||||
|
H
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l Level) String() string {
|
||||||
|
if L <= l && l <= H {
|
||||||
|
return "LMQH"[l : l+1]
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Code is a square pixel grid.
|
||||||
|
type Code struct {
|
||||||
|
Bitmap []byte // 1 is black, 0 is white
|
||||||
|
Size int // number of pixels on a side
|
||||||
|
Stride int // number of bytes per row
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Code) Black(x, y int) bool {
|
||||||
|
return 0 <= x && x < c.Size && 0 <= y && y < c.Size &&
|
||||||
|
c.Bitmap[y*c.Stride+x/8]&(1<<uint(7-x&7)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Mask describes a mask that is applied to the QR
|
||||||
|
// code to avoid QR artifacts being interpreted as
|
||||||
|
// alignment and timing patterns (such as the squares
|
||||||
|
// in the corners). Valid masks are integers from 0 to 7.
|
||||||
|
type Mask int
|
||||||
|
|
||||||
|
// http://www.swetake.com/qr/qr5_en.html
|
||||||
|
var mfunc = []func(int, int) bool{
|
||||||
|
func(i, j int) bool { return (i+j)%2 == 0 },
|
||||||
|
func(i, j int) bool { return i%2 == 0 },
|
||||||
|
func(i, j int) bool { return j%3 == 0 },
|
||||||
|
func(i, j int) bool { return (i+j)%3 == 0 },
|
||||||
|
func(i, j int) bool { return (i/2+j/3)%2 == 0 },
|
||||||
|
func(i, j int) bool { return i*j%2+i*j%3 == 0 },
|
||||||
|
func(i, j int) bool { return (i*j%2+i*j%3)%2 == 0 },
|
||||||
|
func(i, j int) bool { return (i*j%3+(i+j)%2)%2 == 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Mask) Invert(y, x int) bool {
|
||||||
|
if m < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return mfunc[m](y, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Plan describes how to construct a QR code
|
||||||
|
// with a specific version, level, and mask.
|
||||||
|
type Plan struct {
|
||||||
|
Version Version
|
||||||
|
Level Level
|
||||||
|
Mask Mask
|
||||||
|
|
||||||
|
DataBytes int // number of data bytes
|
||||||
|
CheckBytes int // number of error correcting (checksum) bytes
|
||||||
|
Blocks int // number of data blocks
|
||||||
|
|
||||||
|
Pixel [][]Pixel // pixel map
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPlan returns a Plan for a QR code with the given
|
||||||
|
// version, level, and mask.
|
||||||
|
func NewPlan(version Version, level Level, mask Mask) (*Plan, error) {
|
||||||
|
p, err := vplan(version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := fplan(level, mask, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := lplan(version, level, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := mplan(mask, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Pad(n int) {
|
||||||
|
if n < 0 {
|
||||||
|
panic("qr: invalid pad size")
|
||||||
|
}
|
||||||
|
if n <= 4 {
|
||||||
|
b.Write(0, n)
|
||||||
|
} else {
|
||||||
|
b.Write(0, 4)
|
||||||
|
n -= 4
|
||||||
|
n -= -b.Bits() & 7
|
||||||
|
b.Write(0, -b.Bits()&7)
|
||||||
|
pad := n / 8
|
||||||
|
for i := 0; i < pad; i += 2 {
|
||||||
|
b.Write(0xec, 8)
|
||||||
|
if i+1 >= pad {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.Write(0x11, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) AddCheckBytes(v Version, l Level) {
|
||||||
|
nd := v.DataBytes(l)
|
||||||
|
if b.nbit < nd*8 {
|
||||||
|
b.Pad(nd*8 - b.nbit)
|
||||||
|
}
|
||||||
|
if b.nbit != nd*8 {
|
||||||
|
panic("qr: too much data")
|
||||||
|
}
|
||||||
|
|
||||||
|
dat := b.Bytes()
|
||||||
|
vt := &vtab[v]
|
||||||
|
lev := &vt.level[l]
|
||||||
|
db := nd / lev.nblock
|
||||||
|
extra := nd % lev.nblock
|
||||||
|
chk := make([]byte, lev.check)
|
||||||
|
rs := gf256.NewRSEncoder(Field, lev.check)
|
||||||
|
for i := 0; i < lev.nblock; i++ {
|
||||||
|
if i == lev.nblock-extra {
|
||||||
|
db++
|
||||||
|
}
|
||||||
|
rs.ECC(dat[:db], chk)
|
||||||
|
b.Append(chk)
|
||||||
|
dat = dat[db:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.Bytes()) != vt.bytes {
|
||||||
|
panic("qr: internal error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plan) Encode(text ...Encoding) (*Code, error) {
|
||||||
|
var b Bits
|
||||||
|
for _, t := range text {
|
||||||
|
if err := t.Check(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.Encode(&b, p.Version)
|
||||||
|
}
|
||||||
|
if b.Bits() > p.DataBytes*8 {
|
||||||
|
return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8)
|
||||||
|
}
|
||||||
|
b.AddCheckBytes(p.Version, p.Level)
|
||||||
|
bytes := b.Bytes()
|
||||||
|
|
||||||
|
// Now we have the checksum bytes and the data bytes.
|
||||||
|
// Construct the actual code.
|
||||||
|
c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7}
|
||||||
|
c.Bitmap = make([]byte, c.Stride*c.Size)
|
||||||
|
crow := c.Bitmap
|
||||||
|
for _, row := range p.Pixel {
|
||||||
|
for x, pix := range row {
|
||||||
|
switch pix.Role() {
|
||||||
|
case Data, Check:
|
||||||
|
o := pix.Offset()
|
||||||
|
if bytes[o/8]&(1<<uint(7-o&7)) != 0 {
|
||||||
|
pix ^= Black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pix&Black != 0 {
|
||||||
|
crow[x/8] |= 1 << uint(7-x&7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crow = crow[c.Stride:]
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A version describes metadata associated with a version.
|
||||||
|
type version struct {
|
||||||
|
apos int
|
||||||
|
astride int
|
||||||
|
bytes int
|
||||||
|
pattern int
|
||||||
|
level [4]level
|
||||||
|
}
|
||||||
|
|
||||||
|
type level struct {
|
||||||
|
nblock int
|
||||||
|
check int
|
||||||
|
}
|
||||||
|
|
||||||
|
var vtab = []version{
|
||||||
|
{},
|
||||||
|
{100, 100, 26, 0x0, [4]level{{1, 7}, {1, 10}, {1, 13}, {1, 17}}}, // 1
|
||||||
|
{16, 100, 44, 0x0, [4]level{{1, 10}, {1, 16}, {1, 22}, {1, 28}}}, // 2
|
||||||
|
{20, 100, 70, 0x0, [4]level{{1, 15}, {1, 26}, {2, 18}, {2, 22}}}, // 3
|
||||||
|
{24, 100, 100, 0x0, [4]level{{1, 20}, {2, 18}, {2, 26}, {4, 16}}}, // 4
|
||||||
|
{28, 100, 134, 0x0, [4]level{{1, 26}, {2, 24}, {4, 18}, {4, 22}}}, // 5
|
||||||
|
{32, 100, 172, 0x0, [4]level{{2, 18}, {4, 16}, {4, 24}, {4, 28}}}, // 6
|
||||||
|
{20, 16, 196, 0x7c94, [4]level{{2, 20}, {4, 18}, {6, 18}, {5, 26}}}, // 7
|
||||||
|
{22, 18, 242, 0x85bc, [4]level{{2, 24}, {4, 22}, {6, 22}, {6, 26}}}, // 8
|
||||||
|
{24, 20, 292, 0x9a99, [4]level{{2, 30}, {5, 22}, {8, 20}, {8, 24}}}, // 9
|
||||||
|
{26, 22, 346, 0xa4d3, [4]level{{4, 18}, {5, 26}, {8, 24}, {8, 28}}}, // 10
|
||||||
|
{28, 24, 404, 0xbbf6, [4]level{{4, 20}, {5, 30}, {8, 28}, {11, 24}}}, // 11
|
||||||
|
{30, 26, 466, 0xc762, [4]level{{4, 24}, {8, 22}, {10, 26}, {11, 28}}}, // 12
|
||||||
|
{32, 28, 532, 0xd847, [4]level{{4, 26}, {9, 22}, {12, 24}, {16, 22}}}, // 13
|
||||||
|
{24, 20, 581, 0xe60d, [4]level{{4, 30}, {9, 24}, {16, 20}, {16, 24}}}, // 14
|
||||||
|
{24, 22, 655, 0xf928, [4]level{{6, 22}, {10, 24}, {12, 30}, {18, 24}}}, // 15
|
||||||
|
{24, 24, 733, 0x10b78, [4]level{{6, 24}, {10, 28}, {17, 24}, {16, 30}}}, // 16
|
||||||
|
{28, 24, 815, 0x1145d, [4]level{{6, 28}, {11, 28}, {16, 28}, {19, 28}}}, // 17
|
||||||
|
{28, 26, 901, 0x12a17, [4]level{{6, 30}, {13, 26}, {18, 28}, {21, 28}}}, // 18
|
||||||
|
{28, 28, 991, 0x13532, [4]level{{7, 28}, {14, 26}, {21, 26}, {25, 26}}}, // 19
|
||||||
|
{32, 28, 1085, 0x149a6, [4]level{{8, 28}, {16, 26}, {20, 30}, {25, 28}}}, // 20
|
||||||
|
{26, 22, 1156, 0x15683, [4]level{{8, 28}, {17, 26}, {23, 28}, {25, 30}}}, // 21
|
||||||
|
{24, 24, 1258, 0x168c9, [4]level{{9, 28}, {17, 28}, {23, 30}, {34, 24}}}, // 22
|
||||||
|
{28, 24, 1364, 0x177ec, [4]level{{9, 30}, {18, 28}, {25, 30}, {30, 30}}}, // 23
|
||||||
|
{26, 26, 1474, 0x18ec4, [4]level{{10, 30}, {20, 28}, {27, 30}, {32, 30}}}, // 24
|
||||||
|
{30, 26, 1588, 0x191e1, [4]level{{12, 26}, {21, 28}, {29, 30}, {35, 30}}}, // 25
|
||||||
|
{28, 28, 1706, 0x1afab, [4]level{{12, 28}, {23, 28}, {34, 28}, {37, 30}}}, // 26
|
||||||
|
{32, 28, 1828, 0x1b08e, [4]level{{12, 30}, {25, 28}, {34, 30}, {40, 30}}}, // 27
|
||||||
|
{24, 24, 1921, 0x1cc1a, [4]level{{13, 30}, {26, 28}, {35, 30}, {42, 30}}}, // 28
|
||||||
|
{28, 24, 2051, 0x1d33f, [4]level{{14, 30}, {28, 28}, {38, 30}, {45, 30}}}, // 29
|
||||||
|
{24, 26, 2185, 0x1ed75, [4]level{{15, 30}, {29, 28}, {40, 30}, {48, 30}}}, // 30
|
||||||
|
{28, 26, 2323, 0x1f250, [4]level{{16, 30}, {31, 28}, {43, 30}, {51, 30}}}, // 31
|
||||||
|
{32, 26, 2465, 0x209d5, [4]level{{17, 30}, {33, 28}, {45, 30}, {54, 30}}}, // 32
|
||||||
|
{28, 28, 2611, 0x216f0, [4]level{{18, 30}, {35, 28}, {48, 30}, {57, 30}}}, // 33
|
||||||
|
{32, 28, 2761, 0x228ba, [4]level{{19, 30}, {37, 28}, {51, 30}, {60, 30}}}, // 34
|
||||||
|
{28, 24, 2876, 0x2379f, [4]level{{19, 30}, {38, 28}, {53, 30}, {63, 30}}}, // 35
|
||||||
|
{22, 26, 3034, 0x24b0b, [4]level{{20, 30}, {40, 28}, {56, 30}, {66, 30}}}, // 36
|
||||||
|
{26, 26, 3196, 0x2542e, [4]level{{21, 30}, {43, 28}, {59, 30}, {70, 30}}}, // 37
|
||||||
|
{30, 26, 3362, 0x26a64, [4]level{{22, 30}, {45, 28}, {62, 30}, {74, 30}}}, // 38
|
||||||
|
{24, 28, 3532, 0x27541, [4]level{{24, 30}, {47, 28}, {65, 30}, {77, 30}}}, // 39
|
||||||
|
{28, 28, 3706, 0x28c69, [4]level{{25, 30}, {49, 28}, {68, 30}, {81, 30}}}, // 40
|
||||||
|
}
|
||||||
|
|
||||||
|
func grid(siz int) [][]Pixel {
|
||||||
|
m := make([][]Pixel, siz)
|
||||||
|
pix := make([]Pixel, siz*siz)
|
||||||
|
for i := range m {
|
||||||
|
m[i], pix = pix[:siz], pix[siz:]
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// vplan creates a Plan for the given version.
|
||||||
|
func vplan(v Version) (*Plan, error) {
|
||||||
|
p := &Plan{Version: v}
|
||||||
|
if v < 1 || v > 40 {
|
||||||
|
return nil, fmt.Errorf("invalid QR version %d", int(v))
|
||||||
|
}
|
||||||
|
siz := 17 + int(v)*4
|
||||||
|
m := grid(siz)
|
||||||
|
p.Pixel = m
|
||||||
|
|
||||||
|
// Timing markers (overwritten by boxes).
|
||||||
|
const ti = 6 // timing is in row/column 6 (counting from 0)
|
||||||
|
for i := range m {
|
||||||
|
p := Timing.Pixel()
|
||||||
|
if i&1 == 0 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[i][ti] = p
|
||||||
|
m[ti][i] = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position boxes.
|
||||||
|
posBox(m, 0, 0)
|
||||||
|
posBox(m, siz-7, 0)
|
||||||
|
posBox(m, 0, siz-7)
|
||||||
|
|
||||||
|
// Alignment boxes.
|
||||||
|
info := &vtab[v]
|
||||||
|
for x := 4; x+5 < siz; {
|
||||||
|
for y := 4; y+5 < siz; {
|
||||||
|
// don't overwrite timing markers
|
||||||
|
if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) {
|
||||||
|
} else {
|
||||||
|
alignBox(m, x, y)
|
||||||
|
}
|
||||||
|
if y == 4 {
|
||||||
|
y = info.apos
|
||||||
|
} else {
|
||||||
|
y += info.astride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x == 4 {
|
||||||
|
x = info.apos
|
||||||
|
} else {
|
||||||
|
x += info.astride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version pattern.
|
||||||
|
pat := vtab[v].pattern
|
||||||
|
if pat != 0 {
|
||||||
|
v := pat
|
||||||
|
for x := 0; x < 6; x++ {
|
||||||
|
for y := 0; y < 3; y++ {
|
||||||
|
p := PVersion.Pixel()
|
||||||
|
if v&1 != 0 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[siz-11+y][x] = p
|
||||||
|
m[x][siz-11+y] = p
|
||||||
|
v >>= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One lonely black pixel
|
||||||
|
m[siz-8][8] = Unused.Pixel() | Black
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fplan adds the format pixels
|
||||||
|
func fplan(l Level, m Mask, p *Plan) error {
|
||||||
|
// Format pixels.
|
||||||
|
fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10
|
||||||
|
fb |= uint32(m) << 10 // mask
|
||||||
|
const formatPoly = 0x537
|
||||||
|
rem := fb
|
||||||
|
for i := 14; i >= 10; i-- {
|
||||||
|
if rem&(1<<uint(i)) != 0 {
|
||||||
|
rem ^= formatPoly << uint(i-10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fb |= rem
|
||||||
|
invert := uint32(0x5412)
|
||||||
|
siz := len(p.Pixel)
|
||||||
|
for i := uint(0); i < 15; i++ {
|
||||||
|
pix := Format.Pixel() + OffsetPixel(i)
|
||||||
|
if (fb>>i)&1 == 1 {
|
||||||
|
pix |= Black
|
||||||
|
}
|
||||||
|
if (invert>>i)&1 == 1 {
|
||||||
|
pix ^= Invert | Black
|
||||||
|
}
|
||||||
|
// top left
|
||||||
|
switch {
|
||||||
|
case i < 6:
|
||||||
|
p.Pixel[i][8] = pix
|
||||||
|
case i < 8:
|
||||||
|
p.Pixel[i+1][8] = pix
|
||||||
|
case i < 9:
|
||||||
|
p.Pixel[8][7] = pix
|
||||||
|
default:
|
||||||
|
p.Pixel[8][14-i] = pix
|
||||||
|
}
|
||||||
|
// bottom right
|
||||||
|
switch {
|
||||||
|
case i < 8:
|
||||||
|
p.Pixel[8][siz-1-int(i)] = pix
|
||||||
|
default:
|
||||||
|
p.Pixel[siz-1-int(14-i)][8] = pix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lplan edits a version-only Plan to add information
|
||||||
|
// about the error correction levels.
|
||||||
|
func lplan(v Version, l Level, p *Plan) error {
|
||||||
|
p.Level = l
|
||||||
|
|
||||||
|
nblock := vtab[v].level[l].nblock
|
||||||
|
ne := vtab[v].level[l].check
|
||||||
|
nde := (vtab[v].bytes - ne*nblock) / nblock
|
||||||
|
extra := (vtab[v].bytes - ne*nblock) % nblock
|
||||||
|
dataBits := (nde*nblock + extra) * 8
|
||||||
|
checkBits := ne * nblock * 8
|
||||||
|
|
||||||
|
p.DataBytes = vtab[v].bytes - ne*nblock
|
||||||
|
p.CheckBytes = ne * nblock
|
||||||
|
p.Blocks = nblock
|
||||||
|
|
||||||
|
// Make data + checksum pixels.
|
||||||
|
data := make([]Pixel, dataBits)
|
||||||
|
for i := range data {
|
||||||
|
data[i] = Data.Pixel() | OffsetPixel(uint(i))
|
||||||
|
}
|
||||||
|
check := make([]Pixel, checkBits)
|
||||||
|
for i := range check {
|
||||||
|
check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split into blocks.
|
||||||
|
dataList := make([][]Pixel, nblock)
|
||||||
|
checkList := make([][]Pixel, nblock)
|
||||||
|
for i := 0; i < nblock; i++ {
|
||||||
|
// The last few blocks have an extra data byte (8 pixels).
|
||||||
|
nd := nde
|
||||||
|
if i >= nblock-extra {
|
||||||
|
nd++
|
||||||
|
}
|
||||||
|
dataList[i], data = data[0:nd*8], data[nd*8:]
|
||||||
|
checkList[i], check = check[0:ne*8], check[ne*8:]
|
||||||
|
}
|
||||||
|
if len(data) != 0 || len(check) != 0 {
|
||||||
|
panic("data/check math")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up bit sequence, taking first byte of each block,
|
||||||
|
// then second byte, and so on. Then checksums.
|
||||||
|
bits := make([]Pixel, dataBits+checkBits)
|
||||||
|
dst := bits
|
||||||
|
for i := 0; i < nde+1; i++ {
|
||||||
|
for _, b := range dataList {
|
||||||
|
if i*8 < len(b) {
|
||||||
|
copy(dst, b[i*8:(i+1)*8])
|
||||||
|
dst = dst[8:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < ne; i++ {
|
||||||
|
for _, b := range checkList {
|
||||||
|
if i*8 < len(b) {
|
||||||
|
copy(dst, b[i*8:(i+1)*8])
|
||||||
|
dst = dst[8:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dst) != 0 {
|
||||||
|
panic("dst math")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sweep up pair of columns,
|
||||||
|
// then down, assigning to right then left pixel.
|
||||||
|
// Repeat.
|
||||||
|
// See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm
|
||||||
|
siz := len(p.Pixel)
|
||||||
|
rem := make([]Pixel, 7)
|
||||||
|
for i := range rem {
|
||||||
|
rem[i] = Extra.Pixel()
|
||||||
|
}
|
||||||
|
src := append(bits, rem...)
|
||||||
|
for x := siz; x > 0; {
|
||||||
|
for y := siz - 1; y >= 0; y-- {
|
||||||
|
if p.Pixel[y][x-1].Role() == 0 {
|
||||||
|
p.Pixel[y][x-1], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
if p.Pixel[y][x-2].Role() == 0 {
|
||||||
|
p.Pixel[y][x-2], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x -= 2
|
||||||
|
if x == 7 { // vertical timing strip
|
||||||
|
x--
|
||||||
|
}
|
||||||
|
for y := 0; y < siz; y++ {
|
||||||
|
if p.Pixel[y][x-1].Role() == 0 {
|
||||||
|
p.Pixel[y][x-1], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
if p.Pixel[y][x-2].Role() == 0 {
|
||||||
|
p.Pixel[y][x-2], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x -= 2
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mplan edits a version+level-only Plan to add the mask.
|
||||||
|
func mplan(m Mask, p *Plan) error {
|
||||||
|
p.Mask = m
|
||||||
|
for y, row := range p.Pixel {
|
||||||
|
for x, pix := range row {
|
||||||
|
if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) {
|
||||||
|
row[x] ^= Black | Invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// posBox draws a position (large) box at upper left x, y.
|
||||||
|
func posBox(m [][]Pixel, x, y int) {
|
||||||
|
pos := Position.Pixel()
|
||||||
|
// box
|
||||||
|
for dy := 0; dy < 7; dy++ {
|
||||||
|
for dx := 0; dx < 7; dx++ {
|
||||||
|
p := pos
|
||||||
|
if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[y+dy][x+dx] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// white border
|
||||||
|
for dy := -1; dy < 8; dy++ {
|
||||||
|
if 0 <= y+dy && y+dy < len(m) {
|
||||||
|
if x > 0 {
|
||||||
|
m[y+dy][x-1] = pos
|
||||||
|
}
|
||||||
|
if x+7 < len(m) {
|
||||||
|
m[y+dy][x+7] = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for dx := -1; dx < 8; dx++ {
|
||||||
|
if 0 <= x+dx && x+dx < len(m) {
|
||||||
|
if y > 0 {
|
||||||
|
m[y-1][x+dx] = pos
|
||||||
|
}
|
||||||
|
if y+7 < len(m) {
|
||||||
|
m[y+7][x+dx] = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// alignBox draw an alignment (small) box at upper left x, y.
|
||||||
|
func alignBox(m [][]Pixel, x, y int) {
|
||||||
|
// box
|
||||||
|
align := Alignment.Pixel()
|
||||||
|
for dy := 0; dy < 5; dy++ {
|
||||||
|
for dx := 0; dx < 5; dx++ {
|
||||||
|
p := align
|
||||||
|
if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[y+dy][x+dx] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,241 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package gf256 implements arithmetic over the Galois Field GF(256).
|
||||||
|
package gf256 // import "rsc.io/qr/gf256"
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// A Field represents an instance of GF(256) defined by a specific polynomial.
|
||||||
|
type Field struct {
|
||||||
|
log [256]byte // log[0] is unused
|
||||||
|
exp [510]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewField returns a new field corresponding to the polynomial poly
|
||||||
|
// and generator α. The Reed-Solomon encoding in QR codes uses
|
||||||
|
// polynomial 0x11d with generator 2.
|
||||||
|
//
|
||||||
|
// The choice of generator α only affects the Exp and Log operations.
|
||||||
|
func NewField(poly, α int) *Field {
|
||||||
|
if poly < 0x100 || poly >= 0x200 || reducible(poly) {
|
||||||
|
panic("gf256: invalid polynomial: " + strconv.Itoa(poly))
|
||||||
|
}
|
||||||
|
|
||||||
|
var f Field
|
||||||
|
x := 1
|
||||||
|
for i := 0; i < 255; i++ {
|
||||||
|
if x == 1 && i != 0 {
|
||||||
|
panic("gf256: invalid generator " + strconv.Itoa(α) +
|
||||||
|
" for polynomial " + strconv.Itoa(poly))
|
||||||
|
}
|
||||||
|
f.exp[i] = byte(x)
|
||||||
|
f.exp[i+255] = byte(x)
|
||||||
|
f.log[x] = byte(i)
|
||||||
|
x = mul(x, α, poly)
|
||||||
|
}
|
||||||
|
f.log[0] = 255
|
||||||
|
for i := 0; i < 255; i++ {
|
||||||
|
if f.log[f.exp[i]] != byte(i) {
|
||||||
|
panic("bad log")
|
||||||
|
}
|
||||||
|
if f.log[f.exp[i+255]] != byte(i) {
|
||||||
|
panic("bad log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 1; i < 256; i++ {
|
||||||
|
if f.exp[f.log[i]] != byte(i) {
|
||||||
|
panic("bad log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
// nbit returns the number of significant in p.
|
||||||
|
func nbit(p int) uint {
|
||||||
|
n := uint(0)
|
||||||
|
for ; p > 0; p >>= 1 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// polyDiv divides the polynomial p by q and returns the remainder.
|
||||||
|
func polyDiv(p, q int) int {
|
||||||
|
np := nbit(p)
|
||||||
|
nq := nbit(q)
|
||||||
|
for ; np >= nq; np-- {
|
||||||
|
if p&(1<<(np-1)) != 0 {
|
||||||
|
p ^= q << (np - nq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul returns the product x*y mod poly, a GF(256) multiplication.
|
||||||
|
func mul(x, y, poly int) int {
|
||||||
|
z := 0
|
||||||
|
for x > 0 {
|
||||||
|
if x&1 != 0 {
|
||||||
|
z ^= y
|
||||||
|
}
|
||||||
|
x >>= 1
|
||||||
|
y <<= 1
|
||||||
|
if y&0x100 != 0 {
|
||||||
|
y ^= poly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// reducible reports whether p is reducible.
|
||||||
|
func reducible(p int) bool {
|
||||||
|
// Multiplying n-bit * n-bit produces (2n-1)-bit,
|
||||||
|
// so if p is reducible, one of its factors must be
|
||||||
|
// of np/2+1 bits or fewer.
|
||||||
|
np := nbit(p)
|
||||||
|
for q := 2; q < 1<<(np/2+1); q++ {
|
||||||
|
if polyDiv(p, q) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns the sum of x and y in the field.
|
||||||
|
func (f *Field) Add(x, y byte) byte {
|
||||||
|
return x ^ y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exp returns the base-α exponential of e in the field.
|
||||||
|
// If e < 0, Exp returns 0.
|
||||||
|
func (f *Field) Exp(e int) byte {
|
||||||
|
if e < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f.exp[e%255]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log returns the base-α logarithm of x in the field.
|
||||||
|
// If x == 0, Log returns -1.
|
||||||
|
func (f *Field) Log(x byte) int {
|
||||||
|
if x == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return int(f.log[x])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inv returns the multiplicative inverse of x in the field.
|
||||||
|
// If x == 0, Inv returns 0.
|
||||||
|
func (f *Field) Inv(x byte) byte {
|
||||||
|
if x == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f.exp[255-f.log[x]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mul returns the product of x and y in the field.
|
||||||
|
func (f *Field) Mul(x, y byte) byte {
|
||||||
|
if x == 0 || y == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f.exp[int(f.log[x])+int(f.log[y])]
|
||||||
|
}
|
||||||
|
|
||||||
|
// An RSEncoder implements Reed-Solomon encoding
|
||||||
|
// over a given field using a given number of error correction bytes.
|
||||||
|
type RSEncoder struct {
|
||||||
|
f *Field
|
||||||
|
c int
|
||||||
|
gen []byte
|
||||||
|
lgen []byte
|
||||||
|
p []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) gen(e int) (gen, lgen []byte) {
|
||||||
|
// p = 1
|
||||||
|
p := make([]byte, e+1)
|
||||||
|
p[e] = 1
|
||||||
|
|
||||||
|
for i := 0; i < e; i++ {
|
||||||
|
// p *= (x + Exp(i))
|
||||||
|
// p[j] = p[j]*Exp(i) + p[j+1].
|
||||||
|
c := f.Exp(i)
|
||||||
|
for j := 0; j < e; j++ {
|
||||||
|
p[j] = f.Mul(p[j], c) ^ p[j+1]
|
||||||
|
}
|
||||||
|
p[e] = f.Mul(p[e], c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lp = log p.
|
||||||
|
lp := make([]byte, e+1)
|
||||||
|
for i, c := range p {
|
||||||
|
if c == 0 {
|
||||||
|
lp[i] = 255
|
||||||
|
} else {
|
||||||
|
lp[i] = byte(f.Log(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, lp
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSEncoder returns a new Reed-Solomon encoder
|
||||||
|
// over the given field and number of error correction bytes.
|
||||||
|
func NewRSEncoder(f *Field, c int) *RSEncoder {
|
||||||
|
gen, lgen := f.gen(c)
|
||||||
|
return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECC writes to check the error correcting code bytes
|
||||||
|
// for data using the given Reed-Solomon parameters.
|
||||||
|
func (rs *RSEncoder) ECC(data []byte, check []byte) {
|
||||||
|
if len(check) < rs.c {
|
||||||
|
panic("gf256: invalid check byte length")
|
||||||
|
}
|
||||||
|
if rs.c == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The check bytes are the remainder after dividing
|
||||||
|
// data padded with c zeros by the generator polynomial.
|
||||||
|
|
||||||
|
// p = data padded with c zeros.
|
||||||
|
var p []byte
|
||||||
|
n := len(data) + rs.c
|
||||||
|
if len(rs.p) >= n {
|
||||||
|
p = rs.p
|
||||||
|
} else {
|
||||||
|
p = make([]byte, n)
|
||||||
|
}
|
||||||
|
copy(p, data)
|
||||||
|
for i := len(data); i < len(p); i++ {
|
||||||
|
p[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide p by gen, leaving the remainder in p[len(data):].
|
||||||
|
// p[0] is the most significant term in p, and
|
||||||
|
// gen[0] is the most significant term in the generator,
|
||||||
|
// which is always 1.
|
||||||
|
// To avoid repeated work, we store various values as
|
||||||
|
// lv, not v, where lv = log[v].
|
||||||
|
f := rs.f
|
||||||
|
lgen := rs.lgen[1:]
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
c := p[i]
|
||||||
|
if c == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
q := p[i+1:]
|
||||||
|
exp := f.exp[f.log[c]:]
|
||||||
|
for j, lg := range lgen {
|
||||||
|
if lg != 255 { // lgen uses 255 for log 0
|
||||||
|
q[j] ^= exp[lg]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy(check, p[len(data):])
|
||||||
|
rs.p = p
|
||||||
|
}
|
|
@ -0,0 +1,400 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package qr
|
||||||
|
|
||||||
|
// PNG writer for QR codes.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"hash"
|
||||||
|
"hash/crc32"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PNG returns a PNG image displaying the code.
|
||||||
|
//
|
||||||
|
// PNG uses a custom encoder tailored to QR codes.
|
||||||
|
// Its compressed size is about 2x away from optimal,
|
||||||
|
// but it runs about 20x faster than calling png.Encode
|
||||||
|
// on c.Image().
|
||||||
|
func (c *Code) PNG() []byte {
|
||||||
|
var p pngWriter
|
||||||
|
return p.encode(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pngWriter struct {
|
||||||
|
tmp [16]byte
|
||||||
|
wctmp [4]byte
|
||||||
|
buf bytes.Buffer
|
||||||
|
zlib bitWriter
|
||||||
|
crc hash.Hash32
|
||||||
|
}
|
||||||
|
|
||||||
|
var pngHeader = []byte("\x89PNG\r\n\x1a\n")
|
||||||
|
|
||||||
|
func (w *pngWriter) encode(c *Code) []byte {
|
||||||
|
scale := c.Scale
|
||||||
|
siz := c.Size
|
||||||
|
|
||||||
|
w.buf.Reset()
|
||||||
|
|
||||||
|
// Header
|
||||||
|
w.buf.Write(pngHeader)
|
||||||
|
|
||||||
|
// Header block
|
||||||
|
binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale))
|
||||||
|
binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale))
|
||||||
|
w.tmp[8] = 1 // 1-bit
|
||||||
|
w.tmp[9] = 0 // gray
|
||||||
|
w.tmp[10] = 0
|
||||||
|
w.tmp[11] = 0
|
||||||
|
w.tmp[12] = 0
|
||||||
|
w.writeChunk("IHDR", w.tmp[:13])
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
w.writeChunk("tEXt", comment)
|
||||||
|
|
||||||
|
// Data
|
||||||
|
w.zlib.writeCode(c)
|
||||||
|
w.writeChunk("IDAT", w.zlib.bytes.Bytes())
|
||||||
|
|
||||||
|
// End
|
||||||
|
w.writeChunk("IEND", nil)
|
||||||
|
|
||||||
|
return w.buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
var comment = []byte("Software\x00QR-PNG http://qr.swtch.com/")
|
||||||
|
|
||||||
|
func (w *pngWriter) writeChunk(name string, data []byte) {
|
||||||
|
if w.crc == nil {
|
||||||
|
w.crc = crc32.NewIEEE()
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data)))
|
||||||
|
w.buf.Write(w.wctmp[0:4])
|
||||||
|
w.crc.Reset()
|
||||||
|
copy(w.wctmp[0:4], name)
|
||||||
|
w.buf.Write(w.wctmp[0:4])
|
||||||
|
w.crc.Write(w.wctmp[0:4])
|
||||||
|
w.buf.Write(data)
|
||||||
|
w.crc.Write(data)
|
||||||
|
crc := w.crc.Sum32()
|
||||||
|
binary.BigEndian.PutUint32(w.wctmp[0:4], crc)
|
||||||
|
w.buf.Write(w.wctmp[0:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) writeCode(c *Code) {
|
||||||
|
const ftNone = 0
|
||||||
|
|
||||||
|
b.adler32.Reset()
|
||||||
|
b.bytes.Reset()
|
||||||
|
b.nbit = 0
|
||||||
|
|
||||||
|
scale := c.Scale
|
||||||
|
siz := c.Size
|
||||||
|
|
||||||
|
// zlib header
|
||||||
|
b.tmp[0] = 0x78
|
||||||
|
b.tmp[1] = 0
|
||||||
|
b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31)
|
||||||
|
b.bytes.Write(b.tmp[0:2])
|
||||||
|
|
||||||
|
// Start flate block.
|
||||||
|
b.writeBits(1, 1, false) // final block
|
||||||
|
b.writeBits(1, 2, false) // compressed, fixed Huffman tables
|
||||||
|
|
||||||
|
// White border.
|
||||||
|
// First row.
|
||||||
|
b.byte(ftNone)
|
||||||
|
n := (scale*(siz+8) + 7) / 8
|
||||||
|
b.byte(255)
|
||||||
|
b.repeat(n-1, 1)
|
||||||
|
// 4*scale rows total.
|
||||||
|
b.repeat((4*scale-1)*(1+n), 1+n)
|
||||||
|
|
||||||
|
for i := 0; i < 4*scale; i++ {
|
||||||
|
b.adler32.WriteNByte(ftNone, 1)
|
||||||
|
b.adler32.WriteNByte(255, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
row := make([]byte, 1+n)
|
||||||
|
for y := 0; y < siz; y++ {
|
||||||
|
row[0] = ftNone
|
||||||
|
j := 1
|
||||||
|
var z uint8
|
||||||
|
nz := 0
|
||||||
|
for x := -4; x < siz+4; x++ {
|
||||||
|
// Raw data.
|
||||||
|
for i := 0; i < scale; i++ {
|
||||||
|
z <<= 1
|
||||||
|
if !c.Black(x, y) {
|
||||||
|
z |= 1
|
||||||
|
}
|
||||||
|
if nz++; nz == 8 {
|
||||||
|
row[j] = z
|
||||||
|
j++
|
||||||
|
nz = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if j < len(row) {
|
||||||
|
row[j] = z
|
||||||
|
}
|
||||||
|
for _, z := range row {
|
||||||
|
b.byte(z)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale-1 copies.
|
||||||
|
b.repeat((scale-1)*(1+n), 1+n)
|
||||||
|
|
||||||
|
b.adler32.WriteN(row, scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// White border.
|
||||||
|
// First row.
|
||||||
|
b.byte(ftNone)
|
||||||
|
b.byte(255)
|
||||||
|
b.repeat(n-1, 1)
|
||||||
|
// 4*scale rows total.
|
||||||
|
b.repeat((4*scale-1)*(1+n), 1+n)
|
||||||
|
|
||||||
|
for i := 0; i < 4*scale; i++ {
|
||||||
|
b.adler32.WriteNByte(ftNone, 1)
|
||||||
|
b.adler32.WriteNByte(255, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of block.
|
||||||
|
b.hcode(256)
|
||||||
|
b.flushBits()
|
||||||
|
|
||||||
|
// adler32
|
||||||
|
binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32())
|
||||||
|
b.bytes.Write(b.tmp[0:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bitWriter is a write buffer for bit-oriented data like deflate.
|
||||||
|
type bitWriter struct {
|
||||||
|
bytes bytes.Buffer
|
||||||
|
bit uint32
|
||||||
|
nbit uint
|
||||||
|
|
||||||
|
tmp [4]byte
|
||||||
|
adler32 adigest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) {
|
||||||
|
// reverse, for huffman codes
|
||||||
|
if rev {
|
||||||
|
br := uint32(0)
|
||||||
|
for i := uint(0); i < nbit; i++ {
|
||||||
|
br |= ((bit >> i) & 1) << (nbit - 1 - i)
|
||||||
|
}
|
||||||
|
bit = br
|
||||||
|
}
|
||||||
|
b.bit |= bit << b.nbit
|
||||||
|
b.nbit += nbit
|
||||||
|
for b.nbit >= 8 {
|
||||||
|
b.bytes.WriteByte(byte(b.bit))
|
||||||
|
b.bit >>= 8
|
||||||
|
b.nbit -= 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) flushBits() {
|
||||||
|
if b.nbit > 0 {
|
||||||
|
b.bytes.WriteByte(byte(b.bit))
|
||||||
|
b.nbit = 0
|
||||||
|
b.bit = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) hcode(v int) {
|
||||||
|
/*
|
||||||
|
Lit Value Bits Codes
|
||||||
|
--------- ---- -----
|
||||||
|
0 - 143 8 00110000 through
|
||||||
|
10111111
|
||||||
|
144 - 255 9 110010000 through
|
||||||
|
111111111
|
||||||
|
256 - 279 7 0000000 through
|
||||||
|
0010111
|
||||||
|
280 - 287 8 11000000 through
|
||||||
|
11000111
|
||||||
|
*/
|
||||||
|
switch {
|
||||||
|
case v <= 143:
|
||||||
|
b.writeBits(uint32(v)+0x30, 8, true)
|
||||||
|
case v <= 255:
|
||||||
|
b.writeBits(uint32(v-144)+0x190, 9, true)
|
||||||
|
case v <= 279:
|
||||||
|
b.writeBits(uint32(v-256)+0, 7, true)
|
||||||
|
case v <= 287:
|
||||||
|
b.writeBits(uint32(v-280)+0xc0, 8, true)
|
||||||
|
default:
|
||||||
|
panic("invalid hcode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) byte(x byte) {
|
||||||
|
b.hcode(int(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) codex(c int, val int, nx uint) {
|
||||||
|
b.hcode(c + val>>nx)
|
||||||
|
b.writeBits(uint32(val)&(1<<nx-1), nx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) repeat(n, d int) {
|
||||||
|
for ; n >= 258+3; n -= 258 {
|
||||||
|
b.repeat1(258, d)
|
||||||
|
}
|
||||||
|
if n > 258 {
|
||||||
|
// 258 < n < 258+3
|
||||||
|
b.repeat1(10, d)
|
||||||
|
b.repeat1(n-10, d)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n < 3 {
|
||||||
|
panic("invalid flate repeat")
|
||||||
|
}
|
||||||
|
b.repeat1(n, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) repeat1(n, d int) {
|
||||||
|
/*
|
||||||
|
Extra Extra Extra
|
||||||
|
Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
|
||||||
|
---- ---- ------ ---- ---- ------- ---- ---- -------
|
||||||
|
257 0 3 267 1 15,16 277 4 67-82
|
||||||
|
258 0 4 268 1 17,18 278 4 83-98
|
||||||
|
259 0 5 269 2 19-22 279 4 99-114
|
||||||
|
260 0 6 270 2 23-26 280 4 115-130
|
||||||
|
261 0 7 271 2 27-30 281 5 131-162
|
||||||
|
262 0 8 272 2 31-34 282 5 163-194
|
||||||
|
263 0 9 273 3 35-42 283 5 195-226
|
||||||
|
264 0 10 274 3 43-50 284 5 227-257
|
||||||
|
265 1 11,12 275 3 51-58 285 0 258
|
||||||
|
266 1 13,14 276 3 59-66
|
||||||
|
*/
|
||||||
|
switch {
|
||||||
|
case n <= 10:
|
||||||
|
b.codex(257, n-3, 0)
|
||||||
|
case n <= 18:
|
||||||
|
b.codex(265, n-11, 1)
|
||||||
|
case n <= 34:
|
||||||
|
b.codex(269, n-19, 2)
|
||||||
|
case n <= 66:
|
||||||
|
b.codex(273, n-35, 3)
|
||||||
|
case n <= 130:
|
||||||
|
b.codex(277, n-67, 4)
|
||||||
|
case n <= 257:
|
||||||
|
b.codex(281, n-131, 5)
|
||||||
|
case n == 258:
|
||||||
|
b.hcode(285)
|
||||||
|
default:
|
||||||
|
panic("invalid repeat length")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Extra Extra Extra
|
||||||
|
Code Bits Dist Code Bits Dist Code Bits Distance
|
||||||
|
---- ---- ---- ---- ---- ------ ---- ---- --------
|
||||||
|
0 0 1 10 4 33-48 20 9 1025-1536
|
||||||
|
1 0 2 11 4 49-64 21 9 1537-2048
|
||||||
|
2 0 3 12 5 65-96 22 10 2049-3072
|
||||||
|
3 0 4 13 5 97-128 23 10 3073-4096
|
||||||
|
4 1 5,6 14 6 129-192 24 11 4097-6144
|
||||||
|
5 1 7,8 15 6 193-256 25 11 6145-8192
|
||||||
|
6 2 9-12 16 7 257-384 26 12 8193-12288
|
||||||
|
7 2 13-16 17 7 385-512 27 12 12289-16384
|
||||||
|
8 3 17-24 18 8 513-768 28 13 16385-24576
|
||||||
|
9 3 25-32 19 8 769-1024 29 13 24577-32768
|
||||||
|
*/
|
||||||
|
if d <= 4 {
|
||||||
|
b.writeBits(uint32(d-1), 5, true)
|
||||||
|
} else if d <= 32768 {
|
||||||
|
nbit := uint(16)
|
||||||
|
for d <= 1<<(nbit-1) {
|
||||||
|
nbit--
|
||||||
|
}
|
||||||
|
v := uint32(d - 1)
|
||||||
|
v &^= 1 << (nbit - 1) // top bit is implicit
|
||||||
|
code := uint32(2*nbit - 2) // second bit is low bit of code
|
||||||
|
code |= v >> (nbit - 2)
|
||||||
|
v &^= 1 << (nbit - 2)
|
||||||
|
b.writeBits(code, 5, true)
|
||||||
|
// rest of bits follow
|
||||||
|
b.writeBits(uint32(v), nbit-2, false)
|
||||||
|
} else {
|
||||||
|
panic("invalid repeat distance")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) run(v byte, n int) {
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.byte(v)
|
||||||
|
if n-1 < 3 {
|
||||||
|
for i := 0; i < n-1; i++ {
|
||||||
|
b.byte(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.repeat(n-1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type adigest struct {
|
||||||
|
a, b uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) Reset() { d.a, d.b = 1, 0 }
|
||||||
|
|
||||||
|
const amod = 65521
|
||||||
|
|
||||||
|
func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) {
|
||||||
|
// TODO(rsc): 6g doesn't do magic multiplies for b %= amod,
|
||||||
|
// only for b = b%amod.
|
||||||
|
|
||||||
|
// invariant: a, b < amod
|
||||||
|
if pi == 0 {
|
||||||
|
b += uint32(n%amod) * a
|
||||||
|
b = b % amod
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// n times:
|
||||||
|
// a += pi
|
||||||
|
// b += a
|
||||||
|
// is same as
|
||||||
|
// b += n*a + n*(n+1)/2*pi
|
||||||
|
// a += n*pi
|
||||||
|
m := uint32(n)
|
||||||
|
b += (m % amod) * a
|
||||||
|
b = b % amod
|
||||||
|
b += (m * (m + 1) / 2) % amod * uint32(pi)
|
||||||
|
b = b % amod
|
||||||
|
a += (m % amod) * uint32(pi)
|
||||||
|
a = a % amod
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
func afinish(a, b uint32) uint32 {
|
||||||
|
return b<<16 | a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) WriteN(p []byte, n int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
for _, pi := range p {
|
||||||
|
d.a, d.b = aupdate(d.a, d.b, pi, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) WriteNByte(pi byte, n int) {
|
||||||
|
d.a, d.b = aupdate(d.a, d.b, pi, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) }
|
|
@ -0,0 +1,116 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package qr encodes QR codes.
|
||||||
|
*/
|
||||||
|
package qr // import "rsc.io/qr"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"rsc.io/qr/coding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Level denotes a QR error correction level.
|
||||||
|
// From least to most tolerant of errors, they are L, M, Q, H.
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
L Level = iota // 20% redundant
|
||||||
|
M // 38% redundant
|
||||||
|
Q // 55% redundant
|
||||||
|
H // 65% redundant
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode returns an encoding of text at the given error correction level.
|
||||||
|
func Encode(text string, level Level) (*Code, error) {
|
||||||
|
// Pick data encoding, smallest first.
|
||||||
|
// We could split the string and use different encodings
|
||||||
|
// but that seems like overkill for now.
|
||||||
|
var enc coding.Encoding
|
||||||
|
switch {
|
||||||
|
case coding.Num(text).Check() == nil:
|
||||||
|
enc = coding.Num(text)
|
||||||
|
case coding.Alpha(text).Check() == nil:
|
||||||
|
enc = coding.Alpha(text)
|
||||||
|
default:
|
||||||
|
enc = coding.String(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick size.
|
||||||
|
l := coding.Level(level)
|
||||||
|
var v coding.Version
|
||||||
|
for v = coding.MinVersion; ; v++ {
|
||||||
|
if v > coding.MaxVersion {
|
||||||
|
return nil, errors.New("text too long to encode as QR")
|
||||||
|
}
|
||||||
|
if enc.Bits(v) <= v.DataBytes(l)*8 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and execute plan.
|
||||||
|
p, err := coding.NewPlan(v, l, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cc, err := p.Encode(enc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pick appropriate mask.
|
||||||
|
|
||||||
|
return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Code is a square pixel grid.
|
||||||
|
// It implements image.Image and direct PNG encoding.
|
||||||
|
type Code struct {
|
||||||
|
Bitmap []byte // 1 is black, 0 is white
|
||||||
|
Size int // number of pixels on a side
|
||||||
|
Stride int // number of bytes per row
|
||||||
|
Scale int // number of image pixels per QR pixel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black returns true if the pixel at (x,y) is black.
|
||||||
|
func (c *Code) Black(x, y int) bool {
|
||||||
|
return 0 <= x && x < c.Size && 0 <= y && y < c.Size &&
|
||||||
|
c.Bitmap[y*c.Stride+x/8]&(1<<uint(7-x&7)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image returns an Image displaying the code.
|
||||||
|
func (c *Code) Image() image.Image {
|
||||||
|
return &codeImage{c}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// codeImage implements image.Image
|
||||||
|
type codeImage struct {
|
||||||
|
*Code
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
whiteColor color.Color = color.Gray{0xFF}
|
||||||
|
blackColor color.Color = color.Gray{0x00}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *codeImage) Bounds() image.Rectangle {
|
||||||
|
d := (c.Size + 8) * c.Scale
|
||||||
|
return image.Rect(0, 0, d, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codeImage) At(x, y int) color.Color {
|
||||||
|
if c.Black(x, y) {
|
||||||
|
return blackColor
|
||||||
|
}
|
||||||
|
return whiteColor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codeImage) ColorModel() color.Model {
|
||||||
|
return color.GrayModel
|
||||||
|
}
|
Loading…
Reference in New Issue