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

293
img.go
View File

@@ -1,4 +1,4 @@
package main
package libgofunc
import (
/*
@@ -11,7 +11,6 @@ import (
"image"
_ "image/png"
"os"
"strconv"
"strings"
"embed"
@@ -25,18 +24,24 @@ import (
"github.com/kenshaw/escpos"
)
import "log"
const (
canvasWidth = 550
padding = 5
lineGap = 12
defalutFontSize = 18.0
defalutTableBorder = 5
lineGap = 12
elePSize = 18
eleH1Size = 32
eleH2Size = 24
eleH3Size = 19
)
//go:embed static/*
var fontFs embed.FS
//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)
goPayload := C.GoString(payload)
goTmpl := C.GoString(tmpl)
@@ -58,42 +63,29 @@ func GenPNG(outputPath *C.char, payload *C.char, tmpl *C.char) *C.char {
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,
})
face, err := LoadFont(defalutFontSize)
if err != nil {
return NewErr(err)
}
// First pass: compute required height
dummyDC := gg.NewContext(canvasWidth, 150)
dummyDC.SetFontFace(face)
dummyDC.SetFontFace(*face)
bodyNode := findNode(root, "body")
if bodyNode == nil {
bodyNode = root
}
computedHeight := computeHeight(bodyNode, padding, dummyDC, face)
body, height := BuildTree(bodyNode, canvasWidth, dummyDC, face)
// Second pass: actual rendering
dc := gg.NewContext(canvasWidth, computedHeight+50)
dc := gg.NewContext(canvasWidth, height)
dc.SetRGB(1, 1, 1)
dc.Clear()
dc.SetFontFace(face)
dc.SetFontFace(*face)
y := padding
renderNode(dc, bodyNode, &y, face)
y := 0
renderNode(dc, canvasWidth, body, &y, *face)
err = dc.SavePNG(goPath)
if err != nil {
@@ -104,6 +96,12 @@ func GenPNG(outputPath *C.char, payload *C.char, tmpl *C.char) *C.char {
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) {
tmpl := template.Must(template.New("mytemplate").Parse(tmp))
var buf bytes.Buffer
@@ -113,34 +111,48 @@ func renderTemplate(tmp string, data map[string]interface{}) (string, error) {
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)
}
func renderNode(dc *gg.Context, canvasWidth int, n *Node, y *int, face font.Face) {
before := *y
if n.Style.PaddingTop > 0 {
*y += int(n.Style.PaddingTop)
}
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 {
renderNode(dc, c, y, face)
for _, c := range n.Children {
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)
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 {
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 += 5
}
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
}
func drawImage(dc *gg.Context, n *html.Node, y *int) {
src := getAttr(n, "src")
func drawImage(dc *gg.Context, n *Node, y *int) {
src := n.getSrc()
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())
padding := n.Style.PaddingLeft
h := n.Style.Height
if n.Style.Width > 0 {
ix := padding
iy := float64(*y)
scale := n.Style.Width / float64(img.Bounds().Dx())
dc.Push()
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()
*y += int(float64(img.Bounds().Dy()) * scale)
if float64(img.Bounds().Dy())*scale > h {
h = float64(img.Bounds().Dy()) * scale
}
*y += int(h)
} else {
dc.DrawImage(img, padding, *y)
dc.DrawImage(img, int(padding), *y)
*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)
if len(rows) == 0 {
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])
cellWidth := (canvasWidth - padding*2) / colCount
dc.SetFontFace(face)
cellWidth := (canvasWidth - padding) / colCount
border := table.Style.Border
for _, row := range rows {
x := padding
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.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
}
*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 traverse func(*html.Node)
traverse = func(n *html.Node) {
@@ -247,15 +307,6 @@ func getText(n *html.Node) 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 {
if n.Type == html.ElementNode && n.Data == tag {
return n
@@ -268,36 +319,6 @@ func findNode(n *html.Node, tag string) *html.Node {
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 {
@@ -318,3 +339,73 @@ func printImg(prt *escpos.Escpos, imgPath string) error {
_, err = prt.WriteRaw(data)
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
}