Skip to content

Commit

Permalink
WASM support (#61)
Browse files Browse the repository at this point in the history
support WASM
  • Loading branch information
yeqown authored Feb 10, 2022
1 parent 6283ba2 commit bd066a4
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ QR code (abbreviated from Quick Response Code) is the trademark for a type of ma
- [x] Not only shape of cell, but also color of QR Code background and foreground color.
- [x] `WithLogoImage`, `WithLogoImageFilePNG`, `WithLogoImageFileJPEG` help you add an icon at the central of QR Code.
- [x] `WithBorderWidth` allows to specify any width of 4 sides around the qrcode.
- [x] `WebAssembly` support, check out the [Example](./example/webassembly/README.md) and [README](cmd/wasm/README.md) for more detail.

### Install

Expand Down
26 changes: 26 additions & 0 deletions cmd/wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## WebAssembly support

This part provide support to compile and run WebAssembly code.

```javascript
let option = {
encodeVersion: 0, // 0 - 40
encodeMode: 2, // 0 - 3
encodeECLevel: "Q", // L, M, Q, H

outputBgColor: "#123123", // #000000 - #ffffff
outputBgTransparent: false, // true - false
outputQrColor: "#666666", // #000000 - #ffffff
outputQrWidth: 20, // 0 - 256
outputCircleShape: true, // true - false
outputImageEncoder: "png", // png, jpeg, jpg
outputMargin: 20, // 0 - 256
}
let r = generateQRCode("content", option)
// output:
// {
"success": true,
"error": "",
"base64EncodedImage": "iVBORw0KGgoAAAANSUhEUgAAAmwAAAJ... more"
}
```
3 changes: 3 additions & 0 deletions cmd/wasm/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

GOOS=js GOARCH=wasm go build -o com.github.yeqown.goqrcode.wasm
16 changes: 16 additions & 0 deletions cmd/wasm/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module wasm

go 1.18

require (
github.com/pkg/errors v0.9.1
github.com/yeqown/go-qrcode/v2 v2.0.2
github.com/yeqown/go-qrcode/writer/standard v1.1.1
)

require (
github.com/fogleman/gg v1.3.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
)
85 changes: 85 additions & 0 deletions cmd/wasm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//go:build js && wasm

package main

import (
"bytes"
"fmt"
"syscall/js"

"github.com/pkg/errors"
qrcode "github.com/yeqown/go-qrcode/v2"
stdw "github.com/yeqown/go-qrcode/writer/standard"
)

func main() {
js.Global().Set("generateQRCode", js.FuncOf(genqrcode))
fmt.Println("com.github.yeqown.goqrcode.wasm loaded")

select {}
}

// genqrcode generates a qrcode image and returns the base64 encoded string.
// args should be a string array with length of 2 at most. the first one is the
// content string which will be encoded to qrcode, the second one is the encoding
// option, which is optional.
//
// let result = generateQRCode("content", {
// qrWidth: 200,
// qrMargin: 10,
// qrColor: "#000000",
// qrBackColor: "#ffffff",
// encLevel: "H",
// encVersion: 7,
// })
// more options refer to the `genOption` struct
func genqrcode(_ js.Value, args []js.Value) (v interface{}) {
var (
srcContent = ""
r = new(genResult)
opt = new(genOption)
)

defer func() {
v = r.JSValue()
}()

switch len(args) {
case 0:
r.setError(errors.New("no args"))
return
case 1:
srcContent = args[0].String()
case 2:
fallthrough
default:
srcContent = args[0].String()
opt = optionFromJSValue(args[1])
}

//if err := opt.validate(); err != nil {
// r.setError(errors.Wrap(err, "invalid option"))
// return
//}

qrc, err := qrcode.NewWith(srcContent, opt.encodeOptions()...)
if err != nil {
r.setError(errors.Wrap(err, "genqrcode qrcode"))
return
}

buf := bytes.NewBuffer(nil)
wr := nopCloser{Writer: buf}
w := stdw.NewWithWriter(wr, opt.outputOptions()...)

err = qrc.Save(w)
if err != nil {
r.setError(errors.Wrap(err, "apply output option"))
return
}

// apply image to result
r.setImage(buf)

return
}
178 changes: 178 additions & 0 deletions cmd/wasm/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//go:build js && wasm

package main

import (
"bytes"
"encoding/base64"
"io"
"syscall/js"

"github.com/yeqown/go-qrcode/v2"
stdw "github.com/yeqown/go-qrcode/writer/standard"
)

// encodeOption refers to github.com/yeqown/go-qrcode/v2.encodingOption
type encodeOption struct {
version int // encodeVersion
mode uint8 // encodeMode specifies which encMode to use (0/1/2/3).
ecLevel string // encodeECLevel specifies which ecLevel to use (L/M/Q/H)
}

// outputOption refers to github.com/yeqown/go-qrcode/writer/standard.outputImageOptions
type outputOption struct {
bgColor string // outputBgColor is the background color of the QR code image.
bgTransparent bool // outputBgTransparent indicates whether the background color is transparent.
qrColor string // outputQrColor is the foreground color of the QR code.
qrWidth uint8 // outputQrWidth is the width of the QR code.
circleShape bool // outputCircleShape indicates whether to draw the qr block in circle shape.
imageEncoder string // outputImageEncoder specifies file format would be encoded the QR image. (jpg/jpeg/png)
margin int // outputMargin is the border width of the output image.
}

// genOption is a type of option for generating code.
type genOption struct {
encodeOption
outputOption
}

// optionFromJSValue converts js.Value to genOption.
func optionFromJSValue(option js.Value) *genOption {
//fmt.Println(option.Type().String())
if option.IsNull() || option.IsUndefined() || option.Type().String() != "object" {
// option must be an object, otherwise return default empty option.
return nil
}

return &genOption{
encodeOption: encodeOption{
version: option.Get("encodeVersion").Int(),
mode: uint8(option.Get("encodeMode").Int()),
ecLevel: option.Get("encodeECLevel").String(),
},
outputOption: outputOption{
bgColor: option.Get("outputBgColor").String(),
bgTransparent: option.Get("outputBgTransparent").Bool(),
qrColor: option.Get("outputQrColor").String(),
qrWidth: uint8(option.Get("outputQrWidth").Int()),
circleShape: option.Get("outputCircleShape").Bool(),
imageEncoder: option.Get("outputImageEncoder").String(),
margin: option.Get("outputMargin").Int(),
},
}
}

// validate genOption.
func (o *genOption) validate() error {
if o == nil {
return nil
}

return nil
}

func (o *genOption) encodeOptions() []qrcode.EncodeOption {
if o == nil {
return nil
}

out := make([]qrcode.EncodeOption, 0, 4)

if o.encodeOption.version != 0 {
out = append(out, qrcode.WithVersion(o.encodeOption.version))
}

switch o.encodeOption.mode {
case uint8(qrcode.EncModeAlphanumeric):
out = append(out, qrcode.WithEncodingMode(qrcode.EncModeAlphanumeric))
case uint8(qrcode.EncModeNumeric):
out = append(out, qrcode.WithEncodingMode(qrcode.EncModeNumeric))
case uint8(qrcode.EncModeByte):
out = append(out, qrcode.WithEncodingMode(qrcode.EncModeByte))
case uint8(qrcode.EncModeJP):
out = append(out, qrcode.WithEncodingMode(qrcode.EncModeJP))
}

switch o.encodeOption.ecLevel {
case "L":
out = append(out, qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionLow))
case "M":
out = append(out, qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium))
case "Q":
out = append(out, qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionQuart))
case "H":
out = append(out, qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionHighest))
}

