add printer
This commit is contained in:
317
img.go
Normal file
317
img.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"image"
|
||||
_ "image/png"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"embed"
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/kenshaw/escpos"
|
||||
)
|
||||
|
||||
const (
|
||||
canvasWidth = 550
|
||||
padding = 5
|
||||
lineGap = 12
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var fontFs embed.FS
|
||||
|
||||
//export GenPNG
|
||||
func GenPNG(outputPath *C.char, payload *C.char, tmpl *C.char) *C.char {
|
||||
goPath := C.GoString(outputPath)
|
||||
goPayload := C.GoString(payload)
|
||||
goTmpl := C.GoString(tmpl)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
err := json.Unmarshal([]byte(goPayload), &data)
|
||||
if err != nil {
|
||||
return NewErr(err)
|
||||
}
|
||||
|
||||
htmlStr, err := renderTemplate(goTmpl, data)
|
||||
if err != nil {
|
||||
return NewErr(err)
|
||||
}
|
||||
|
||||
htmlStr = uni2zg(htmlStr)
|
||||
root, err := html.Parse(strings.NewReader(htmlStr))
|
||||
if err != nil {
|
||||
return NewErr(err)
|
||||
}
|
||||
|
||||
fontBytes, err := fontFs.ReadFile("static/Zawgyi-One.ttf")
|
||||
if err != nil {
|
||||
return NewErr(err)
|
||||
}
|
||||
ttfFont, err := opentype.Parse(fontBytes)
|
||||
if err != nil {
|
||||
return NewErr(err)
|
||||
}
|
||||
|
||||
face, err := opentype.NewFace(ttfFont, &opentype.FaceOptions{
|
||||
Size: 24,
|
||||
DPI: 72,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
return NewErr(err)
|
||||
}
|
||||
|
||||
// First pass: compute required height
|
||||
dummyDC := gg.NewContext(canvasWidth, 150)
|
||||
dummyDC.SetFontFace(face)
|
||||
bodyNode := findNode(root, "body")
|
||||
if bodyNode == nil {
|
||||
bodyNode = root
|
||||
}
|
||||
|
||||
computedHeight := computeHeight(bodyNode, padding, dummyDC, face)
|
||||
|
||||
// Second pass: actual rendering
|
||||
dc := gg.NewContext(canvasWidth, computedHeight+50)
|
||||
dc.SetRGB(1, 1, 1)
|
||||
dc.Clear()
|
||||
dc.SetFontFace(face)
|
||||
|
||||
y := padding
|
||||
renderNode(dc, bodyNode, &y, face)
|
||||
|
||||
err = dc.SavePNG(goPath)
|
||||
if err != nil {
|
||||
return NewErr(err)
|
||||
}
|
||||
|
||||
// PrintReceipt(goPath)
|
||||
return NewOk(nil)
|
||||
}
|
||||
|
||||
func renderTemplate(tmp string, data map[string]interface{}) (string, error) {
|
||||
tmpl := template.Must(template.New("mytemplate").Parse(tmp))
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func renderNode(dc *gg.Context, n *html.Node, y *int, face font.Face) {
|
||||
if n.Type == html.ElementNode {
|
||||
switch n.Data {
|
||||
case "h1":
|
||||
drawTextBlock(dc, getText(n), 28, y, face)
|
||||
case "p":
|
||||
drawTextBlock(dc, getText(n), 24, y, face)
|
||||
case "img":
|
||||
drawImage(dc, n, y)
|
||||
case "table":
|
||||
renderTable(dc, n, y, face)
|
||||
}
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
renderNode(dc, c, y, face)
|
||||
}
|
||||
}
|
||||
|
||||
func drawTextBlock(dc *gg.Context, text string, size float64, y *int, face font.Face) {
|
||||
dc.SetFontFace(face)
|
||||
lines := wordWrap(dc, text, canvasWidth-padding*2)
|
||||
for _, line := range lines {
|
||||
dc.SetRGB(0, 0, 0)
|
||||
dc.DrawString(line, padding, float64(*y))
|
||||
*y += int(size) + lineGap
|
||||
}
|
||||
*y += 5
|
||||
}
|
||||
|
||||
func wordWrap(dc *gg.Context, text string, maxWidth int) []string {
|
||||
words := strings.Fields(text)
|
||||
if len(words) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
var lines []string
|
||||
current := words[0]
|
||||
for _, w := range words[1:] {
|
||||
test := current + " " + w
|
||||
wWidth, _ := dc.MeasureString(test)
|
||||
if int(wWidth) > maxWidth {
|
||||
lines = append(lines, current)
|
||||
current = w
|
||||
} else {
|
||||
current = test
|
||||
}
|
||||
}
|
||||
lines = append(lines, current)
|
||||
return lines
|
||||
}
|
||||
|
||||
func drawImage(dc *gg.Context, n *html.Node, y *int) {
|
||||
src := getAttr(n, "src")
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
img, _, _ := image.Decode(file)
|
||||
widthStr := getAttr(n, "width")
|
||||
if widthStr != "" {
|
||||
w, _ := strconv.Atoi(widthStr)
|
||||
scale := float64(w) / float64(img.Bounds().Dx())
|
||||
dc.Push()
|
||||
dc.Scale(scale, scale)
|
||||
dc.DrawImage(img, padding, *y)
|
||||
dc.Pop()
|
||||
*y += int(float64(img.Bounds().Dy()) * scale)
|
||||
} else {
|
||||
dc.DrawImage(img, padding, *y)
|
||||
*y += img.Bounds().Dy()
|
||||
}
|
||||
*y += 10
|
||||
}
|
||||
|
||||
func renderTable(dc *gg.Context, table *html.Node, y *int, face font.Face) {
|
||||
rows := extractRows(table)
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
colCount := len(rows[0])
|
||||
cellWidth := (canvasWidth - padding*2) / colCount
|
||||
dc.SetFontFace(face)
|
||||
for _, row := range rows {
|
||||
x := padding
|
||||
for _, cell := range row {
|
||||
dc.DrawRectangle(float64(x), float64(*y), float64(cellWidth), 30)
|
||||
dc.Stroke()
|
||||
dc.SetRGB(0, 0, 0)
|
||||
dc.DrawString(cell, float64(x+8), float64(*y+20))
|
||||
x += cellWidth
|
||||
}
|
||||
*y += 30
|
||||
}
|
||||
*y += 10
|
||||
}
|
||||
|
||||
func extractRows(table *html.Node) [][]string {
|
||||
var rows [][]string
|
||||
var traverse func(*html.Node)
|
||||
traverse = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "tr" {
|
||||
var row []string
|
||||
for td := n.FirstChild; td != nil; td = td.NextSibling {
|
||||
if td.Type == html.ElementNode && (td.Data == "td" || td.Data == "th") {
|
||||
row = append(row, getText(td))
|
||||
}
|
||||
}
|
||||
if len(row) > 0 {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
traverse(c)
|
||||
}
|
||||
}
|
||||
traverse(table)
|
||||
return rows
|
||||
}
|
||||
|
||||
func getText(n *html.Node) string {
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
if n.Type == html.TextNode {
|
||||
return strings.TrimSpace(n.Data)
|
||||
}
|
||||
var buf strings.Builder
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
buf.WriteString(getText(c))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func getAttr(n *html.Node, key string) string {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == key {
|
||||
return a.Val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findNode(n *html.Node, tag string) *html.Node {
|
||||
if n.Type == html.ElementNode && n.Data == tag {
|
||||
return n
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if res := findNode(c, tag); res != nil {
|
||||
return res
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func computeHeight(n *html.Node, currentY int, dc *gg.Context, face font.Face) int {
|
||||
y := currentY
|
||||
if n.Type == html.ElementNode {
|
||||
switch n.Data {
|
||||
case "h1":
|
||||
dc.SetFontFace(face)
|
||||
lines := wordWrap(dc, getText(n), canvasWidth-padding*2)
|
||||
y += len(lines)*28 + len(lines)*lineGap + 5
|
||||
case "p":
|
||||
dc.SetFontFace(face)
|
||||
lines := wordWrap(dc, getText(n), canvasWidth-padding*2)
|
||||
y += len(lines)*18 + len(lines)*lineGap + 5
|
||||
case "table":
|
||||
rows := extractRows(n)
|
||||
y += len(rows)*30 + 10
|
||||
case "img":
|
||||
widthStr := getAttr(n, "width")
|
||||
h := 100
|
||||
if widthStr != "" {
|
||||
h = 100
|
||||
}
|
||||
y += h + 10
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
y = computeHeight(c, y, dc, face)
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func printImg(prt *escpos.Escpos, imgPath string) error {
|
||||
imgFile, err := os.Open(imgPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
img, _, err := image.Decode(imgFile)
|
||||
defer imgFile.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
gray := toMonochrome(img)
|
||||
data := escposRaster(gray)
|
||||
|
||||
_, err = prt.WriteRaw(data)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user