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) }