From f94dc9efc6248e721d2b746a0018f38e06335171 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Jun 2020 11:31:00 +0200 Subject: [PATCH] bundle swiss cross image file to avoid file system dependency --- GENERATED_swisscross.go | 7 ++ generate.go | 3 + goembed.go | 190 ++++++++++++++++++++++++++++++++++++++++ qrcodegenerator.go | 13 +-- 4 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 GENERATED_swisscross.go create mode 100644 generate.go create mode 100644 goembed.go diff --git a/GENERATED_swisscross.go b/GENERATED_swisscross.go new file mode 100644 index 0000000..b54ffae --- /dev/null +++ b/GENERATED_swisscross.go @@ -0,0 +1,7 @@ +package qrbill + +// Table of contents +var swisscross = map[string][]byte{ + "third_party/swiss-cross/CH-Kreuz_7mm/CH-Kreuz_7mm.png": swisscross_0, +} +var swisscross_0 = []byte("\x89PNG \n\n\x00\x00\x00 IHDR\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\x00x\xf3\xc9\xda\x00\x00\x00 pHYs\x00\x00\\F\x00\x00\\F\x94CA\x00\x0080iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\n\n \n \n Adobe Photoshop CC 2015.5 (Macintosh)\n 2017-06-07T09:36:22+02:00\n 2017-06-07T09:43:10+02:00\n 2017-06-07T09:43:10+02:00\n image/png\n 0\n xmp.iid:d37a2394-3f20-4130-9fc2-6cccaed7bb7f\n xmp.did:d37a2394-3f20-4130-9fc2-6cccaed7bb7f\n xmp.did:d37a2394-3f20-4130-9fc2-6cccaed7bb7f\n \n \n \n created\n xmp.iid:d37a2394-3f20-4130-9fc2-6cccaed7bb7f\n 2017-06-07T09:36:22+02:00\n Adobe Photoshop CC 2015.5 (Macintosh)\n \n \n \n 1\n 6000000/10000\n 6000000/10000\n 2\n 65535\n 166\n 166\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\x98\xfd\xb9B\x00\x00\x00 cHRM\x00\x00\x87\n\x00\x00\x8c \x00\xb7\x00\x00\x80\xe8\x00\x00R\x00[\x00\x006\xaf\x00\x00A\xd0\xdci\x00\x00\x00\xb9IDATx\xda\xec\x97K\xc20 Dm\x87}\x8f\x94\xabs\xa4r\x80tX\xf0i\xde \x83Y\xa0d=Yv2\x9e\xb8\xaaÒ\xdcR\xea)E\xebD5\"6\xa7\x83\xa9fX\xdf`Q-W'\x88X\xfa2yM\xa6!\xeb\xe0D/\xd2wϻ\x008\xe2_\xc6})mD5\xc6}\x90\xdff\xfb\xa9SM\xf6/s +Rry\x9b\xdc\xe3\x9b: \x80\xd5\xcc\xfa\xddÍ8\x99>K\x82\xb8\xc5V\xd4!\xdd;U\xd3ȋ\xce0\x88^|az>\xbf\x85\x879\xd9!\xabE\x9d\x9ftҗuzl.>ՙ\xb4\x8c\xca(B\xaf\x00\x89\xabA\x00\x80\x85S\x00\x00\x00\x00IEND\xaeB`\x82") diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..5061248 --- /dev/null +++ b/generate.go @@ -0,0 +1,3 @@ +package qrbill + +//go:generate sh -c "go run goembed.go -package qrbill -var swisscross third_party/swiss-cross/CH-Kreuz_7mm/CH-Kreuz_7mm.png > GENERATED_swisscross.go && gofmt -w GENERATED_swisscross.go" diff --git a/goembed.go b/goembed.go new file mode 100644 index 0000000..73fbfb4 --- /dev/null +++ b/goembed.go @@ -0,0 +1,190 @@ +// copied from https://github.com/dsymonds/goembed/ with pull requests applied + +// +build ignore + +// goembed generates a Go source file from an input file. +package main + +import ( + "bufio" + "bytes" + "compress/gzip" + "flag" + "fmt" + "io" + "log" + "os" + "text/template" + "unicode/utf8" +) + +var ( + packageFlag = flag.String("package", "", "Go package name") + varFlag = flag.String("var", "", "Go var name") + gzipFlag = flag.Bool("gzip", false, "Whether to gzip contents") +) + +func main() { + flag.Parse() + + fmt.Printf("package %s\n\n", *packageFlag) + + if *gzipFlag { + err := gzipPrologue.Execute(os.Stdout, map[string]interface{}{ + "Args": flag.Args(), + "VarName": *varFlag, + }) + if err != nil { + log.Fatal(err) + } + } + + if flag.NArg() > 0 { + fmt.Println("// Table of contents") + fmt.Printf("var %v = map[string][]byte{\n", *varFlag) + for i, filename := range flag.Args() { + fmt.Printf("\t%#v: %s_%d,\n", filename, *varFlag, i) + } + fmt.Println("}") + + // Using a separate variable for each []byte, instead of + // combining them into a single map literal, enables a storage + // optimization: the compiler places the data directly in the + // program's noptrdata section instead of the heap. + for i, filename := range flag.Args() { + if err := oneVar(fmt.Sprintf("%s_%d", *varFlag, i), filename); err != nil { + log.Fatal(err) + } + } + } else { + if err := oneVarReader(*varFlag, os.Stdin); err != nil { + log.Fatal(err) + } + } +} + +func oneVar(varName, filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + return oneVarReader(varName, f) +} + +func oneVarReader(varName string, r io.Reader) error { + // Generate []byte() instead of []byte{}. + // The latter causes a memory explosion in the compiler (60 MB of input chews over 9 GB RAM). + // Doing a string conversion avoids some of that, but incurs a slight startup cost. + if !*gzipFlag { + fmt.Printf(`var %s = []byte("`, varName) + } else { + var buf bytes.Buffer + gzw, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression) + if _, err := io.Copy(gzw, r); err != nil { + return err + } + if err := gzw.Close(); err != nil { + return err + } + fmt.Printf("var %s []byte // set in init\n\n", varName) + fmt.Printf(`var %s_gzip = []byte("`, varName) + r = &buf + } + + bufw := bufio.NewWriter(os.Stdout) + if _, err := io.Copy(&writer{w: bufw}, r); err != nil { + return err + } + if err := bufw.Flush(); err != nil { + return err + } + fmt.Println(`")`) + return nil +} + +type writer struct { + w io.Writer +} + +func (w *writer) Write(data []byte) (n int, err error) { + n = len(data) + + for err == nil && len(data) > 0 { + // https://golang.org/ref/spec#String_literals: "Within the quotes, any + // character may appear except newline and unescaped double quote. The + // text between the quotes forms the value of the literal, with backslash + // escapes interpreted as they are in rune literals […]." + switch b := data[0]; b { + case '\\': + _, err = w.w.Write([]byte(`\\`)) + case '"': + _, err = w.w.Write([]byte(`\"`)) + case '\n': + _, err = w.w.Write([]byte(`\n`)) + + case '\x00': + // https://golang.org/ref/spec#Source_code_representation: "Implementation + // restriction: For compatibility with other tools, a compiler may + // disallow the NUL character (U+0000) in the source text." + _, err = w.w.Write([]byte(`\x00`)) + + default: + // https://golang.org/ref/spec#Source_code_representation: "Implementation + // restriction: […] A byte order mark may be disallowed anywhere else in + // the source." + const byteOrderMark = '\uFEFF' + + if r, size := utf8.DecodeRune(data); r != utf8.RuneError && r != byteOrderMark { + _, err = w.w.Write(data[:size]) + data = data[size:] + continue + } + + _, err = fmt.Fprintf(w.w, `\x%02x`, b) + } + data = data[1:] + } + + return n - len(data), err +} + +var gzipPrologue = template.Must(template.New("").Parse(` +import ( + "bytes" + "compress/gzip" + "io/ioutil" +) + +func init() { + var ( + r *gzip.Reader + err error + ) + +{{ if gt (len .Args) 0 }} +{{ range $idx, $var := .Args }} +{{ $n := printf "%s_%d" $.VarName $idx }} + r, err = gzip.NewReader(bytes.NewReader({{ $n }}_gzip)) + if err != nil { + panic(err) + } + {{ $n }}, err = ioutil.ReadAll(r) + r.Close() + if err != nil { + panic(err) + } +{{ end }} +{{ else }} + r, err = gzip.NewReader(bytes.NewReader({{ .VarName }}_gzip)) + if err != nil { + panic(err) + } + {{ .VarName }}, err = ioutil.ReadAll(r) + r.Close() + if err != nil { + panic(err) + } +{{ end }} +} +`)) diff --git a/qrcodegenerator.go b/qrcodegenerator.go index 8487f76..b9800d2 100644 --- a/qrcodegenerator.go +++ b/qrcodegenerator.go @@ -1,9 +1,9 @@ package qrbill import ( + "bytes" "image" "image/draw" - "os" "github.com/boombuler/barcode" "github.com/boombuler/barcode/qr" @@ -50,15 +50,8 @@ func generateQrCodeImage(payload string) (image.Image, error) { } func overlayWithSwissCross(qrCodeImage image.Image) (image.Image, error) { - // TODO: bundle the swiss cross image instead of reading it - const swissCrossPath = "/home/michael/go/src/github.com/stapelberg/qrbill/third_party/swiss-cross/CH-Kreuz_7mm/CH-Kreuz_7mm.png" - - f, err := os.Open(swissCrossPath) - if err != nil { - return nil, err - } - defer f.Close() - swissCrossImage, _, err := image.Decode(f) + b := swisscross["third_party/swiss-cross/CH-Kreuz_7mm/CH-Kreuz_7mm.png"] + swissCrossImage, _, err := image.Decode(bytes.NewReader(b)) if err != nil { return nil, err }