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 }