fix html to png

This commit is contained in:
2026-04-08 15:31:23 +06:30
parent 016cc5b6fe
commit ecb03c1fb7
12 changed files with 694 additions and 163 deletions

3
.gitignore vendored
View File

@@ -26,4 +26,5 @@ go.work.sum
.env .env
build build
assets assets
libgofunc

15
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Dev",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd",
},
]
}

141
canvas.go Normal file
View File

@@ -0,0 +1,141 @@
package libgofunc
import (
"strconv"
"strings"
"github.com/fogleman/gg"
"golang.org/x/image/font"
"golang.org/x/net/html"
)
// -------------------- STYLE --------------------
type Style struct {
PaddingLeft float64
PaddingRight float64
PaddingTop float64
PaddingBottom float64
FontSize float64
Width float64
Height float64
TextAlign string
VerticalAlign string
Border float64
}
func parseStyle(styleStr string) Style {
style := Style{
FontSize: 16,
TextAlign: "left",
VerticalAlign: "top",
}
parts := strings.Split(styleStr, ";")
for _, p := range parts {
kv := strings.SplitN(strings.TrimSpace(p), ":", 2)
if len(kv) != 2 {
continue
}
key := strings.TrimSpace(kv[0])
val := strings.TrimSpace(kv[1])
switch key {
case "padding":
v := parsePx(val)
style.PaddingLeft, style.PaddingRight, style.PaddingTop, style.PaddingBottom = v, v, v, v
case "padding-left":
v := parsePx(val)
style.PaddingLeft = v
case "padding-right":
v := parsePx(val)
style.PaddingRight = v
case "padding-top":
v := parsePx(val)
style.PaddingTop = v
case "padding-bottom":
v := parsePx(val)
style.PaddingBottom = v
case "font-size":
style.FontSize = parsePx(val)
case "width":
style.Width = parsePx(val)
case "height":
style.Height = parsePx(val)
case "text-align":
style.TextAlign = val
case "vertical-align":
style.VerticalAlign = val
case "border":
style.Border = parsePx(val)
}
}
return style
}
func parsePx(v string) float64 {
v = strings.ReplaceAll(v, "px", "")
f, _ := strconv.ParseFloat(v, 64)
return f
}
// -------------------- NODE --------------------
type Node struct {
Tag string
Text string
Attr map[string]string
Style Style
Children []*Node
}
func (n Node) getSrc() string {
for k, v := range n.Attr {
if k == "src" {
return v
}
}
return ""
}
func (n Node) getHeight() int {
return int(n.Style.Height)
}
func BuildTree(n *html.Node, canvasWidth int, dc *gg.Context, face *font.Face) (*Node, int) {
if n.Type != html.ElementNode {
return nil, 0
}
text := ""
if n.Data == "h1" || n.Data == "h2" || n.Data == "h3" ||
n.Data == "p" || n.Data == "td" || n.Data == "th" ||
n.Type == html.TextNode {
text = getText(n)
}
node := &Node{
Tag: n.Data,
Text: text,
Attr: map[string]string{},
}
for _, a := range n.Attr {
node.Attr[a.Key] = a.Val
}
if s, ok := node.Attr["style"]; ok {
node.Style = parseStyle(s)
}
xPadding := node.Style.PaddingLeft + node.Style.PaddingRight
yPadding := node.Style.PaddingTop + node.Style.PaddingBottom
fontSize := node.Style.FontSize
h := nodeHeight(n, dc, canvasWidth, int(xPadding), int(yPadding),
face, fontSize, node.getHeight())
for c := n.FirstChild; c != nil; c = c.NextSibling {
child, ch := BuildTree(c, canvasWidth, dc, face)
if child != nil {
node.Children = append(node.Children, child)
}
h += ch
}
return node, h
}

109
cmd/main.go Normal file
View File

