add printer

This commit is contained in:
2026-03-16 22:42:30 +06:30
parent 5c84b96fcf
commit a7b923798b
11 changed files with 1544 additions and 0 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ go.work.sum
# env file
.env
build

39
build_lib.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
export PATH=$HOME/apps/go1.22.12/bin:$PATH
go version
# need Android NDK
NDK_HOME="$HOME/Android/Sdk/ndk/28.2.13676358" # <--- CHECK YOUR VERSION
API=21
TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
echo "Building for Android x86_64..."
CC="$TOOLCHAIN/x86_64-linux-android$API-clang" \
CGO_ENABLED=1 GOOS=android GOARCH=amd64 \
go build -buildmode=c-shared -o ./build/libgofunc_x64.so .
echo "Building for Android ARM64..."
CC="$TOOLCHAIN/aarch64-linux-android$API-clang" \
CGO_ENABLED=1 GOOS=android GOARCH=arm64 \
go build -buildmode=c-shared -o ./build/libgofunc_arm64.so .
echo "Building for Android ARMv7..."
CC="$TOOLCHAIN/armv7a-linux-androideabi$API-clang" \
CGO_ENABLED=1 GOOS=android GOARCH=arm GOARM=7 \
go build -buildmode=c-shared -o ./build/libgofunc_armv7a.so .
# echo "Building for iOS ARM64..."
# CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \
# go build -buildmode=c-archive -o ./libgofunc_arm64.a .
# cp ./libgofunc_x64.so /home/sainw/ws/forward_pos/android/app/src/main/jniLibs/x86_64/libgofuncso
cp ./build/libgofunc_x64.so $HOME/ws/forward_pos/native/android/x86_64/libgofunc.so
cp ./build/libgofunc_arm64.so $HOME/ws/forward_pos/native/android/arm64-v8a/libgofunc.so
cp ./build/libgofunc_armv7a.so $HOME/ws/forward_pos/native/android/armeabi-v7a/libgofunc.so
echo "Done!"

192
escpos_printer.go Normal file
View File

