595 lines
11 KiB
Go
595 lines
11 KiB
Go
package printer
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// DLE ASCII (DataLinkEscape)
|
|
DLE byte = 0x10
|
|
|
|
// EOT ASCII (EndOfTransmission)
|
|
EOT byte = 0x04
|
|
|
|
// GS ASCII (Group Separator)
|
|
GS byte = 0x1D
|
|
)
|
|
|
|
// text replacement map
|
|
var textReplaceMap = map[string]string{
|
|
// horizontal tab
|
|
"	": "\x09",
|
|
"	": "\x09",
|
|
|
|
// linefeed
|
|
" ": "\n",
|
|
"
": "\n",
|
|
|
|
// xml stuff
|
|
"'": "'",
|
|
""": `"`,
|
|
">": ">",
|
|
"<": "<",
|
|
|
|
// ampersand must be last to avoid double decoding
|
|
"&": "&",
|
|
}
|
|
|
|
// replace text from the above map
|
|
func textReplace(data string) string {
|
|
for k, v := range textReplaceMap {
|
|
data = strings.Replace(data, k, v, -1)
|
|
}
|
|
return data
|
|
}
|
|
|
|
type Escpos struct {
|
|
// destination
|
|
dst io.ReadWriter
|
|
|
|
// font metrics
|
|
width, height uint8
|
|
|
|
// state toggles ESC[char]
|
|
underline uint8
|
|
emphasize uint8
|
|
upsidedown uint8
|
|
rotate uint8
|
|
|
|
// state toggles GS[char]
|
|
reverse, smooth uint8
|
|
}
|
|
|
|
// reset toggles
|
|
func (e *Escpos) reset() {
|
|
e.width = 1
|
|
e.height = 1
|
|
|
|
e.underline = 0
|
|
e.emphasize = 0
|
|
e.upsidedown = 0
|
|
e.rotate = 0
|
|
|
|
e.reverse = 0
|
|
e.smooth = 0
|
|
}
|
|
|
|
// create Escpos printer
|
|
func New(dst io.ReadWriter) (e *Escpos) {
|
|
e = &Escpos{dst: dst}
|
|
e.reset()
|
|
return
|
|
}
|
|
|
|
// write raw bytes to printer
|
|
func (e *Escpos) WriteRaw(data []byte) (n int, err error) {
|
|
if len(data) > 0 {
|
|
log.Printf("Writing %d bytes\n", len(data))
|
|
e.dst.Write(data)
|
|
} else {
|
|
log.Printf("Wrote NO bytes\n")
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// read raw bytes from printer
|
|
func (e *Escpos) ReadRaw(data []byte) (n int, err error) {
|
|
return e.dst.Read(data)
|
|
}
|
|
|
|
// write a string to the printer
|
|
func (e *Escpos) Write(data string) (int, error) {
|
|
return e.WriteRaw([]byte(data))
|
|
}
|
|
|
|
// init/reset printer settings
|
|
func (e *Escpos) Init() {
|
|
e.reset()
|
|
e.Write("\x1B@")
|
|
}
|
|
|
|
// end output
|
|
func (e *Escpos) End() {
|
|
e.Write("\xFA")
|
|
}
|
|
|
|
// send cut
|
|
func (e *Escpos) Cut() {
|
|
e.Write("\x1DVA0")
|
|
}
|
|
|
|
// send cut minus one point (partial cut)
|
|
func (e *Escpos) CutPartial() {
|
|
e.WriteRaw([]byte{GS, 0x56, 1})
|
|
}
|
|
|
|
// send cash
|
|
func (e *Escpos) Cash() {
|
|
e.Write("\x1B\x70\x00\x0A\xFF")
|
|
}
|
|
|
|
// send linefeed
|
|
func (e *Escpos) Linefeed() {
|
|
e.Write("\n")
|
|
}
|
|
|
|
// send N formfeeds
|
|
func (e *Escpos) FormfeedN(n int) {
|
|
e.Write(fmt.Sprintf("\x1Bd%c", n))
|
|
}
|
|
|
|
// send formfeed
|
|
func (e *Escpos) Formfeed() {
|
|
e.FormfeedN(1)
|
|
}
|
|
|
|
// set font
|
|
func (e *Escpos) SetFont(font string) {
|
|
f := 0
|
|
|
|
switch font {
|
|
case "A":
|
|
f = 0
|
|
case "B":
|
|
f = 1
|
|
case "C":
|
|
f = 2
|
|
default:
|
|
log.Fatalf("Invalid font: '%s', defaulting to 'A'", font)
|
|
f = 0
|
|
}
|
|
|
|
e.Write(fmt.Sprintf("\x1BM%c", f))
|
|
}
|
|
|
|
func (e *Escpos) SendFontSize() {
|
|
e.Write(fmt.Sprintf("\x1D!%c", ((e.width-1)<<4)|(e.height-1)))
|
|
}
|
|
|
|
// set font size
|
|
func (e *Escpos) SetFontSize(width, height uint8) {
|
|
if width > 0 && height > 0 && width <= 8 && height <= 8 {
|
|
e.width = width
|
|
e.height = height
|
|
e.SendFontSize()
|
|
} else {
|
|
log.Fatalf("Invalid font size passed: %d x %d", width, height)
|
|
}
|
|
}
|
|
|
|
// send underline
|
|
func (e *Escpos) SendUnderline() {
|
|
e.Write(fmt.Sprintf("\x1B-%c", e.underline))
|
|
}
|
|
|
|
// send emphasize / doublestrike
|
|
func (e *Escpos) SendEmphasize() {
|
|
e.Write(fmt.Sprintf("\x1BG%c", e.emphasize))
|
|
}
|
|
|
|
// send upsidedown
|
|
func (e *Escpos) SendUpsidedown() {
|
|
e.Write(fmt.Sprintf("\x1B{%c", e.upsidedown))
|
|
}
|
|
|
|
// send rotate
|
|
func (e *Escpos) SendRotate() {
|
|
e.Write(fmt.Sprintf("\x1BR%c", e.rotate))
|
|
}
|
|
|
|
// send reverse
|
|
func (e *Escpos) SendReverse() {
|
|
e.Write(fmt.Sprintf("\x1DB%c", e.reverse))
|
|
}
|
|
|
|
// send smooth
|
|
func (e *Escpos) SendSmooth() {
|
|
e.Write(fmt.Sprintf("\x1Db%c", e.smooth))
|
|
}
|
|
|
|
// send move x
|
|
func (e *Escpos) SendMoveX(x uint16) {
|
|
e.Write(string([]byte{0x1b, 0x24, byte(x % 256), byte(x / 256)}))
|
|
}
|
|
|
|
// send move y
|
|
func (e *Escpos) SendMoveY(y uint16) {
|
|
e.Write(string([]byte{0x1d, 0x24, byte(y % 256), byte(y / 256)}))
|
|
}
|
|
|
|
// set underline
|
|
func (e *Escpos) SetUnderline(v uint8) {
|
|
e.underline = v
|
|
e.SendUnderline()
|
|
}
|
|
|
|
// set emphasize
|
|
func (e *Escpos) SetEmphasize(u uint8) {
|
|
e.emphasize = u
|
|
e.SendEmphasize()
|
|
}
|
|
|
|
// set upsidedown
|
|
func (e *Escpos) SetUpsidedown(v uint8) {
|
|
e.upsidedown = v
|
|
e.SendUpsidedown()
|
|
}
|
|
|
|
// set rotate
|
|
func (e *Escpos) SetRotate(v uint8) {
|
|
e.rotate = v
|
|
e.SendRotate()
|
|
}
|
|
|
|
// set reverse
|
|
func (e *Escpos) SetReverse(v uint8) {
|
|
e.reverse = v
|
|
e.SendReverse()
|
|
}
|
|
|
|
// set smooth
|
|
func (e *Escpos) SetSmooth(v uint8) {
|
|
e.smooth = v
|
|
e.SendSmooth()
|
|
}
|
|
|
|
// pulse (open the drawer)
|
|
func (e *Escpos) Pulse() {
|
|
// with t=2 -- meaning 2*2msec
|
|
e.Write("\x1Bp\x02")
|
|
}
|
|
|
|
// set alignment
|
|
func (e *Escpos) SetAlign(align string) {
|
|
a := 0
|
|
switch align {
|
|
case "left":
|
|
a = 0
|
|
case "center":
|
|
a = 1
|
|
case "right":
|
|
a = 2
|
|
default:
|
|
log.Fatalf("Invalid alignment: %s", align)
|
|
}
|
|
e.Write(fmt.Sprintf("\x1Ba%c", a))
|
|
}
|
|
|
|
// set language -- ESC R
|
|
func (e *Escpos) SetLang(lang string) {
|
|
l := 0
|
|
|
|
switch lang {
|
|
case "en":
|
|
l = 0
|
|
case "fr":
|
|
l = 1
|
|
case "de":
|
|
l = 2
|
|
case "uk":
|
|
l = 3
|
|
case "da":
|
|
l = 4
|
|
case "sv":
|
|
l = 5
|
|
case "it":
|
|
l = 6
|
|
case "es":
|
|
l = 7
|
|
case "ja":
|
|
l = 8
|
|
case "no":
|
|
l = 9
|
|
default:
|
|
log.Fatalf("Invalid language: %s", lang)
|
|
}
|
|
e.Write(fmt.Sprintf("\x1BR%c", l))
|
|
}
|
|
|
|
// do a block of text
|
|
func (e *Escpos) Text(params map[string]string, data string) {
|
|
|
|
// send alignment to printer
|
|
if align, ok := params["align"]; ok {
|
|
e.SetAlign(align)
|
|
}
|
|
|
|
// set lang
|
|
if lang, ok := params["lang"]; ok {
|
|
e.SetLang(lang)
|
|
}
|
|
|
|
// set smooth
|
|
if smooth, ok := params["smooth"]; ok && (smooth == "true" || smooth == "1") {
|
|
e.SetSmooth(1)
|
|
}
|
|
|
|
// set emphasize
|
|
if em, ok := params["em"]; ok && (em == "true" || em == "1") {
|
|
e.SetEmphasize(1)
|
|
}
|
|
|
|
// set underline
|
|
if ul, ok := params["ul"]; ok && (ul == "true" || ul == "1") {
|
|
e.SetUnderline(1)
|
|
}
|
|
|
|
// set reverse
|
|
if reverse, ok := params["reverse"]; ok && (reverse == "true" || reverse == "1") {
|
|
e.SetReverse(1)
|
|
}
|
|
|
|
// set rotate
|
|
if rotate, ok := params["rotate"]; ok && (rotate == "true" || rotate == "1") {
|
|
e.SetRotate(1)
|
|
}
|
|
|
|
// set font
|
|
if font, ok := params["font"]; ok {
|
|
e.SetFont(strings.ToUpper(font[5:6]))
|
|
}
|
|
|
|
// do dw (double font width)
|
|
if dw, ok := params["dw"]; ok && (dw == "true" || dw == "1") {
|
|
e.SetFontSize(2, e.height)
|
|
}
|
|
|
|
// do dh (double font height)
|
|
if dh, ok := params["dh"]; ok && (dh == "true" || dh == "1") {
|
|
e.SetFontSize(e.width, 2)
|
|
}
|
|
|
|
// do font width
|
|
if width, ok := params["width"]; ok {
|
|
if i, err := strconv.Atoi(width); err == nil {
|
|
e.SetFontSize(uint8(i), e.height)
|
|
} else {
|
|
log.Fatalf("Invalid font width: %s", width)
|
|
}
|
|
}
|
|
|
|
// do font height
|
|
if height, ok := params["height"]; ok {
|
|
if i, err := strconv.Atoi(height); err == nil {
|
|
e.SetFontSize(e.width, uint8(i))
|
|
} else {
|
|
log.Fatalf("Invalid font height: %s", height)
|
|
}
|
|
}
|
|
|
|
// do y positioning
|
|
if x, ok := params["x"]; ok {
|
|
if i, err := strconv.Atoi(x); err == nil {
|
|
e.SendMoveX(uint16(i))
|
|
} else {
|
|
log.Fatalf("Invalid x param %s", x)
|
|
}
|
|
}
|
|
|
|
// do y positioning
|
|
if y, ok := params["y"]; ok {
|
|
if i, err := strconv.Atoi(y); err == nil {
|
|
e.SendMoveY(uint16(i))
|
|
} else {
|
|
log.Fatalf("Invalid y param %s", y)
|
|
}
|
|
}
|
|
|
|
// do text replace, then write data
|
|
data = textReplace(data)
|
|
if len(data) > 0 {
|
|
e.Write(data)
|
|
}
|
|
}
|
|
|
|
// feed the printer
|
|
func (e *Escpos) Feed(params map[string]string) {
|
|
// handle lines (form feed X lines)
|
|
if l, ok := params["line"]; ok {
|
|
if i, err := strconv.Atoi(l); err == nil {
|
|
e.FormfeedN(i)
|
|
} else {
|
|
log.Fatalf("Invalid line number %s", l)
|
|
}
|
|
}
|
|
|
|
// handle units (dots)
|
|
if u, ok := params["unit"]; ok {
|
|
if i, err := strconv.Atoi(u); err == nil {
|
|
e.SendMoveY(uint16(i))
|
|
} else {
|
|
log.Fatalf("Invalid unit number %s", u)
|
|
}
|
|
}
|
|
|
|
// send linefeed
|
|
e.Linefeed()
|
|
|
|
// reset variables
|
|
e.reset()
|
|
|
|
// reset printer
|
|
e.SendEmphasize()
|
|
e.SendRotate()
|
|
e.SendSmooth()
|
|
e.SendReverse()
|
|
e.SendUnderline()
|
|
e.SendUpsidedown()
|
|
e.SendFontSize()
|
|
e.SendUnderline()
|
|
}
|
|
|
|
// feed and cut based on parameters
|
|
func (e *Escpos) FeedAndCut(params map[string]string) {
|
|
if t, ok := params["type"]; ok && t == "feed" {
|
|
e.Formfeed()
|
|
}
|
|
|
|
e.Cut()
|
|
}
|
|
|
|
// Barcode sends a barcode to the printer.
|
|
func (e *Escpos) Barcode(barcode string, format int) {
|
|
code := ""
|
|
switch format {
|
|
case 0:
|
|
code = "\x00"
|
|
case 1:
|
|
code = "\x01"
|
|
case 2:
|
|
code = "\x02"
|
|
case 3:
|
|
code = "\x03"
|
|
case 4:
|
|
code = "\x04"
|
|
case 73:
|
|
code = "\x49"
|
|
}
|
|
|
|
// reset settings
|
|
e.reset()
|
|
|
|
// set align
|
|
e.SetAlign("center")
|
|
|
|
// write barcode
|
|
if format > 69 {
|
|
e.Write(fmt.Sprintf("\x1dk"+code+"%v%v", len(barcode), barcode))
|
|
} else if format < 69 {
|
|
e.Write(fmt.Sprintf("\x1dk"+code+"%v\x00", barcode))
|
|
}
|
|
e.Write(fmt.Sprintf("%v", barcode))
|
|
}
|
|
|
|
// used to send graphics headers
|
|
func (e *Escpos) gSend(m byte, fn byte, data []byte) {
|
|
l := len(data) + 2
|
|
|
|
e.Write("\x1b(L")
|
|
e.WriteRaw([]byte{byte(l % 256), byte(l / 256), m, fn})
|
|
e.WriteRaw(data)
|
|
}
|
|
|
|
// write an image
|
|
func (e *Escpos) Image(params map[string]string, data string) {
|
|
// send alignment to printer
|
|
if align, ok := params["align"]; ok {
|
|
e.SetAlign(align)
|
|
}
|
|
|
|
// get width
|
|
wstr, ok := params["width"]
|
|
if !ok {
|
|
log.Fatal("No width specified on image")
|
|
}
|
|
|
|
// get height
|
|
hstr, ok := params["height"]
|
|
if !ok {
|
|
log.Fatal("No height specified on image")
|
|
}
|
|
|
|
// convert width
|
|
width, err := strconv.Atoi(wstr)
|
|
if err != nil {
|
|
log.Fatalf("Invalid image width %s", wstr)
|
|
}
|
|
|
|
// convert height
|
|
height, err := strconv.Atoi(hstr)
|
|
if err != nil {
|
|
log.Fatalf("Invalid image height %s", hstr)
|
|
}
|
|
|
|
// decode data frome b64 string
|
|
dec, err := base64.StdEncoding.DecodeString(data)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
log.Printf("Image len:%d w: %d h: %d\n", len(dec), width, height)
|
|
|
|
// $imgHeader = self::dataHeader(array($img -> getWidth(), $img -> getHeight()), true);
|
|
// $tone = '0';
|
|
// $colors = '1';
|
|
// $xm = (($size & self::IMG_DOUBLE_WIDTH) == self::IMG_DOUBLE_WIDTH) ? chr(2) : chr(1);
|
|
// $ym = (($size & self::IMG_DOUBLE_HEIGHT) == self::IMG_DOUBLE_HEIGHT) ? chr(2) : chr(1);
|
|
//
|
|
// $header = $tone . $xm . $ym . $colors . $imgHeader;
|
|
// $this -> graphicsSendData('0', 'p', $header . $img -> toRasterFormat());
|
|
// $this -> graphicsSendData('0', '2');
|
|
|
|
header := []byte{
|
|
byte('0'), 0x01, 0x01, byte('1'),
|
|
}
|
|
|
|
a := append(header, dec...)
|
|
|
|
e.gSend(byte('0'), byte('p'), a)
|
|
e.gSend(byte('0'), byte('2'), []byte{})
|
|
|
|
}
|
|
|
|
// write a "node" to the printer
|
|
func (e *Escpos) WriteNode(name string, params map[string]string, data string) {
|
|
cstr := ""
|
|
if data != "" {
|
|
str := data[:]
|
|
if len(data) > 40 {
|
|
str = fmt.Sprintf("%s ...", data[0:40])
|
|
}
|
|
cstr = fmt.Sprintf(" => '%s'", str)
|
|
}
|
|
log.Printf("Write: %s => %+v%s\n", name, params, cstr)
|
|
|
|
switch name {
|
|
case "text":
|
|
e.Text(params, data)
|
|
case "feed":
|
|
e.Feed(params)
|
|
case "cut":
|
|
e.FeedAndCut(params)
|
|
case "pulse":
|
|
e.Pulse()
|
|
case "image":
|
|
e.Image(params, data)
|
|
}
|
|
}
|
|
|
|
// ReadStatus Read the status n from the printer
|
|
func (e *Escpos) ReadStatus(n byte) (byte, error) {
|
|
e.WriteRaw([]byte{DLE, EOT, n})
|
|
data := make([]byte, 1)
|
|
_, err := e.ReadRaw(data)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return data[0], nil
|
|
}
|