@@ -0,0 +1,109 @@
package main
import (
/*
#include <stdint.h>
*/
"C"
"fmt"
_ "image/png"
"github.com/google/gousb"
)
import (
"github.com/google/gousb/usbid"
"gt.mokkon.com/sainw/libgofunc"
)
//export Sum
func Sum(a, b int) int {
return a + b
}
func main() {
payload := `{"Name":"Ko Myo","Amount":3000}`
const temp = `
<img src="logo.png" style="width:230;height:200;padding-left:130px;padding-top:30px"/>
<h1 style="padding-left:150px">New Day Energy</h1>
<p style="padding-left:10px">Address: မင်္ဂလာပါ {{.Name}}</p>
<p style="padding-left:10px">Receipt: RCPT001</p>
<p style="padding-left:10px">Phone: 0977777777</p>
<p style="padding-left:10px">Date: 4 Jan 2026 15:38:38</p>
<p style="padding-left:10px">Car No.: 3J/3883</p>
<p style="padding-left:10px">Casher: မနှင်းနှင်း</p>
<p style="padding-left:10px">MOP: B2B</p>
<p style="padding-left:10px">Tier: 1</p>
<p style="padding-left:10px">Deal No.: RR</p>
<hr style="height:1px;padding-left:10px;padding-right:10px;padding-bottom:10px"/>
<table style="padding-left:10px;padding-bottom:10px;border:0px;font-size:16px;border:0px;">
<tr>
<th>Item</th>
<th>Pump</th>
<th>Price</th>
<th>Liter</th>
<th>Gallon</th>
<th>Amount</th>
</tr>
<tr>
<td>95 RON</td>
<td>P8</td>
<td>2,000</td>
<td>3.25</td>
<td>0.715</td>
<td>6,500</td>
</tr>
</table>
<p style="padding-left:0px;padding-top:50px;">
စက်သုံးဆီ အရေအတွက် နှင့် အရည်အသွေးစျေးနှုန်းများ အားသံသယရှိပါက ph 09450539099, 09765421033, 09765421029 သို့တိုင်ကြား နိုင်ပါသည်။</p>
`
result := libgofunc.GenImg(550, "./out.png", payload, temp)
fmt.Println("Result:", result)
// PrintImg(C.CString("usb:/dev/usb/lp1"), C.CString("build/out.png"))
// libgofunc.Print("int:/dev/bus/usb/001/046", "./out.png")
libgofunc.Print("tcp:192.168.100.151:9100", "./out.png")
// printer := "tcp:192.168.100.151:9100"
// ListUSB()
}
func ListUSB() {
ctx := gousb.NewContext()
defer ctx.Close()
// OpenDevices returns all devices that return 'true' in the filter
devices, _ := ctx.OpenDevices(func(desc *gousb.DeviceDesc) bool {
// fmt.Println(usbid.Describe(desc))
// path := fmt.Sprintf("/dev/bus/usb/%03d/%03d", desc.Bus, desc.Address)
// fmt.Printf("Device Path: %s\n", path)
// switch desc.Class {
// case 7:
// fmt.Println("Found a standard printer")
// case 0xFF:
// fmt.Println("Found a vendor-specific device (likely your printer)")
// }
return true // Open every device to read its name
})
defer func() {
for _, d := range devices {
d.Close()
}
}()
for _, d := range devices {
fmt.Println(usbid.Describe(d.Desc))
fmt.Printf("Bus: %d, Address: %d\n", d.Desc.Bus, d.Desc.Address)
// Read human-readable names from the device strings
manufacturer, _ := d.Manufacturer()
product, _ := d.Product()
serial, _ := d.SerialNumber()
// d.Desc.Path
fmt.Printf("ID: %s:%s | Manufacturer: %s | Product: %s | Serial: %s\n",
d.Desc.Vendor, d.Desc.Product, manufacturer, product, serial)
}
}

View File