@@ -0,0 +1,192 @@
package main
import (
"image"
"image/color"
"io"
)
// Printer wraps an io.Writer (USB file, TCP conn, etc.)
type Printer struct {
w io.Writer
}
func NewPrinter(w io.Writer) *Printer {
return &Printer{w: w}
}
func (p *Printer) Write(data []byte) error {
_, err := p.w.Write(data)
return err
}
// Print plain text
func (p *Printer) PrintText(text string) error {
return p.Write([]byte(text))
}
// New line
func (p *Printer) LF(num int) error {
for range num {
err := p.PrintText("\n")
if err != nil {
return err
}
}
return nil
}
// Bold text ON
func (p *Printer) BoldOn() error {
return p.Write([]byte{0x1b, 0x45, 0x01})
}
// Bold text OFF
func (p *Printer) BoldOff() error {
return p.Write([]byte{0x1b, 0x45, 0x00})
}
// Alignment: 0=left, 1=center, 2=right
func (p *Printer) Align(n int) error {
return p.Write([]byte{0x1b, 0x61, byte(n)})
}
// Cut paper
func (p *Printer) Cut() error {
return p.Write([]byte{0x1d, 0x56, 0x00})
}
// SelectFont chooses printer's built-in font (0=A, 1=B, 2=C if supported)
func (p *Printer) SelectFont(n int) error {
return p.Write([]byte{0x1b, 0x4d, byte(n)})
}
// SetFontSize scales text (x, y from 18)
func (p *Printer) SetFontSize(x, y int) error {
n := byte(((y - 1) << 4) | (x - 1))
return p.Write([]byte{0x1d, 0x21, n})
}
// Underline: 0=off, 1=thin, 2=thick
func (p *Printer) Underline(n int) error {
return p.Write([]byte{0x1b, 0x2d, byte(n)})
}
// UpsideDown: 0=off, 1=on
func (p *Printer) UpsideDown(n int) error {
return p.Write([]byte{0x1b, 0x7b, byte(n)})
}
// SetCharSpacing sets extra spacing between characters
func (p *Printer) SetCharSpacing(n int) error {
return p.Write([]byte{0x1b, 0x20, byte(n)})
}
// ---------------- IMAGE PRINTING ----------------
// convert to monochrome
func toMonochrome(img image.Image) *image.Gray {
bounds := img.Bounds()
gray := image.NewGray(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, _ := img.At(x, y).RGBA()
grayValue := uint8((r + g + b) / 3 >> 8)
if grayValue > 128 {
gray.Set(x, y, color.White)
} else {
gray.Set(x, y, color.Black)
}
}
}
return gray
}
// ESC/POS: GS v 0
func escposRaster(img *image.Gray) []byte {
bounds := img.Bounds()
w := bounds.Dx()
h := bounds.Dy()
// Width must be in bytes (8 pixels per byte)
wBytes := (w + 7) / 8
data := []byte{}
// ESC/POS raster format: GS v 0 m xL xH yL yH [data]
// GS v 0
data = append(data, 0x1d, 0x76, 0x30, 0x00)
// xL, xH
data = append(data, byte(wBytes%256), byte(wBytes/256))
// yL, yH
data = append(data, byte(h%256), byte(h/256))
// Image data
for y := 0; y < h; y++ {
for xByte := 0; xByte < wBytes; xByte++ {
var b byte
for bit := 0; bit < 8; bit++ {
x := xByte*8 + bit
if x < w {
grayColor := img.GrayAt(x, y).Y
if grayColor < 128 {
// Black pixel = 1
b |= (1 << (7 - bit))
}
}
}
data = append(data, b)
}
}
return data
}
// PrintImage sends an image using GS v 0 (raster bit image)
func (p *Printer) PrintImage(img image.Image) error {
gray := toMonochrome(img)
data := escposRaster(gray)
return p.Write(data)
}
// PrintBarcode prints a CODE128 barcode (most supported)
func (p *Printer) PrintBarcode(data string) error {
// Select barcode height
p.Write([]byte{0x1d, 0x68, 100}) // height in dots
// Select barcode width
p.Write([]byte{0x1d, 0x77, 2}) // module width (2 = medium)
// HRI (human readable interpretation) below barcode
p.Write([]byte{0x1d, 0x48, 2})
// GS k m d1...dk NUL
cmd := []byte{0x1d, 0x6b, 73, byte(len(data))} // 73 = CODE128 auto
cmd = append(cmd, []byte(data)...)
return p.Write(cmd)
}
// ---------------- QR CODE ----------------
// PrintQRCode prints a QR code
func (p *Printer) PrintQRCode(data string, size int, ecLevel byte) error {
// size: module size (116)
// ecLevel: error correction (48=L, 49=M, 50=Q, 51=H)
// [1] Store QR data
storeLen := len(data) + 3
cmdStore := []byte{0x1d, 0x28, 0x6b, byte(storeLen % 256), byte(storeLen / 256), 49, 80, 48}
cmdStore = append(cmdStore, []byte(data)...)
// [2] Set module size
cmdSize := []byte{0x1d, 0x28, 0x6b, 3, 0, 49, 67, byte(size)}
// [3] Set error correction level
cmdErr := []byte{0x1d, 0x28, 0x6b, 3, 0, 49, 69, ecLevel}
// [4] Print
cmdPrint := []byte{0x1d, 0x28, 0x6b, 3, 0, 49, 81, 48}
p.Write(cmdSize)
p.Write(cmdErr)
p.Write(cmdStore)
return p.Write(cmdPrint)
}

16
go.mod Normal file
View File

@@ -0,0 +1,16 @@
module libgofunc
go 1.22.0
require (
github.com/dlclark/regexp2 v1.11.5
github.com/fogleman/gg v1.3.0
github.com/kenshaw/escpos v0.0.0-20221114190919-df06b682a8fc
golang.org/x/image v0.24.0
golang.org/x/net v0.34.0
)
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
golang.org/x/text v0.22.0 // indirect
)

