initial commit

This commit is contained in:
2025-02-03 16:58:00 -05:00
commit 35b287667a
9 changed files with 1655 additions and 0 deletions

594
printer/printer.go Normal file
View File

@ -0,0 +1,594 @@
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
"'": "'",
""": `"`,
">": ">",
"&lt;": "<",
// ampersand must be last to avoid double decoding
"&amp;": "&",
}
// 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
}