@@ -1,4 +1,4 @@
package main package libgofunc
import ( import (
"image" "image"

40
go.mod
View File

@@ -1,16 +1,46 @@
module libgofunc module gt.mokkon.com/sainw/libgofunc
go 1.22.0 go 1.25.0
require ( require (
github.com/dlclark/regexp2 v1.11.5 github.com/dlclark/regexp2 v1.11.5
github.com/fogleman/gg v1.3.0 github.com/fogleman/gg v1.3.0
github.com/kenshaw/escpos v0.0.0-20221114190919-df06b682a8fc github.com/kenshaw/escpos v0.0.0-20221114190919-df06b682a8fc
golang.org/x/image v0.24.0 golang.org/x/image v0.38.0
golang.org/x/net v0.34.0 golang.org/x/net v0.52.0
)
require (
codeberg.org/go-latex/latex v0.2.0 // indirect
codeberg.org/go-pdf/fpdf v0.11.1 // indirect
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect
github.com/Kagami/go-avif v0.1.0 // indirect
github.com/andybalholm/brotli v1.2.1 // indirect
github.com/benoitkugler/textlayout v0.3.2 // indirect
github.com/benoitkugler/textprocessing v0.0.6 // indirect
github.com/go-fonts/latin-modern v0.3.3 // indirect
github.com/go-text/typesetting v0.3.4 // indirect
github.com/kolesa-team/go-webp v1.0.5 // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 // indirect
github.com/tdewolff/font v0.0.0-20260314002930-9f995dac393e // indirect
github.com/tdewolff/minify/v2 v2.24.11 // indirect
github.com/tdewolff/parse/v2 v2.8.11 // indirect
github.com/wcharczuk/go-chart/v2 v2.1.2 // indirect
github.com/yuin/goldmark v1.8.2 // indirect
gonum.org/v1/plot v0.16.0 // indirect
modernc.org/knuth v0.5.5 // indirect
modernc.org/token v1.1.0 // indirect
star-tex.org/x/tex v0.7.1 // indirect
) )
require ( require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
golang.org/x/text v0.22.0 // indirect github.com/google/gousb v1.1.3
github.com/tdewolff/canvas v0.0.0-20260406091912-5d4f7059846e
golang.org/x/text v0.35.0 // indirect
) )

134
go.sum
View File

@@ -1,14 +1,148 @@
codeberg.org/go-latex/latex v0.2.0 h1:Ol/a6VHY06N+5gPfewswymoRb5ZcKDXWVaVegcx4hbI=
codeberg.org/go-latex/latex v0.2.0/go.mod h1:VJAwQir7/T8LZxj7xAPivISKiVOwkMpQ8bTuPQ31X0Y=
codeberg.org/go-pdf/fpdf v0.11.1 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs=
codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA=
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w=
github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA=
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
github.com/benoitkugler/textlayout v0.3.2 h1:Y/LTkwpQ9G0Fia9yhPmZA9IR5AnE8Cq30j3C+Gx5/IE=
github.com/benoitkugler/textlayout v0.3.2/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo=
github.com/benoitkugler/textprocessing v0.0.6 h1:obkMyj62GEPg3xUVYqROlCN22z1OleuZm6ULqX9Om1g=
github.com/benoitkugler/textprocessing v0.0.6/go.mod h1:Io0gN08/PXEzrSOWFa88xHx2Xv3VjvLMY7H76YoI23A=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/go-fonts/latin-modern v0.3.3 h1:g2xNgI8yzdNzIVm+qvbMryB6yGPe0pSMss8QT3QwlJ0=
github.com/go-fonts/latin-modern v0.3.3/go.mod h1:tHaiWDGze4EPB0Go4cLT5M3QzRY3peya09Z/8KSCrpY=
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o=
github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
github.com/kenshaw/escpos v0.0.0-20221114190919-df06b682a8fc h1:4JwmN2Scz1vR+hfSxkdy2IE/DzxX2Cftm2lhWHyN0k0= github.com/kenshaw/escpos v0.0.0-20221114190919-df06b682a8fc h1:4JwmN2Scz1vR+hfSxkdy2IE/DzxX2Cftm2lhWHyN0k0=
github.com/kenshaw/escpos v0.0.0-20221114190919-df06b682a8fc/go.mod h1:M+GIBmg2MqaSWIJrXCZS+/wRFbr9fOguRz3SHn8DRPE= github.com/kenshaw/escpos v0.0.0-20221114190919-df06b682a8fc/go.mod h1:M+GIBmg2MqaSWIJrXCZS+/wRFbr9fOguRz3SHn8DRPE=
github.com/kolesa-team/go-webp v1.0.5 h1:GZQHJBaE8dsNKZltfwqsL0qVJ7vqHXsfA+4AHrQW3pE=
github.com/kolesa-team/go-webp v1.0.5/go.mod h1:QmJu0YHXT3ex+4SgUvs+a+1SFCDcCqyZg+LbIuNNTnE=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 h1:ZdkidVdpLW13BQ9a+/3uerT2ezy9J7KQWH18JCfhDmI=
github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388/go.mod h1:C/WY5lmWfMtPFYYBTd3Lzdn4FTLr+RxlIeiBNye+/os=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/canvas v0.0.0-20260406091912-5d4f7059846e h1:A8iHpv0I/IDv+i5YQ/km/UlqSmonRz4UNIrckFxYc+A=
github.com/tdewolff/canvas v0.0.0-20260406091912-5d4f7059846e/go.mod h1:xCHrGFuVb9eeblIAffMuLzKoRnYHQj8xZq8yZ7YQfEw=
github.com/tdewolff/font v0.0.0-20260314002930-9f995dac393e h1:20EEwnJWwKApfX5KttlWbIjgXrXa+HUvkiVUow1hdJ0=
github.com/tdewolff/font v0.0.0-20260314002930-9f995dac393e/go.mod h1:EFuulYdR5cCTF2zKMW6NxchFgd5xtEPSrjkD1MFWDpA=
github.com/tdewolff/minify/v2 v2.24.11 h1:JlANsiWaRBXedoYtsiZgY3YFkdr42oF32vp2SLgQKi4=
github.com/tdewolff/minify/v2 v2.24.11/go.mod h1:exq1pjdrh9uAICdfVKQwqz6MsJmWmQahZuTC6pTO6ro=
github.com/tdewolff/parse/v2 v2.8.11 h1:SGyjEy3xEqd+W9WVzTlTQ5GkP/en4a1AZNZVJ1cvgm0=
github.com/tdewolff/parse/v2 v2.8.11/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/plot v0.16.0 h1:dK28Qx/Ky4VmPUN/2zeW0ELyM6ucDnBAj5yun7M9n1g=
gonum.org/v1/plot v0.16.0/go.mod h1:Xz6U1yDMi6Ni6aaXILqmVIb6Vro8E+K7Q/GeeH+Pn0c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/knuth v0.5.5 h1:6lap2U/ISm8aC/4NU58ALFCRllNPaK0EZcIGY/oDgUg=
modernc.org/knuth v0.5.5/go.mod h1:e5SBb35HQBj2aFwbBO3ClPcViLY3Wi0LzaOd7c/3qMk=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
star-tex.org/x/tex v0.7.1 h1:4qGAByRyY0WQsOjtcHlxz+FgrYxz8fzxIds2Gjepp5U=
star-tex.org/x/tex v0.7.1/go.mod h1:Y3y0U7sZTltTh/CDZIx0oAtMjG7eMaTuTtvDZGdyhJo=

293
img.go
View File

@@ -1,4 +1,4 @@
package main package libgofunc
import ( import (
/* /*
@@ -11,7 +11,6 @@ import (
"image" "image"
_ "image/png" _ "image/png"
"os" "os"
"strconv"
"strings" "strings"
"embed" "embed"
@@ -25,18 +24,24 @@ import (
"github.com/kenshaw/escpos" "github.com/kenshaw/escpos"
) )
import "log"
const ( const (
canvasWidth = 550 defalutFontSize = 18.0
padding = 5 defalutTableBorder = 5
lineGap = 12 lineGap = 12
elePSize = 18
eleH1Size = 32
eleH2Size = 24
eleH3Size = 19
) )
//go:embed static/* //go:embed static/*
var fontFs embed.FS var fontFs embed.FS
//export GenPNG //export GenPNG
func GenPNG(outputPath *C.char, payload *C.char, tmpl *C.char) *C.char { func GenPNG(width C.int, outputPath *C.char, payload *C.char, tmpl *C.char) *C.char {
canvasWidth := int(width)
goPath := C.GoString(outputPath) goPath := C.GoString(outputPath)
goPayload := C.GoString(payload) goPayload := C.GoString(payload)
goTmpl := C.GoString(tmpl) goTmpl := C.GoString(tmpl)
@@ -58,42 +63,29 @@ func GenPNG(outputPath *C.char, payload *C.char, tmpl *C.char) *C.char {
return NewErr(err) return NewErr(err)
} }
fontBytes, err := fontFs.ReadFile("static/Zawgyi-One.ttf") face, err := LoadFont(defalutFontSize)
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 { if err != nil {
return NewErr(err) return NewErr(err)
} }
// First pass: compute required height // First pass: compute required height
dummyDC := gg.NewContext(canvasWidth, 150) dummyDC := gg.NewContext(canvasWidth, 150)
dummyDC.SetFontFace(face) dummyDC.SetFontFace(*face)
bodyNode := findNode(root, "body") bodyNode := findNode(root, "body")
if bodyNode == nil { if bodyNode == nil {
bodyNode = root bodyNode = root
} }
computedHeight := computeHeight(bodyNode, padding, dummyDC, face) body, height := BuildTree(bodyNode, canvasWidth, dummyDC, face)
// Second pass: actual rendering // Second pass: actual rendering
dc := gg.NewContext(canvasWidth, computedHeight+50) dc := gg.NewContext(canvasWidth, height)
dc.SetRGB(1, 1, 1) dc.SetRGB(1, 1, 1)
dc.Clear() dc.Clear()
dc.SetFontFace(face) dc.SetFontFace(*face)
y := padding y := 0
renderNode(dc, bodyNode, &y, face) renderNode(dc, canvasWidth, body, &y, *face)
err = dc.SavePNG(goPath) err = dc.SavePNG(goPath)
if err != nil { if err != nil {
@@ -104,6 +96,12 @@ func GenPNG(outputPath *C.char, payload *C.char, tmpl *C.char) *C.char {
return NewOk(nil) return NewOk(nil)
} }
func GenImg(width int, outputPath, payload, tmpl string) string {
result := GenPNG(C.int(width), C.CString(outputPath), C.CString(payload), C.CString(tmpl))
r := C.GoString(result)
return r
}
func renderTemplate(tmp string, data map[string]interface{}) (string, error) { func renderTemplate(tmp string, data map[string]interface{}) (string, error) {
tmpl := template.Must(template.New("mytemplate").Parse(tmp)) tmpl := template.Must(template.New("mytemplate").Parse(tmp))
var buf bytes.Buffer var buf bytes.Buffer
@@ -113,34 +111,48 @@ func renderTemplate(tmp string, data map[string]interface{}) (string, error) {
return buf.String(), nil return buf.String(), nil
} }
func renderNode(dc *gg.Context, n *html.Node, y *int, face font.Face) { func renderNode(dc *gg.Context, canvasWidth int, n *Node, y *int, face font.Face) {
if n.Type == html.ElementNode { before := *y
switch n.Data { if n.Style.PaddingTop > 0 {
case "h1": *y += int(n.Style.PaddingTop)
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)
}
} }
switch n.Tag {
case "h1":
drawTextBlock(dc, n, canvasWidth, eleH1Size, y, face)
case "h2":
drawTextBlock(dc, n, canvasWidth, eleH2Size, y, face)
case "h3":
drawTextBlock(dc, n, canvasWidth, eleH3Size, y, face)
case "p":
drawTextBlock(dc, n, canvasWidth, elePSize, y, face)
case "hr":
renderLine(dc, n, canvasWidth, y)
case "img":
drawImage(dc, n, y)
case "table":
renderTable(dc, canvasWidth, n, y, face)
}
if n.Style.PaddingBottom > 0 {
*y += int(n.Style.PaddingBottom)
}
log.Printf("render %s y, y', height: %d, %d, %d\n", n.Tag, before, *y, *y-before)
for c := n.FirstChild; c != nil; c = c.NextSibling { for _, c := range n.Children {
renderNode(dc, c, y, face) renderNode(dc, canvasWidth, c, y, face)
} }
} }
func drawTextBlock(dc *gg.Context, text string, size float64, y *int, face font.Face) { func drawTextBlock(dc *gg.Context, n *Node, canvasWidth int, size float64, y *int, face font.Face) {
dc.SetFontFace(face) dc.SetFontFace(face)
lines := wordWrap(dc, text, canvasWidth-padding*2) padding := n.Style.PaddingLeft + n.Style.PaddingRight
lines := wordWrap(dc, n.Text, canvasWidth-int(padding))
SetFontSize(dc, size)
for _, line := range lines { for _, line := range lines {
dc.SetRGB(0, 0, 0) dc.SetRGB(0, 0, 0)
dc.DrawString(line, padding, float64(*y)) dc.DrawStringAnchored(line, padding, float64(*y), 0, 0.5)
// dc.DrawString(line, padding, float64(*y))
*y += int(size) + lineGap *y += int(size) + lineGap
} }
*y += 5
} }
func wordWrap(dc *gg.Context, text string, maxWidth int) []string { func wordWrap(dc *gg.Context, text string, maxWidth int) []string {
@@ -164,53 +176,101 @@ func wordWrap(dc *gg.Context, text string, maxWidth int) []string {
return lines return lines
} }
func drawImage(dc *gg.Context, n *html.Node, y *int) { func drawImage(dc *gg.Context, n *Node, y *int) {
src := getAttr(n, "src") src := n.getSrc()
file, err := os.Open(src) file, err := os.Open(src)
if err != nil { if err != nil {
return return
} }
defer file.Close() defer file.Close()
img, _, _ := image.Decode(file) img, _, _ := image.Decode(file)
widthStr := getAttr(n, "width") padding := n.Style.PaddingLeft
if widthStr != "" { h := n.Style.Height
w, _ := strconv.Atoi(widthStr) if n.Style.Width > 0 {
scale := float64(w) / float64(img.Bounds().Dx()) ix := padding
iy := float64(*y)
scale := n.Style.Width / float64(img.Bounds().Dx())
dc.Push() dc.Push()
dc.Scale(scale, scale) dc.Scale(scale, scale)
dc.DrawImage(img, padding, *y) if scale > 0 {
ix = ix / scale
iy = iy / scale
}
dc.DrawImage(img, int(ix), int(iy))
dc.Pop() dc.Pop()
*y += int(float64(img.Bounds().Dy()) * scale) if float64(img.Bounds().Dy())*scale > h {
h = float64(img.Bounds().Dy()) * scale
}
*y += int(h)
} else { } else {
dc.DrawImage(img, padding, *y) dc.DrawImage(img, int(padding), *y)
*y += img.Bounds().Dy() *y += img.Bounds().Dy()
} }
*y += 10
} }
func renderTable(dc *gg.Context, table *html.Node, y *int, face font.Face) { func renderTable(dc *gg.Context, canvasWidth int, table *Node, y *int, face font.Face) {
rows := extractRows(table) rows := extractRows(table)
if len(rows) == 0 { if len(rows) == 0 {
return return
} }
fontSize := defalutFontSize
if table.Style.FontSize > 0 {
fontSize = table.Style.FontSize
}
SetFontSize(dc, fontSize)
padding := int(table.Style.PaddingLeft + table.Style.PaddingRight)
colCount := len(rows[0]) colCount := len(rows[0])
cellWidth := (canvasWidth - padding*2) / colCount cellWidth := (canvasWidth - padding) / colCount
dc.SetFontFace(face) border := table.Style.Border
for _, row := range rows { for _, row := range rows {
x := padding x := padding
for _, cell := range row { for _, cell := range row {
dc.DrawRectangle(float64(x), float64(*y), float64(cellWidth), 30) if border > 0 {
dc.SetLineWidth(border)
dc.DrawRectangle(float64(x), float64(*y), float64(cellWidth), fontSize+defalutTableBorder)
}
dc.Stroke() dc.Stroke()
dc.SetRGB(0, 0, 0) dc.SetRGB(0, 0, 0)
dc.DrawString(cell, float64(x+8), float64(*y+20)) dc.DrawStringAnchored(cell, float64(x+8), float64(*y+20), 0, 0)
x += cellWidth x += cellWidth
} }
*y += 30 *y += int(fontSize) + defalutTableBorder
} }
*y += 10
} }
func extractRows(table *html.Node) [][]string { func renderLine(dc *gg.Context, line *Node, canvasWidth int, y *int) {
height := line.Style.Height
xPadding := line.Style.PaddingLeft + line.Style.PaddingRight
dc.SetLineWidth(height)
dc.DrawLine(line.Style.PaddingLeft, float64(*y), float64(canvasWidth)-xPadding, float64(*y))
*y += int(height)
}
func extractRows(table *Node) [][]string {
var rows [][]string
var traverse func(*Node)
traverse = func(n *Node) {
if n.Tag == "tr" {
var row []string
for _, td := range n.Children {
if td.Tag == "td" || td.Tag == "th" {
row = append(row, td.Text)
}
}
if len(row) > 0 {
rows = append(rows, row)
}
}
for _, c := range n.Children {
traverse(c)
}
}
traverse(table)
return rows
}
func extractNodeRows(table *html.Node) [][]string {
var rows [][]string var rows [][]string
var traverse func(*html.Node) var traverse func(*html.Node)
traverse = func(n *html.Node) { traverse = func(n *html.Node) {
@@ -247,15 +307,6 @@ func getText(n *html.Node) string {
return buf.String() 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 { func findNode(n *html.Node, tag string) *html.Node {
if n.Type == html.ElementNode && n.Data == tag { if n.Type == html.ElementNode && n.Data == tag {
return n return n
@@ -268,36 +319,6 @@ func findNode(n *html.Node, tag string) *html.Node {
return nil 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 { func printImg(prt *escpos.Escpos, imgPath string) error {
imgFile, err := os.Open(imgPath) imgFile, err := os.Open(imgPath)
if err != nil { if err != nil {
@@ -318,3 +339,73 @@ func printImg(prt *escpos.Escpos, imgPath string) error {
_, err = prt.WriteRaw(data) _, err = prt.WriteRaw(data)
return err return err
} }
func LoadFont(size float64) (*font.Face, error) {
fontBytes, err := fontFs.ReadFile("static/Zawgyi-One.ttf")
if err != nil {
return nil, err
}
ttfFont, err := opentype.Parse(fontBytes)
if err != nil {
return nil, err
}
face, err := opentype.NewFace(ttfFont, &opentype.FaceOptions{
Size: size,
DPI: 72,
Hinting: font.HintingFull,
})
if err != nil {
return nil, err
}
return &face, nil
}
func SetFontSize(dc *gg.Context, size float64) {
if size < 5 {
return
}
face, err := LoadFont(size)
if err != nil {
log.Println("SetFontSize: ", err.Error())
}
dc.SetFontFace(*face)
}
func nodeHeight(n *html.Node, dc *gg.Context, canvasWidth int, xPadding, yPadding int, face *font.Face, fontSize float64, implicit int) int {
y := 0
if n.Type == html.ElementNode {
h := 0
switch n.Data {
case "h1":
dc.SetFontFace(*face)
lines := wordWrap(dc, getText(n), canvasWidth-xPadding)
h = (len(lines) * eleH1Size) + (len(lines) * lineGap) + yPadding
case "h2":
dc.SetFontFace(*face)
lines := wordWrap(dc, getText(n), canvasWidth-xPadding)
h = (len(lines) * eleH2Size) + (len(lines) * lineGap) + yPadding
case "h3":
dc.SetFontFace(*face)
lines := wordWrap(dc, getText(n), canvasWidth-xPadding)
h = (len(lines) * eleH3Size) + (len(lines) * lineGap) + yPadding
case "p":
dc.SetFontFace(*face)
lines := wordWrap(dc, getText(n), canvasWidth-xPadding)
h = (len(lines) * elePSize) + (len(lines) * lineGap) + yPadding
case "hr":
h = yPadding
case "table":
rows := extractNodeRows(n)
h = (len(rows) * int(fontSize+defalutTableBorder)) + yPadding
case "img":
h = implicit + yPadding
}
if implicit > h {
h = implicit
}
y += h
log.Printf("nodeHeight %s height: %d, \n", n.Data, y)
}
return y
}

51
main.go
View File

@@ -1,51 +0,0 @@
package main
import (
/*
#include <stdint.h>
*/
"C"
"fmt"
_ "image/png"
)
//export Sum
func Sum(a, b int) int {
return a + b
}
func main() {
payload := `{"Name":"Ko Myo","Amount":3000}`
const temp = `
<table>
<tr>
<th><img src="static/logo.png" width="80"/></th>
<th><h1 style="font-size:28">မြန်မာစာသည်တို့စာ (Invoice)</h1></th>
</tr>
</table>
<p>မင်္ဂလာပါ {{.Name}}, သင်၏ အိုင်ဗွိုင်းစိန်း အချက်အလက်များပါသည်။</p>
<p>အထက်ပါ အကွက်နှစ်ကွက်ကတော့ အချိန်နဲ့ တပြေးညီ ဖောင့်ပြောင်းပေးတဲ့ မြန်မာဖောင့် ကွန်ဗာတာပဲ ဖြစ်ပါတယ်။ စာရိုက်ထည့်တာနဲ့ဖြစ်ဖြစ် ဒါမှမဟုတ် ကူးထည့်တာနဲ့ဖြစ်ဖြစ် မြန်မာဖောင့် တစ်ခုကနေ တစ်ခုကို ပြောင်းပေးပါတယ်။ မြန်မာ ယူနီကုဒ်ကနေ ပြောင်းချင်တယ်ဆို မြန်မာ ယူနီကုဒ်ဘက်မှာ ရိုက်ထည့်၊ ကူးထည့်လိုက်တာနဲ့ ဇော်ဂျီဝမ်းဘက်မှာ ဇော်ဂျီဖောင့်ကိုပြောင်းပြီးသား တိုက်ရိုက်ထွက်လာပါမယ်။ အပြန်အလှန်ပါပဲ၊ ဇော်ဂျီကနေပြောင်းချင်တယ်ဆိုရင် ဇော်ဂျီဝမ်းဘက်မှာ ရိုက်ထည့်၊ ကူးထည့်တာနဲ့ မြန်မာ ယူနီကုဒ်ဖောင့်ကို ပြောင်းပြီးသားက မြန်မာ ယူနီကုဒ်အကွက်ထဲမှာ ပေါ်လာမှာဖြစ်ပါတယ်။</p>
<table border="1">
<tr>
<th>ပစ္စည်း</th>
<th>အရေအတွက်</th>
<th>ဈေးနှုန်း</th>
</tr>
<tr>
<td>Hosting</td>
<td>1</td>
<td>{{.Amount}}</td>
</tr>
<tr>
<td>Domain registration</td>
<td>1</td>
<td>$15</td>
</tr>
</table>`
result := GenPNG(C.CString("build/out.png"), C.CString(payload), C.CString(temp))
goResult := C.GoString(result)
fmt.Println("Result:", goResult)
PrintImg(C.CString("usb:/dev/usb/lp1"), C.CString("build/out.png"))
}

View File

@@ -1,4 +1,4 @@
package main package libgofunc
import ( import (
/* /*
@@ -17,12 +17,33 @@ import (
"github.com/kenshaw/escpos" "github.com/kenshaw/escpos"
) )
import (
"fmt"
"github.com/google/gousb"
)
type USBReadWriter struct {
out *gousb.OutEndpoint
in *gousb.InEndpoint // Optional, can be nil if you only write
}
func (urw *USBReadWriter) Write(p []byte) (n int, err error) {
return urw.out.Write(p)
}
func (urw *USBReadWriter) Read(p []byte) (n int, err error) {
if urw.in == nil {
return 0, fmt.Errorf("read not supported")
}
return urw.in.Read(p)
}
//export PrintImg //export PrintImg
func PrintImg(printer *C.char, imagePath *C.char) *C.char { func PrintImg(printer *C.char, imagePath *C.char) *C.char {
goPrinter := C.GoString(printer) goPrinter := C.GoString(printer)
goImagePath := C.GoString(imagePath) goImagePath := C.GoString(imagePath)
var out *gousb.OutEndpoint
// printer := "tcp:192.168.100.151:9100" // printer := "tcp:192.168.100.151:9100"
// printer := "usb:/dev/usb/lp1" // printer := "usb:/dev/usb/lp1"
var w *bufio.ReadWriter var w *bufio.ReadWriter
@@ -44,6 +65,40 @@ func PrintImg(printer *C.char, imagePath *C.char) *C.char {
} }
defer f.Close() defer f.Close()
w = bufio.NewReadWriter(bufio.NewReader(f), bufio.NewWriter(f)) w = bufio.NewReadWriter(bufio.NewReader(f), bufio.NewWriter(f))
} else if strings.HasPrefix(goPrinter, "int:") {
ctx := gousb.NewContext()
// location := strings.TrimLeft(goPrinter, "int:")
targetBus := 1
targetAddr := 5
devs, err := ctx.OpenDevices(func(desc *gousb.DeviceDesc) bool {
return int(desc.Bus) == targetBus && int(desc.Address) == targetAddr
})
if err != nil || len(devs) == 0 {
log.Fatal("Could not find or open the device")
}
dev := devs[0]
defer dev.Close()
dev.SetAutoDetach(true)
// 2. Claim the default interface (usually 0 for printers)
// Note: This may require detaching the kernel driver on Linux
intf, done, err := dev.DefaultInterface()
if err != nil {
log.Fatalf("Failed to claim interface: %v", err)
}
defer done()
// 3. Open the Bulk Output Endpoint (usually endpoint #1 or #2)
// You may need to inspect desc.Endpoints to find the correct Bulk Out ID
out, err = intf.OutEndpoint(1)
if err != nil {
log.Fatalf("Failed to open OUT endpoint: %v", err)
}
// w = bufio.NewReadWriter(bufio.NewReader(outPort), bufio.NewWriter(f))
rw := &USBReadWriter{out: out}
reader := bufio.NewReader(rw)
writer := bufio.NewWriter(rw)
w = bufio.NewReadWriter(reader, writer)
} }
prt := escpos.New(w) prt := escpos.New(w)
@@ -59,3 +114,9 @@ func PrintImg(printer *C.char, imagePath *C.char) *C.char {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
return NewOk(nil) return NewOk(nil)
} }
func Print(printer, imagePath string) string {
result := PrintImg(C.CString(printer), C.CString(imagePath))
r := C.GoString(result)
return r
}

View File

@@ -1,4 +1,4 @@
package main package libgofunc
import ( import (
"encoding/json" "encoding/json"

2
vo.go
View File

@@ -1,4 +1,4 @@
package main package libgofunc
import ( import (
/* /*