14
go.sum Normal file
View File

@@ -0,0 +1,14 @@
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
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/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
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/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=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
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/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=

317
img.go Normal file
View 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
}

43
main.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"C"
"fmt"
_ "image/png"
)
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"))
}

58
printer.go Normal file
View File

@@ -0,0 +1,58 @@
package main
import (
"C"
_ "image/png"
"os"
"strings"
"bufio"
"log"
"net"
"time"
"github.com/kenshaw/escpos"
)
//export PrintImg
func PrintImg(printer *C.char, imagePath *C.char) *C.char {
goPrinter := C.GoString(printer)
goImagePath := C.GoString(imagePath)
// printer := "tcp:192.168.100.151:9100"
// printer := "usb:/dev/usb/lp1"
var w *bufio.ReadWriter
if strings.HasPrefix(goPrinter, "tcp:") {
location := strings.TrimLeft(goPrinter, "tcp:")
conn, err := net.Dial("tcp", location)
if err != nil {
log.Println("error dialing:", err.Error())
return NewErr(err)
}
defer conn.Close()
w = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
} else if strings.HasPrefix(goPrinter, "usb:") {
location := strings.TrimLeft(goPrinter, "usb:")
f, err := os.OpenFile(location, os.O_RDWR, os.ModeDevice)
if err != nil {
log.Println("error OpenFile usb:", err.Error())
return NewErr(err)
}
defer f.Close()
w = bufio.NewReadWriter(bufio.NewReader(f), bufio.NewWriter(f))
}
prt := escpos.New(w)
prt.Init()
prt.SetSmooth(1)
err := printImg(prt, goImagePath)
if err != nil {
return NewErr(err)
}
prt.Cut()
prt.End()
w.Flush()
time.Sleep(100 * time.Millisecond)
return NewOk(nil)
}

833
rabbit.go Normal file
View File

