193 lines
4.4 KiB
Go
193 lines
4.4 KiB
Go
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 1–8)
|
||
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 (1–16)
|
||
// 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)
|
||
}
|