176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"syscall/js"
|
|
|
|
"github.com/go-pdf/fpdf"
|
|
"github.com/thegrumpylion/jsref"
|
|
|
|
"covergen/pkg/covergen"
|
|
)
|
|
|
|
// jsmap is just a type alias that corresponds to the ES5 Record<string, any>, or just a plain object {}
|
|
type jsmap = map[string]interface{}
|
|
|
|
type covergenArgs struct {
|
|
Customer string `jsref:"customer"`
|
|
Number string `jsref:"number"`
|
|
NumberPrefix string `jsref:"numberPrefix"`
|
|
HLColor string `jsref:"hlColor"`
|
|
}
|
|
|
|
// parseHexColor takes a CSS hex color (#RRGGBB or the shorthand #RGB) and parses out the red, green, and blue components
|
|
// into a covergen.Color
|
|
func parseHexColor(s string) (c covergen.Color, err error) {
|
|
switch len(s) {
|
|
case 7:
|
|
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
|
|
case 4:
|
|
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
|
|
// Double the hex digits:
|
|
c.R *= 17
|
|
c.G *= 17
|
|
c.B *= 17
|
|
default:
|
|
err = fmt.Errorf("invalid length, must be 7 or 4")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// settingsFromValue tries to parse the WASM boundary-crossing arguments blob into a golang-native settings object
|
|
func settingsFromValue(arg js.Value) (*covergen.CoverSettings, error) {
|
|
settings := covergenArgs{
|
|
NumberPrefix: "offerte",
|
|
}
|
|
err := jsref.Unmarshal(&settings, arg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if settings.Number == "" {
|
|
return nil, errors.New("number: shouldn't be empty")
|
|
}
|
|
|
|
if settings.Customer == "" {
|
|
return nil, errors.New("customer: shouldn't be empty")
|
|
}
|
|
|
|
color := covergen.Color{R: 255}
|
|
if settings.HLColor != "" {
|
|
color, err = parseHexColor(settings.HLColor)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("hlColor: %w", err)
|
|
}
|
|
}
|
|
|
|
return &covergen.CoverSettings{
|
|
Number: settings.Number,
|
|
NumberPrefix: settings.NumberPrefix,
|
|
CustomerName: settings.Customer,
|
|
HLColor: color,
|
|
}, nil
|
|
}
|
|
|
|
// pdfToJs takes the pdf object and outputs it into a Uint8Array and passes that back to JS.
|
|
func pdfToJs(pdf *fpdf.Fpdf) (js.Value, error) {
|
|
var buf bytes.Buffer
|
|
if err := pdf.Output(&buf); err != nil {
|
|
return js.Null(), fmt.Errorf("failed to write pdf: %w", err)
|
|
}
|
|
|
|
s := buf.Bytes()
|
|
ta := js.Global().Get("Uint8Array").New(len(s))
|
|
js.CopyBytesToJS(ta, s)
|
|
return ta, nil
|
|
}
|
|
|
|
// generateCover is the JS exposed entrypoint to generate a 2 page pdf with both the front and back cover
|
|
func generateCover(this js.Value, args []js.Value) interface{} {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Printf("recovered from critical error: %+v", r)
|
|
}
|
|
}()
|
|
|
|
if len(args) != 1 {
|
|
return jsmap{"error": "missing argument"}
|
|
}
|
|
|
|
arg := args[0]
|
|
if arg.Type() != js.TypeObject {
|
|
return jsmap{"error": "expected object"}
|
|
}
|
|
|
|
settings, err := settingsFromValue(arg)
|
|
if err != nil {
|
|
return jsmap{"error": err.Error()}
|
|
}
|
|
|
|
pdf, err := covergen.GenerateInvoice(*settings)
|
|
if err != nil {
|
|
return jsmap{"error": err.Error()}
|
|
}
|
|
|
|
jsBytes, err := pdfToJs(pdf)
|
|
if err != nil {
|
|
return jsmap{"error": err.Error()}
|
|
}
|
|
return jsBytes
|
|
}
|
|
|
|
func generateSplitCover(this js.Value, args []js.Value) interface{} {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Printf("recovered from critical error: %+v", r)
|
|
}
|
|
}()
|
|
|
|
if len(args) != 1 {
|
|
return jsmap{"error": "missing argument"}
|
|
}
|
|
|
|
arg := args[0]
|
|
if arg.Type() != js.TypeObject {
|
|
return jsmap{"error": "expected object"}
|
|
}
|
|
|
|
settings, err := settingsFromValue(arg)
|
|
if err != nil {
|
|
return jsmap{"error": err.Error()}
|
|
}
|
|
|
|
front, err := covergen.GenerateFrontCover(*settings)
|
|
if err != nil {
|
|
return jsmap{"error": fmt.Sprintf("failed to render front: %s", err.Error())}
|
|
}
|
|
|
|
back, err := covergen.GenerateBackCover(*settings)
|
|
if err != nil {
|
|
return jsmap{"error": fmt.Sprintf("failed to render back: %s", err.Error())}
|
|
}
|
|
|
|
frontJSBytes, err := pdfToJs(front)
|
|
if err != nil {
|
|
return jsmap{"error": fmt.Sprintf("failed to render front: %s", err.Error())}
|
|
}
|
|
backJSBytes, err := pdfToJs(back)
|
|
if err != nil {
|
|
return jsmap{"error": fmt.Sprintf("failed to render back: %s", err.Error())}
|
|
}
|
|
|
|
return jsmap{
|
|
"front": frontJSBytes,
|
|
"back": backJSBytes,
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
js.Global().Set("generateCover", js.FuncOf(generateCover))
|
|
js.Global().Set("generateSplitCover", js.FuncOf(generateSplitCover))
|
|
<-make(chan bool)
|
|
}
|