return out
}

func (o *genOption) outputOptions() []stdw.ImageOption {
if o == nil {
return nil
}

out := make([]stdw.ImageOption, 0, 8)

if o.outputOption.bgColor != "" {
out = append(out, stdw.WithBgColorRGBHex(o.outputOption.bgColor))
}
if o.outputOption.bgTransparent {
out = append(out, stdw.WithBgTransparent())
}
if o.outputOption.qrColor != "" {
out = append(out, stdw.WithFgColorRGBHex(o.outputOption.qrColor))
}
if o.outputOption.qrWidth != 0 {
out = append(out, stdw.WithQRWidth(o.outputOption.qrWidth))
}
if o.outputOption.circleShape {
out = append(out, stdw.WithCircleShape())
}

switch o.outputOption.imageEncoder {
case "jpg", "jpeg":
out = append(out, stdw.WithBuiltinImageEncoder(stdw.JPEG_FORMAT))
case "png":
fallthrough
default:
out = append(out, stdw.WithBuiltinImageEncoder(stdw.PNG_FORMAT))
}

if o.outputOption.margin != 0 {
out = append(out, stdw.WithBorderWidth(o.outputOption.margin))
}

return out
}

// genResult is result contains generated code image or error message.
type genResult struct {
Success bool `json:"success"`
Error string `json:"error"`
Base64EncodedImage string `json:"base64EncodedImage"`
}

func (r *genResult) setError(err error) {
r.Success = false
r.Error = err.Error()
}

func (r *genResult) setImage(buf *bytes.Buffer) {
r.Success = true
r.Base64EncodedImage = base64.StdEncoding.EncodeToString(buf.Bytes())
}

func (r *genResult) JSValue() js.Value {
return js.ValueOf(map[string]any{
"success": r.Success,
"error": r.Error,
"base64EncodedImage": r.Base64EncodedImage,
})
}

type nopCloser struct {
io.Writer
}

func (nopCloser) Close() error { return nil }
32 changes: 32 additions & 0 deletions cmd/wasm/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"encoding/json"
"testing"
)

func Test_genOption(t *testing.T) {
o := genOption{
encodeOption: encodeOption{
version: 0,
mode: 0,
ecLevel: "",
},
outputOption: outputOption{
bgColor: "",
bgTransparent: false,
qrColor: "",
qrWidth: 0,
circleShape: false,
imageEncoder: "",
margin: 0,
},
}

byts, err := json.MarshalIndent(o, "", " ")
if err != nil {
t.Error(err)
}

t.Log(string(byts))
}
2 changes: 2 additions & 0 deletions example/webassembly/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
com.github.yeqown.goqrcode.wasm
wasm_exec.js
14 changes: 14 additions & 0 deletions example/webassembly/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## WebAssembly Example

This example demonstrates how to use the `go-qrcode/wasm` pre-compiled `wasm` to generate
QRCode image in Web browser.

```bash
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
cp "$PATH/go-qrcode/wasm/com.github.yeqown.goqrcode.wasm" .
# then serve the index.html in browser
python3 -m http.server
# it serves the index.html on http://localhost:8000, you can use another way too.
```

after that, you can visit http://localhost:8000 to see the demo.
Loading

0 comments on commit bd066a4

Please sign in to comment.