@@ -0,0 +1,833 @@
package main
import (
"encoding/json"
"github.com/dlclark/regexp2"
)
type RuleStruct struct {
From string
To string
}
func uni2zg(str string) string {
unizgRule := `[
{
"from": "\u1004\u103a\u1039",
"to": "\u1064"
},
{
"from": "\u1039\u1010\u103d",
"to": "\u1096"
},
{
"from": "\u102b\u103a",
"to": "\u105a"
},
{
"from": "\u102d\u1036",
"to": "\u108e"
},
{
"from": "\u104e\u1004\u103a\u1038",
"to": "\u104e"
},
{
"from": "[\u1025\u1009](?=\u1039)",
"to": "\u106a"
},
{
"from": "\u1009(?=[\u102f\u1030])",
"to": "\u1025"
},
{
"from": "[\u1025\u1009](?=[\u1037]?[\u103a])",
"to": "\u1025"
},
{
"from": "\u100a(?=[\u1039\u103d])",
"to": "\u106b"
},
{
"from": "(\u1039[\u1000-\u1021])(\u102D){0,1}\u102f",
"to": "$1$2\u1033"
},
{
"from": "(\u1039[\u1000-\u1021])\u1030",
"to": "$1\u1034"
},
{
"from": "\u1014(?=[\u102d\u102e\u102f\u103A]?[\u1030\u103d\u103e\u102f\u1039])",
"to": "\u108f"
},
{
"from": "\u1014(?=\u103A\u102F )",
"to": "\u108f"
},
{
"from" : "\u1014\u103c",
"to" : "\u108f\u103c"
},
{
"from": "\u1039\u1000",
"to": "\u1060"
},
{
"from": "\u1039\u1001",
"to": "\u1061"
},
{
"from": "\u1039\u1002",
"to": "\u1062"
},
{
"from": "\u1039\u1003",
"to": "\u1063"
},
{
"from": "\u1039\u1005",
"to": "\u1065"
},
{
"from": "\u1039\u1006",
"to": "\u1066"
},
{
"from": "\u1039\u1007",
"to": "\u1068"
},
{
"from": "\u1039\u1008",
"to": "\u1069"
},
{
"from": "\u1039\u100b",
"to": "\u106c"
},
{
"from": "\u100b\u1039\u100c",
"to": "\u1092"
},
{
"from": "\u1039\u100c",
"to": "\u106d"
},
{
"from": "\u100d\u1039\u100d",
"to": "\u106e"
},
{
"from": "\u100d\u1039\u100e",
"to": "\u106f"
},
{
"from": "\u1039\u100f",
"to": "\u1070"
},
{
"from": "\u1039\u1010",
"to": "\u1071"
},
{
"from": "\u1039\u1011",
"to": "\u1073"
},
{
"from": "\u1039\u1012",
"to": "\u1075"
},
{
"from": "\u1039\u1013",
"to": "\u1076"
},
{
"from": "\u1039[\u1014\u108f]",
"to": "\u1077"
},
{
"from": "\u1039\u1015",
"to": "\u1078"
},
{
"from": "\u1039\u1016",
"to": "\u1079"
},
{
"from": "\u1039\u1017",
"to": "\u107a"
},
{
"from": "\u1039\u1018",
"to": "\u107b"
},
{
"from": "\u1039\u1019",
"to": "\u107c"
},
{
"from": "\u1039\u101c",
"to": "\u1085"
},
{
"from": "\u103f",
"to": "\u1086"
},
{
"from": "\u103d\u103e",
"to": "\u108a"
},
{
"from": "(\u1064)([\u1000-\u1021])([\u103b\u103c]?)\u102d",
"to": "$2$3\u108b"
},
{
"from": "(\u1064)([\u1000-\u1021])([\u103b\u103c]?)\u102e",
"to": "$2$3\u108c"
},
{
"from": "(\u1064)([\u1000-\u1021])([\u103b\u103c]?)\u1036",
"to": "$2$3\u108d"
},
{
"from": "(\u1064)([\u1000-\u1021\u1040-\u1049])([\u103b\u103c]?)([\u1031]?)",
"to": "$2$3$4$1"
},
{
"from": "\u101b(?=([\u102d\u102e]?)[\u102f\u1030\u103d\u108a])",
"to": "\u1090"
},
{
"from": "\u100f\u1039\u100d",
"to": "\u1091"
},
{
"from": "\u100b\u1039\u100b",
"to": "\u1097"
},
{
"from": "([\u1000-\u1021\u108f\u1029\u106e\u106f\u1086\u1090\u1091\u1092\u1097])([\u1060-\u1069\u106c\u106d\u1070-\u107c\u1085\u108a])?([\u103b-\u103e]*)?\u1031",
"to": "\u1031$1$2$3"
},
{
"from": "\u103c\u103e",
"to": "\u103c\u1087"
},
{
"from": "([\u1000-\u1021\u108f\u1029])([\u1060-\u1069\u106c\u106d\u1070-\u107c\u1085])?(\u103c)",
"to": "$3$1$2"
},
{
"from": "\u103a",
"to": "\u1039"
},
{
"from": "\u103b",
"to": "\u103a"
},
{
"from": "\u103c",
"to": "\u103b"
},
{
"from": "\u103d",
"to": "\u103c"
},
{
"from": "\u103e",
"to": "\u103d"
},
{
"from": "([^\u103a\u100a])\u103d([\u102d\u102e]?)\u102f",
"to": "$1\u1088$2"
},
{
"from": "([\u101b\u103a\u103c\u108a\u1088\u1090])([\u1030\u103d])?([\u1032\u1036\u1039\u102d\u102e\u108b\u108c\u108d\u108e]?)(\u102f)?\u1037",
"to": "$1$2$3$4\u1095"
},
{
"from": "([\u102f\u1014\u1030\u103d])([\u1032\u1036\u1039\u102d\u102e\u108b\u108c\u108d\u108e]?)\u1037",
"to": "$1$2\u1094"
},
{
"from": "([\u103b])([\u1000-\u1021])([\u1087]?)([\u1036\u102d\u102e\u108b\u108c\u108d\u108e]?)\u102f",
"to": "$1$2$3$4\u1033"
},
{
"from": "([\u103b])([\u1000-\u1021])([\u1087]?)([\u1036\u102d\u102e\u108b\u108c\u108d\u108e]?)\u1030",
"to": "$1$2$3$4\u1034"
},
{
"from": "([\u103a\u103c\u100a\u1008\u100b\u100c\u100d\u1020\u1025])([\u103d]?)([\u1036\u102d\u102e\u108b\u108c\u108d\u108e]?)\u102f",
"to": "$1$2$3\u1033"
},
{
"from": "([\u103a\u103c\u100a\u1008\u100b\u100c\u100d\u1020\u1025])(\u103d?)([\u1036\u102d\u102e\u108b\u108c\u108d\u108e]?)\u1030",
"to": "$1$2$3\u1034"
},
{
"from": "([\u100a\u1020\u1009])\u103d",
"to": "$1\u1087"
},
{
"from": "\u103d\u1030",
"to": "\u1089"
},
{
"from": "\u103b([\u1000\u1003\u1006\u100f\u1010\u1011\u1018\u101a\u101c\u101a\u101e\u101f])",
"to": "\u107e$1"
},
{
"from": "\u107e([\u1000\u1003\u1006\u100f\u1010\u1011\u1018\u101a\u101c\u101a\u101e\u101f])([\u103c\u108a])([\u1032\u1036\u102d\u102e\u108b\u108c\u108d\u108e])",
"to": "\u1084$1$2$3"
},
{
"from": "\u107e([\u1000\u1003\u1006\u100f\u1010\u1011\u1018\u101a\u101c\u101a\u101e\u101f])([\u103c\u108a])",
"to": "\u1082$1$2"
},
{
"from": "\u107e([\u1000\u1003\u1006\u100f\u1010\u1011\u1018\u101a\u101c\u101a\u101e\u101f])([\u1033\u1034]?)([\u1032\u1036\u102d\u102e\u108b\u108c\u108d\u108e])",
"to": "\u1080$1$2$3"
},
{
"from": "\u103b([\u1000-\u1021])([\u103c\u108a])([\u1032\u1036\u102d\u102e\u108b\u108c\u108d\u108e])",
"to": "\u1083$1$2$3"
},
{
"from": "\u103b([\u1000-\u1021])([\u103c\u108a])",
"to": "\u1081$1$2"
},
{
"from": "\u103b([\u1000-\u1021])([\u1033\u1034]?)([\u1032\u1036\u102d\u102e\u108b\u108c\u108d\u108e])",
"to": "\u107f$1$2$3"
},
{
"from": "\u103a\u103d",
"to": "\u103d\u103a"
},
{
"from": "\u103a([\u103c\u108a])",
"to": "$1\u107d"
},
{
"from": "([\u1033\u1034])(\u1036?)\u1094",
"to": "$1$2\u1095"
},
{
"from": "\u108F\u1071",
"to" : "\u108F\u1072"
},
{
"from": "([\u1000-\u1021])([\u107B\u1066])\u102C",
"to": "$1\u102C$2"
},
{
"from": "\u102C([\u107B\u1066])\u1037",
"to": "\u102C$1\u1094"
},
{
"from": "\u1047((?=[\u1000-\u1021]\u1039)|(?=[\u102c-\u1030\u1032\u1036-\u1038\u103c\u103d]))",
"to": "\u101b"
}
]
`
return replaceRule(unizgRule, str)
}
func Zg2uni(str string) string {
zguniRule := `[
{
"from" : "([\u102D\u102E\u103D\u102F\u1037\u1095])\\1+",
"to" : "$1"
},
{
"from": "\u200B",
"to": ""
},
{
"from" : "\u103d\u103c",
"to" : "\u108a"
},
{
"from": "(\u103d|\u1087)",
"to": "\u103e"
},
{
"from": "\u103c",
"to": "\u103d"
},
{
"from": "(\u103b|\u107e|\u107f|\u1080|\u1081|\u1082|\u1083|\u1084)",
"to": "\u103c"
},
{
"from": "(\u103a|\u107d)",
"to": "\u103b"
},
{
"from": "\u1039",
"to": "\u103a"
},
{
"from": "(\u1066|\u1067)",
"to": "\u1039\u1006"
},
{
"from": "\u106a",
"to": "\u1009"
},
{
"from": "\u106b",
"to": "\u100a"
},
{
"from": "\u106c",
"to": "\u1039\u100b"
},
{
"from": "\u106d",
"to": "\u1039\u100c"
},
{
"from": "\u106e",
"to": "\u100d\u1039\u100d"
},
{
"from": "\u106f",
"to": "\u100d\u1039\u100e"
},
{
"from": "\u1070",
"to": "\u1039\u100f"
},
{
"from": "(\u1071|\u1072)",
"to": "\u1039\u1010"
},
{
"from": "\u1060",
"to": "\u1039\u1000"
},
{
"from": "\u1061",
"to": "\u1039\u1001"
},
{
"from": "\u1062",
"to": "\u1039\u1002"
},
{
"from": "\u1063",
"to": "\u1039\u1003"
},
{
"from": "\u1065",
"to": "\u1039\u1005"
},
{
"from": "\u1068",
"to": "\u1039\u1007"
},
{
"from": "\u1069",
"to": "\u1039\u1008"
},
{
"from": "(\u1073|\u1074)",
"to": "\u1039\u1011"
},
{
"from": "\u1075",
"to": "\u1039\u1012"
},
{
"from": "\u1076",
"to": "\u1039\u1013"
},
{
"from": "\u1077",
"to": "\u1039\u1014"
},
{
"from": "\u1078",
"to": "\u1039\u1015"
},
{
"from": "\u1079",
"to": "\u1039\u1016"
},
{
"from": "\u107a",
"to": "\u1039\u1017"
},
{
"from": "\u107c",
"to": "\u1039\u1019"
},
{
"from": "\u1085",
"to": "\u1039\u101c"
},
{
"from": "\u1033",
"to": "\u102f"
},
{
"from": "\u1034",
"to": "\u1030"
},
{
"from": "\u103f",
"to": "\u1030"
},
{
"from": "\u1086",
"to": "\u103f"
},
{
"from": "\u1036\u1088",
"to": "\u1088\u1036"
},
{
"from": "\u1088",
"to": "\u103e\u102f"
},
{
"from": "\u1089",
"to": "\u103e\u1030"
},
{
"from": "\u108a",
"to": "\u103d\u103e"
},
{
"from": "\u103B\u1064",
"to": "\u1064\u103B"
},
{
"from": "\u103c([\u1000-\u1021])(\u1064|\u108b)",
"to": "$1\u103c$2"
},
{
"from": "(\u1031)?([\u1000-\u1021\u1040-\u1049])(\u103c)?\u1064",
"to": "\u1004\u103a\u1039$1$2$3"
},
{
"from": "(\u1031)?([\u1000-\u1021])(\u103b|\u103c)?\u108b",
"to": "\u1004\u103a\u1039$1$2$3\u102d"
},
{
"from": "(\u1031)?([\u1000-\u1021])(\u103b)?\u108c",
"to": "\u1004\u103a\u1039$1$2$3\u102e"
},
{
"from": "(\u1031)?([\u1000-\u1021])(\u103b)?\u108d",
"to": "\u1004\u103a\u1039$1$2$3\u1036"
},
{
"from": "\u108e",
"to": "\u102d\u1036"
},
{
"from": "\u108f",
"to": "\u1014"
},
{
"from": "\u1090",
"to": "\u101b"
},
{
"from": "\u1091",
"to": "\u100f\u1039\u100d"
},
{
"from": "\u1092",
"to": "\u100b\u1039\u100c"
},
{
"from": "\u1019\u102c(\u107b|\u1093)",
"to": "\u1019\u1039\u1018\u102c"
},
{
"from": "(\u107b|\u1093)",
"to": "\u1039\u1018"
},
{
"from": "(\u1094|\u1095)",
"to": "\u1037"
},
{
"from": "([\u1000-\u1021])\u1037\u1032",
"to": "$1\u1032\u1037"
},
{
"from": "\u1096",
"to": "\u1039\u1010\u103d"
},
{
"from": "\u1097",
"to": "\u100b\u1039\u100b"
},
{
"from": "\u103c([\u1000-\u1021])([\u1000-\u1021])?",
"to": "$1\u103c$2"
},
{
"from": "([\u1000-\u1021])\u103c\u103a",
"to": "\u103c$1\u103a"
},
{
"from": "\u1047(?=[\u102c-\u1030\u1032\u1036-\u1038\u103d\u1038])",
"to": "\u101b"
},
{
"from": "\u1031\u1047",
"to": "\u1031\u101b"
},
{
"from": "\u1040(\u102e|\u102f|\u102d\u102f|\u1030|\u1036|\u103d|\u103e)",
"to": "\u101d$1"
},
{
"from": "([^\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049])\u1040\u102b",
"to": "$1\u101d\u102b"
},
{
"from": "([\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049])\u1040\u102b(?!\u1038)",
"to": "$1\u101d\u102b"
},
{
"from": "^\u1040(?=\u102b)",
"to": "\u101d"
},
{
"from": "\u1040\u102d(?!\u0020?/)",
"to": "\u101d\u102d"
},
{
"from": "([^\u1040-\u1049])\u1040([^\u1040-\u1049\u0020]|[\u104a\u104b])",
"to": "$1\u101d$2"
},
{
"from": "([^\u1040-\u1049])\u1040(?=[\\f\\n\\r])",
"to": "$1\u101d"
},
{
"from": "([^\u1040-\u1049])\u1040$",
"to": "$1\u101d"
},
{
"from": "\u1031([\u1000-\u1021\u103f])(\u103e)?(\u103b)?",
"to": "$1$2$3\u1031"
},
{
"from": "([\u1000-\u1021])\u1031([\u103b\u103c\u103d\u103e]+)",
"to": "$1$2\u1031"
},
{
"from": "\u1032\u103d",
"to": "\u103d\u1032"
},
{
"from": "([\u102d\u102e])\u103b",
"to": "\u103b$1"
},
{
"from": "\u103d\u103b",
"to": "\u103b\u103d"
},
{
"from": "\u103a\u1037",
"to": "\u1037\u103a"
},
{
"from": "\u102f(\u102d|\u102e|\u1036|\u1037)\u102f",
"to": "\u102f$1"
},
{
"from": "(\u102f|\u1030)(\u102d|\u102e)",
"to": "$2$1"
},
{
"from": "(\u103e)(\u103b|\u103c)",
"to": "$2$1"
},
{
"from": "\u1025(?=[\u1037]?[\u103a\u102c])",
"to": "\u1009"
},
{
"from": "\u1025\u102e",
"to": "\u1026"
},
{
"from": "\u1005\u103b",
"to": "\u1008"
},
{
"from": "\u1036(\u102f|\u1030)",
"to": "$1\u1036"
},
{
"from": "\u1031\u1037\u103e",
"to": "\u103e\u1031\u1037"
},
{
"from": "\u1031\u103e\u102c",
"to": "\u103e\u1031\u102c"
},
{
"from": "\u105a",
"to": "\u102b\u103a"
},
{
"from": "\u1031\u103b\u103e",
"to": "\u103b\u103e\u1031"
},
{
"from": "(\u102d|\u102e)(\u103d|\u103e)",
"to": "$2$1"
},
{
"from": "\u102c\u1039([\u1000-\u1021])",
"to": "\u1039$1\u102c"
},
{
"from": "\u1039\u103c\u103a\u1039([\u1000-\u1021])",
"to": "\u103a\u1039$1\u103c"
},
{
"from": "\u103c\u1039([\u1000-\u1021])",
"to": "\u1039$1\u103c"
},
{
"from": "\u1036\u1039([\u1000-\u1021])",
"to": "\u1039$1\u1036"
},
{
"from": "\u104e",
"to": "\u104e\u1004\u103a\u1038"
},
{
"from": "\u1040(\u102b|\u102c|\u1036)",
"to": "\u101d$1"
},
{
"from": "\u1025\u1039",
"to": "\u1009\u1039"
},
{
"from": "([\u1000-\u1021])\u103c\u1031\u103d",
"to": "$1\u103c\u103d\u1031"
},
{
"from": "([\u1000-\u1021])\u103b\u1031\u103d(\u103e)?",
"to": "$1\u103b\u103d$2\u1031"
},
{
"from": "([\u1000-\u1021])\u103d\u1031\u103b",
"to": "$1\u103b\u103d\u1031"
},
{
"from": "([\u1000-\u1021])\u1031(\u1039[\u1000-\u1021])",
"to": "$1$2\u1031"
},
{
"from": "\u1038\u103a",
"to": "\u103a\u1038"
},
{
"from": "\u102d\u103a|\u103a\u102d",
"to": "\u102d"
},
{
"from": "\u102d\u102f\u103a",
"to": "\u102d\u102f"
},
{
"from": "\u0020\u1037",
"to": "\u1037"
},
{
"from": "\u1037\u1036",
"to": "\u1036\u1037"
},
{
"from": "[\u102d]+",
"to": "\u102d"
},
{
"from": "[\u103a]+",
"to": "\u103a"
},
{
"from": "[\u103d]+",
"to": "\u103d"
},
{
"from": "[\u1037]+",
"to": "\u1037"
},
{
"from": "[\u102e]+",
"to": "\u102e"
},
{
"from": "\u102d\u102e|\u102e\u102d",
"to": "\u102e"
},
{
"from": "\u102f\u102d",
"to": "\u102d\u102f"
},
{
"from": "\u1037\u1037",
"to": "\u1037"
},
{
"from": "\u1032\u1032",
"to": "\u1032"
},
{
"from": "\u1044\u1004\u103a\u1038",
"to": "\u104E\u1004\u103a\u1038"
},
{
"from": "([\u102d\u102e])\u1039([\u1000-\u1021])",
"to": "\u1039$2$1"
},
{
"from": "(\u103c\u1031)\u1039([\u1000-\u1021])",
"to": "\u1039$2$1"
},
{
"from": "\u1036\u103d",
"to": "\u103d\u1036"
},
{
"from": "\u1047((?=[\u1000-\u1021]\u103a)|(?=[\u102c-\u1030\u1032\u1036-\u1038\u103d\u103e]))",
"to": "\u101b"
}
]`
return replaceRule(zguniRule, str)
}
func replaceRule(ruleJson string, output string) string {
var rules []RuleStruct
json.Unmarshal([]byte(ruleJson), &rules)
text := output
for _, rule := range rules {
re := regexp2.MustCompile(rule.From, 1)
res, err := re.Replace(text, rule.To, 0, -1)
text = res
if err != nil {
//nothing to do
continue
}
}
return text
}

BIN
static/Zawgyi-One.ttf Normal file

Binary file not shown.

31
vo.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"C"
"encoding/json"
"log"
)
type Reply struct {
Status int `json:"status"`
Err string `json:"err"`
Result interface{} `json:"result"`
}
func NewErr(err error) *C.char {
e := Reply{Status: 1, Err: err.Error()}
b, err := json.Marshal(e)
if err != nil {
log.Println("Error json.Marshal:", err.Error())
}
return C.CString(string(b))
}
func NewOk(data interface{}) *C.char {
e := Reply{Status: 0, Result: data}
b, err := json.Marshal(e)
if err != nil {
log.Println("Error json.Marshal:", err.Error())
}
return C.CString(string(b))
}