diff --git a/.gitignore b/.gitignore
index 16a33ed..396995d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-epos-server/epos-server
+/cmd/epos-server/epos-server
+/cmd/epos-server/epos-server.exe
diff --git a/README.md b/README.md
index 16e98ad..da1776d 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,8 @@
# About escpos #
-This is a simple [Golang](http://www.golang.org/project) package that provides
-[ESC-POS](https://en.wikipedia.org/wiki/ESC/P) library functions to help with
-sending control codes to a ESC-POS capable printer such as an Epson TM-T82 or
-similar.
+This is a simple [Go][1] package that provides [ESC-POS][2] library functions
+to help with sending control codes to a ESC-POS capable printer such as an
+Epson TM-T82 or similar.
These printers are often used in retail environments in conjunction with a
point-of-sale (POS) system.
@@ -12,14 +11,13 @@ point-of-sale (POS) system.
Install the package via the following:
- go get -u github.com/knq/escpos
+ go get -u github.com/kenshaw/escpos
## Example epos-server ##
-An example EPOS server implementation is available in the
-[epos-server](epos-server) subdirectory of this project. This example
-server is more or less compatible with [Epson TM-Intelligent](https://c4b.epson-biz.com)
-printers and print server implementations.
+An example EPOS server implementation is available in the [cmd/epos-server][3]
+subdirectory of this project. This example server is more or less compatible
+with [Epson TM-Intelligent][4] printers and print server implementations.
## Usage ##
@@ -32,7 +30,7 @@ import (
"bufio"
"os"
- "github.com/knq/escpos"
+ "github.com/kenshaw/escpos"
)
func main() {
@@ -86,5 +84,8 @@ func main() {
## TODO
- Fix barcode/image support
-- Update code to be idiomatic Go
-- Fix example server implementation
+
+[1]: http://www.golang.org/project
+[2]: https://en.wikipedia.org/wiki/ESC/P
+[3]: cmd/epos-server
+[4]: https://c4b.epson-biz.com
diff --git a/epos-server/README.md b/cmd/epos-server/README.md
similarity index 71%
rename from epos-server/README.md
rename to cmd/epos-server/README.md
index 46ff4cc..7774618 100644
--- a/epos-server/README.md
+++ b/cmd/epos-server/README.md
@@ -1,10 +1,9 @@
# About epos-server #
-This is a quick and dirty Golang implementation of a [Epson TM-Intelligent](https://c4b.epson-biz.com/)
-print server. This also serves as example code for the
-[escpos](https://github.com/knq/escpos) package. This is "more-or-less"
-compatible with the ePOS-Print API and shows how ePOS-XML is translated into
-simple ESCPOS data.
+This is a quick and dirty Golang implementation of a [Epson TM-Intelligent][1]
+print server. This also serves as example code for the [escpos][2] package.
+This is "more-or-less" compatible with the ePOS-Print API and shows how
+ePOS-XML is translated into simple ESCPOS data.
This has been tested and works as expected on Linux.
@@ -20,12 +19,11 @@ like the following for your system:
Then install via the following:
- go get -u github.com/knq/escpos/epos-server
-
+ go get -u github.com/kenshaw/escpos/epos-server
You should then be able to build the epos-server like this:
- go build github.com/knq/escpos/epos-server
+ go build github.com/kenshaw/escpos/epos-server
## Usage ##
@@ -46,3 +44,6 @@ printer:
The following still needs to be implemented:
* Fix image decoding and printing
+
+[1]: https://c4b.epson-biz.com/
+[2]: https://github.com/kenshaw/escpos
diff --git a/cmd/epos-server/main.go b/cmd/epos-server/main.go
new file mode 100644
index 0000000..c16774b
--- /dev/null
+++ b/cmd/epos-server/main.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "net/http"
+ "os"
+
+ "github.com/kenshaw/escpos"
+)
+
+var (
+ flagListen = flag.String("l", "127.0.22.8:80", "listen")
+ flagEndpoint = flag.String("endpoint", escpos.DefaultEndpoint, "endpoint")
+ flagPrinter = flag.String("p", "", "path to printer")
+)
+
+func main() {
+ flag.Parse()
+
+ if *flagPrinter == "" {
+ log.Fatal("must specify path to printer via -p")
+ }
+
+ // open printer
+ f, err := os.Create(*flagPrinter)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+
+ // create printer
+ ep, err := escpos.NewPrinter(f)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // create server
+ s, err := escpos.NewServer(ep)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // set up mux
+ mux := http.NewServeMux()
+ mux.Handle(*flagEndpoint, s)
+ mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
+ http.Error(res, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ })
+
+ log.Fatal(http.ListenAndServe(*flagListen, mux))
+}
diff --git a/epos-server/epos-server.go b/epos-server/epos-server.go
deleted file mode 100644
index e108591..0000000
--- a/epos-server/epos-server.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package main
-
-import (
- "bufio"
- "errors"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "os"
-
- "github.com/gorilla/mux"
- "github.com/knq/escpos"
- "github.com/moovweb/gokogiri"
- "github.com/moovweb/gokogiri/xml"
- "github.com/moovweb/gokogiri/xpath"
-)
-
-var listenAddr = flag.String("l", "127.0.22.8", "Address to listen on")
-var port = flag.Int("port", 80, "Port to listen on")
-var printerPath = flag.String("p", "/dev/usb/lp0", "Path to printer")
-
-type EposServer struct {
- r *mux.Router
- printer *escpos.Escpos
- printerWriter *bufio.Writer
-}
-
-func writeSoapResponse(rw http.ResponseWriter, req *http.Request, code string) {
- success_str := "false"
- if code == "" {
- success_str = "true"
- }
-
- response := fmt.Sprintf(`
-
-
-
-
-`, success_str, code)
-
- log.Printf("Sending:\n%s\n", response)
-
- // inject response
- rw.Header().Set("Content-Type", req.Header.Get("Content-Type"))
- rw.Write([]byte(response))
-}
-
-func getEposNodes(doc *xml.XmlDocument) (retnodes []xml.Node, err error) {
- // grab the 'Body' element
- path := xpath.Compile("*[local-name()='Body']")
- nodes, e := doc.Root().Search(path)
- if e != nil {
- err = e
- return
- }
-
- // check that the data is present
- if len(nodes) < 1 || nodes[0].CountChildren() < 1 {
- err = errors.New("bad data")
- return
- }
-
- // get epos data
- return nodes[0].FirstChild().Search("./*")
-}
-
-func (s *EposServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
- // send origin headers
- if origin := req.Header.Get("Origin"); origin != "" {
- rw.Header().Set("Access-Control-Allow-Origin", origin)
- rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
- rw.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, If-Modified-Since, SOAPAction")
- }
-
- // stop if its options
- if req.Method == "OPTIONS" {
- log.Printf("OPTIONS %s\n", req.URL)
- return
- }
-
- // handle crappy soap action
- if req.Method == "POST" {
- // grab posted body
- data, _ := ioutil.ReadAll(req.Body)
- log.Printf("POST %s:\n%s\n\n", req.URL, string(data))
-
- // parse xml with gokogiri
- doc, _ := gokogiri.ParseXml(data)
- defer doc.Free()
-
- // load print nodes from xml doc
- epos_nodes, err := getEposNodes(doc)
- if err != nil {
- rw.WriteHeader(503)
- log.Fatal(err)
- return
- }
-
- // init printer
- s.printer.Init()
-
- // loop over nodes
- for _, en := range epos_nodes {
- // grab name and inner text
- name := en.Name()
- content := en.Content()
-
- // grab parameters
- params := make(map[string]string)
- for _, attr := range en.Attributes() {
- params[attr.Name()] = attr.Value()
- }
-
- // write data to printer
- s.printer.WriteNode(name, params, content)
- }
-
- // end
- s.printer.End()
-
- // flush writer
- s.printerWriter.Flush()
-
- //rw.WriteHeader(402)
- // write soap response
- writeSoapResponse(rw, req, "")
-
- return
- }
-
- // force an error for everything else
- rw.WriteHeader(403)
-
- // Lets Gorilla work
- s.r.ServeHTTP(rw, req)
-}
-
-func main() {
- flag.Parse()
-
- // open printer
- f, err := os.Create(*printerPath)
- if err != nil {
- panic(err)
- }
- defer f.Close()
-
- // setup buffered writer
- w := bufio.NewWriter(f)
- ep := escpos.New(w)
-
- // set up service router
- r := mux.NewRouter()
- http.Handle("/cgi-bin/epos/service.cgi", &EposServer{r, ep, w})
-
- log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *listenAddr, *port), nil))
-}
diff --git a/escpos.go b/escpos.go
index df73667..57ebb3a 100644
--- a/escpos.go
+++ b/escpos.go
@@ -2,134 +2,112 @@ package escpos
import (
"encoding/base64"
+ "errors"
"fmt"
"io"
"log"
"strconv"
"strings"
+ "sync"
)
-// 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 {
+// Printer wraps sending ESC-POS commands to a io.Writer.
+type Printer struct {
// destination
- dst io.Writer
+ w io.Writer
// font metrics
- width, height uint8
+ width, height byte
// state toggles ESC[char]
- underline uint8
- emphasize uint8
- upsidedown uint8
- rotate uint8
+ underline byte
+ emphasize byte
+ upsidedown byte
+ rotate byte
// state toggles GS[char]
- reverse, smooth uint8
+ reverse, smooth byte
+
+ sync.Mutex
}
-// reset toggles
-func (e *Escpos) reset() {
- e.width = 1
- e.height = 1
+// NewPrinter creates a new printer using the specified writer.
+func NewPrinter(w io.Writer /*, opts ...PrinterOption*/) (*Printer, error) {
+ if w == nil {
+ return nil, errors.New("must supply valid writer")
+ }
- e.underline = 0
- e.emphasize = 0
- e.upsidedown = 0
- e.rotate = 0
+ p := &Printer{
+ w: w,
+ width: 1,
+ height: 1,
+ }
- e.reverse = 0
- e.smooth = 0
+ return p, nil
}
-// create Escpos printer
-func New(dst io.Writer) (e *Escpos) {
- e = &Escpos{dst: dst}
- e.reset()
- return
-}
+// Reset resets the printer state.
+func (p *Printer) Reset() {
+ p.width = 1
+ p.height = 1
-// 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")
- }
+ p.underline = 0
+ p.emphasize = 0
+ p.upsidedown = 0
+ p.rotate = 0
- return 0, nil
+ p.reverse = 0
+ p.smooth = 0
}
-// write a string to the printer
-func (e *Escpos) Write(data string) (int, error) {
- return e.WriteRaw([]byte(data))
+// Write writes buf to printer.
+func (p *Printer) Write(buf []byte) (int, error) {
+ return p.w.Write(buf)
}
-// init/reset printer settings
-func (e *Escpos) Init() {
- e.reset()
- e.Write("\x1B@")
+// WriteString writes a string to the printer.
+func (p *Printer) WriteString(s string) (int, error) {
+ return p.w.Write([]byte(s))
}
-// end output
-func (e *Escpos) End() {
- e.Write("\xFA")
+// Init resets the state of the printer, and writes the initialize code.
+func (p *Printer) Init() {
+ p.Reset()
+ p.WriteString("\x1B@")
}
-// send cut
-func (e *Escpos) Cut() {
- e.Write("\x1DVA0")
+// End terminates the printer session.
+func (p *Printer) End() {
+ p.WriteString("\xFA")
}
-// send cash
-func (e *Escpos) Cash() {
- e.Write("\x1B\x70\x00\x0A\xFF")
+// Cut writes the cut code to the printer.
+func (p *Printer) Cut() {
+ p.WriteString("\x1DVA0")
}
-// send linefeed
-func (e *Escpos) Linefeed() {
- e.Write("\n")
+// Cash writes the cash code to the printer.
+func (p *Printer) Cash() {
+ p.WriteString("\x1B\x70\x00\x0A\xFF")
}
-// send N formfeeds
-func (e *Escpos) FormfeedN(n int) {
- e.Write(fmt.Sprintf("\x1Bd%c", n))
+// Linefeed writes a line end to the printer.
+func (p *Printer) Linefeed() {
+ p.WriteString("\n")
}
-// send formfeed
-func (e *Escpos) Formfeed() {
- e.FormfeedN(1)
+// FormfeedN writes N formfeeds to the printer.
+func (p *Printer) FormfeedN(n int) {
+ p.WriteString(fmt.Sprintf("\x1Bd%c", n))
}
-// set font
-func (e *Escpos) SetFont(font string) {
+// Formfeed writes 1 formfeed to the printer.
+func (p *Printer) Formfeed() {
+ p.FormfeedN(1)
+}
+
+// SetFont sets the font on the printer.
+func (p *Printer) SetFont(font string) {
f := 0
switch font {
@@ -144,108 +122,108 @@ func (e *Escpos) SetFont(font string) {
f = 0
}
- e.Write(fmt.Sprintf("\x1BM%c", f))
+ p.WriteString(fmt.Sprintf("\x1BM%c", f))
}
-func (e *Escpos) SendFontSize() {
- e.Write(fmt.Sprintf("\x1D!%c", ((e.width-1)<<4)|(e.height-1)))
+// SendFontSize sends the font size command to the printer.
+func (p *Printer) SendFontSize() {
+ p.WriteString(fmt.Sprintf("\x1D!%c", ((p.width-1)<<4)|(p.height-1)))
}
-// set font size
-func (e *Escpos) SetFontSize(width, height uint8) {
+// SetFontSize sets the font size state and sends the command to the printer.
+func (p *Printer) SetFontSize(width, height byte) {
if width > 0 && height > 0 && width <= 8 && height <= 8 {
- e.width = width
- e.height = height
- e.SendFontSize()
+ p.width, p.height = width, height
+ p.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))
+// SendUnderline sends the underline command to the printer.
+func (p *Printer) SendUnderline() {
+ p.WriteString(fmt.Sprintf("\x1B-%c", p.underline))
}
-// send emphasize / doublestrike
-func (e *Escpos) SendEmphasize() {
- e.Write(fmt.Sprintf("\x1BG%c", e.emphasize))
+// SendEmphasize sends the emphasize / doublestrike command to the printer.
+func (p *Printer) SendEmphasize() {
+ p.WriteString(fmt.Sprintf("\x1BG%c", p.emphasize))
}
-// send upsidedown
-func (e *Escpos) SendUpsidedown() {
- e.Write(fmt.Sprintf("\x1B{%c", e.upsidedown))
+// SendUpsidedown sends the upsidedown command to the printer.
+func (p *Printer) SendUpsidedown() {
+ p.WriteString(fmt.Sprintf("\x1B{%c", p.upsidedown))
}
-// send rotate
-func (e *Escpos) SendRotate() {
- e.Write(fmt.Sprintf("\x1BR%c", e.rotate))
+// SendRotate sends the rotate command to the printer.
+func (p *Printer) SendRotate() {
+ p.WriteString(fmt.Sprintf("\x1BR%c", p.rotate))
}
-// send reverse
-func (e *Escpos) SendReverse() {
- e.Write(fmt.Sprintf("\x1DB%c", e.reverse))
+// SendReverse sends the reverse command to the printer.
+func (p *Printer) SendReverse() {
+ p.WriteString(fmt.Sprintf("\x1DB%c", p.reverse))
}
-// send smooth
-func (e *Escpos) SendSmooth() {
- e.Write(fmt.Sprintf("\x1Db%c", e.smooth))
+// SendSmooth sends the smooth command to the printer.
+func (p *Printer) SendSmooth() {
+ p.WriteString(fmt.Sprintf("\x1Db%c", p.smooth))
}
-// send move x
-func (e *Escpos) SendMoveX(x uint16) {
- e.Write(string([]byte{0x1b, 0x24, byte(x % 256), byte(x / 256)}))
+// SendMoveX sends the move x command to the printer.
+func (p *Printer) SendMoveX(x uint16) {
+ p.Write([]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)}))
+// SendMoveY sends the move y command to the printer.
+func (p *Printer) SendMoveY(y uint16) {
+ p.Write([]byte{0x1d, 0x24, byte(y % 256), byte(y / 256)})
}
-// set underline
-func (e *Escpos) SetUnderline(v uint8) {
- e.underline = v
- e.SendUnderline()
+// SetUnderline sets the underline state and sends it to the printer.
+func (p *Printer) SetUnderline(v byte) {
+ p.underline = v
+ p.SendUnderline()
}
-// set emphasize
-func (e *Escpos) SetEmphasize(u uint8) {
- e.emphasize = u
- e.SendEmphasize()
+// SetEmphasize sets the emphasize state and sends it to the printer.
+func (p *Printer) SetEmphasize(u byte) {
+ p.emphasize = u
+ p.SendEmphasize()
}
-// set upsidedown
-func (e *Escpos) SetUpsidedown(v uint8) {
- e.upsidedown = v
- e.SendUpsidedown()
+// SetUpsidedown sets the upsidedown state and sends it to the printer.
+func (p *Printer) SetUpsidedown(v byte) {
+ p.upsidedown = v
+ p.SendUpsidedown()
}
-// set rotate
-func (e *Escpos) SetRotate(v uint8) {
- e.rotate = v
- e.SendRotate()
+// SetRotate sets the rotate state and sends it to the printer.
+func (p *Printer) SetRotate(v byte) {
+ p.rotate = v
+ p.SendRotate()
}
-// set reverse
-func (e *Escpos) SetReverse(v uint8) {
- e.reverse = v
- e.SendReverse()
+// SetReverse sets the reverse state and sends it to the printer.
+func (p *Printer) SetReverse(v byte) {
+ p.reverse = v
+ p.SendReverse()
}
-// set smooth
-func (e *Escpos) SetSmooth(v uint8) {
- e.smooth = v
- e.SendSmooth()
+// SetSmooth sets the smooth state and sends it to the printer.
+func (p *Printer) SetSmooth(v byte) {
+ p.smooth = v
+ p.SendSmooth()
}
-// pulse (open the drawer)
-func (e *Escpos) Pulse() {
+// Pulse sends the pulse (open drawer) code to the printer.
+func (p *Printer) Pulse() {
// with t=2 -- meaning 2*2msec
- e.Write("\x1Bp\x02")
+ p.WriteString("\x1Bp\x02")
}
-// set alignment
-func (e *Escpos) SetAlign(align string) {
+// SetAlign sets the alignment state and sends it to the printer.
+func (p *Printer) SetAlign(align string) {
a := 0
switch align {
case "left":
@@ -257,11 +235,11 @@ func (e *Escpos) SetAlign(align string) {
default:
log.Fatalf("Invalid alignment: %s", align)
}
- e.Write(fmt.Sprintf("\x1Ba%c", a))
+ p.WriteString(fmt.Sprintf("\x1Ba%c", a))
}
-// set language -- ESC R
-func (e *Escpos) SetLang(lang string) {
+// SetLang sets the language state and sends it to the printer.
+func (p *Printer) SetLang(lang string) {
l := 0
switch lang {
@@ -288,66 +266,66 @@ func (e *Escpos) SetLang(lang string) {
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) {
+ p.WriteString(fmt.Sprintf("\x1BR%c", l))
+}
+// Text sends a block of text to the printer using the formatting parameters in params.
+func (p *Printer) Text(params map[string]string, text string) {
// send alignment to printer
if align, ok := params["align"]; ok {
- e.SetAlign(align)
+ p.SetAlign(align)
}
// set lang
if lang, ok := params["lang"]; ok {
- e.SetLang(lang)
+ p.SetLang(lang)
}
// set smooth
if smooth, ok := params["smooth"]; ok && (smooth == "true" || smooth == "1") {
- e.SetSmooth(1)
+ p.SetSmooth(1)
}
// set emphasize
if em, ok := params["em"]; ok && (em == "true" || em == "1") {
- e.SetEmphasize(1)
+ p.SetEmphasize(1)
}
// set underline
if ul, ok := params["ul"]; ok && (ul == "true" || ul == "1") {
- e.SetUnderline(1)
+ p.SetUnderline(1)
}
// set reverse
if reverse, ok := params["reverse"]; ok && (reverse == "true" || reverse == "1") {
- e.SetReverse(1)
+ p.SetReverse(1)
}
// set rotate
if rotate, ok := params["rotate"]; ok && (rotate == "true" || rotate == "1") {
- e.SetRotate(1)
+ p.SetRotate(1)
}
// set font
if font, ok := params["font"]; ok {
- e.SetFont(strings.ToUpper(font[5:6]))
+ p.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)
+ p.SetFontSize(2, p.height)
}
// do dh (double font height)
if dh, ok := params["dh"]; ok && (dh == "true" || dh == "1") {
- e.SetFontSize(e.width, 2)
+ p.SetFontSize(p.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)
+ p.SetFontSize(byte(i), p.height)
} else {
log.Fatalf("Invalid font width: %s", width)
}
@@ -356,7 +334,7 @@ func (e *Escpos) Text(params map[string]string, data string) {
// do font height
if height, ok := params["height"]; ok {
if i, err := strconv.Atoi(height); err == nil {
- e.SetFontSize(e.width, uint8(i))
+ p.SetFontSize(p.width, byte(i))
} else {
log.Fatalf("Invalid font height: %s", height)
}
@@ -365,7 +343,7 @@ func (e *Escpos) Text(params map[string]string, data string) {
// do y positioning
if x, ok := params["x"]; ok {
if i, err := strconv.Atoi(x); err == nil {
- e.SendMoveX(uint16(i))
+ p.SendMoveX(uint16(i))
} else {
log.Fatalf("Invalid x param %s", x)
}
@@ -374,25 +352,24 @@ func (e *Escpos) Text(params map[string]string, data string) {
// do y positioning
if y, ok := params["y"]; ok {
if i, err := strconv.Atoi(y); err == nil {
- e.SendMoveY(uint16(i))
+ p.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)
+ if len(text) > 0 {
+ p.WriteString(textReplacer.Replace(text))
}
}
-// feed the printer
-func (e *Escpos) Feed(params map[string]string) {
+// Feed feeds the printer, applying the supplied params as necessary.
+func (p *Printer) 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)
+ p.FormfeedN(i)
} else {
log.Fatalf("Invalid line number %s", l)
}
@@ -401,40 +378,41 @@ func (e *Escpos) Feed(params map[string]string) {
// handle units (dots)
if u, ok := params["unit"]; ok {
if i, err := strconv.Atoi(u); err == nil {
- e.SendMoveY(uint16(i))
+ p.SendMoveY(uint16(i))
} else {
log.Fatalf("Invalid unit number %s", u)
}
}
// send linefeed
- e.Linefeed()
+ p.Linefeed()
// reset variables
- e.reset()
+ p.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) {
+ p.SendEmphasize()
+ p.SendRotate()
+ p.SendSmooth()
+ p.SendReverse()
+ p.SendUnderline()
+ p.SendUpsidedown()
+ p.SendFontSize()
+ p.SendUnderline()
+}
+
+// FeedAndCut feeds the printer using the supplied params and then sends a cut
+// command.
+func (p *Printer) FeedAndCut(params map[string]string) {
if t, ok := params["type"]; ok && t == "feed" {
- e.Formfeed()
+ p.Formfeed()
}
- e.Cut()
+ p.Cut()
}
// Barcode sends a barcode to the printer.
-func (e *Escpos) Barcode(barcode string, format int) {
+func (p *Printer) Barcode(barcode string, format int) {
code := ""
switch format {
case 0:
@@ -452,34 +430,34 @@ func (e *Escpos) Barcode(barcode string, format int) {
}
// reset settings
- e.reset()
+ p.Reset()
// set align
- e.SetAlign("center")
+ p.SetAlign("center")
// write barcode
if format > 69 {
- e.Write(fmt.Sprintf("\x1dk"+code+"%v%v", len(barcode), barcode))
+ p.WriteString(fmt.Sprintf("\x1dk"+code+"%v%v", len(barcode), barcode))
} else if format < 69 {
- e.Write(fmt.Sprintf("\x1dk"+code+"%v\x00", barcode))
+ p.WriteString(fmt.Sprintf("\x1dk"+code+"%v\x00", barcode))
}
- e.Write(fmt.Sprintf("%v", barcode))
+ p.WriteString(barcode)
}
-// used to send graphics headers
-func (e *Escpos) gSend(m byte, fn byte, data []byte) {
+// gSendsend graphics headers.
+func (p *Printer) 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)
+ p.WriteString("\x1b(L")
+ p.Write([]byte{byte(l % 256), byte(l / 256), m, fn})
+ p.Write(data)
}
-// write an image
-func (e *Escpos) Image(params map[string]string, data string) {
+// Image writes an image using the supplied params.
+func (p *Printer) Image(params map[string]string, data string) {
// send alignment to printer
if align, ok := params["align"]; ok {
- e.SetAlign(align)
+ p.SetAlign(align)
}
// get width
@@ -530,16 +508,16 @@ func (e *Escpos) Image(params map[string]string, data string) {
a := append(header, dec...)
- e.gSend(byte('0'), byte('p'), a)
- e.gSend(byte('0'), byte('2'), []byte{})
-
+ p.gSend(byte('0'), byte('p'), a)
+ p.gSend(byte('0'), byte('2'), []byte{})
}
-// write a "node" to the printer
-func (e *Escpos) WriteNode(name string, params map[string]string, data string) {
+// WriteNode writes a node of type name with the supplied params and data to
+// the printer.
+func (p *Printer) WriteNode(name string, params map[string]string, data string) {
cstr := ""
if data != "" {
- str := data[:]
+ str := data
if len(data) > 40 {
str = fmt.Sprintf("%s ...", data[0:40])
}
@@ -549,14 +527,39 @@ func (e *Escpos) WriteNode(name string, params map[string]string, data string) {
switch name {
case "text":
- e.Text(params, data)
+ p.Text(params, data)
+
case "feed":
- e.Feed(params)
+ p.Feed(params)
+
case "cut":
- e.FeedAndCut(params)
+ p.FeedAndCut(params)
+
case "pulse":
- e.Pulse()
+ p.Pulse()
+
case "image":
- e.Image(params, data)
+ p.Image(params, data)
}
}
+
+// textReplacer is a simple text replacer for the only valid XML encoded
+// entities for escpos printers.
+var textReplacer = strings.NewReplacer(
+ // horizontal tab
+ " ", "\x09",
+ " ", "\x09",
+
+ // linefeed
+ "
", "\n",
+ "
", "\n",
+
+ // xml entities
+ "'", "'",
+ """, `"`,
+ ">", ">",
+ "<", "<",
+
+ // & (ampersand) must be last to avoid double decoding
+ "&", "&",
+)
diff --git a/example/all-in-one-print/README.md b/example/all-in-one-print/README.md
new file mode 100644
index 0000000..af89e47
--- /dev/null
+++ b/example/all-in-one-print/README.md
@@ -0,0 +1 @@
+Must be run from this folder to get the image.
diff --git a/example/all-in-one-print/crab_nebula.jpg b/example/all-in-one-print/crab_nebula.jpg
new file mode 100644
index 0000000..61d156e
Binary files /dev/null and b/example/all-in-one-print/crab_nebula.jpg differ
diff --git a/example/all-in-one-print/main.go b/example/all-in-one-print/main.go
new file mode 100644
index 0000000..5269437
--- /dev/null
+++ b/example/all-in-one-print/main.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "image"
+ "log"
+ "os"
+
+ _ "image/jpeg"
+
+ "github.com/kenshaw/escpos"
+ "github.com/kenshaw/escpos/raster"
+)
+
+var (
+ lpDev = flag.String("p", "/dev/usb/lp0", "Printer dev file")
+ maxWidth = flag.Int("printer-max-width", 512, "Printer max width in pixels")
+
+ ep *escpos.Printer
+)
+
+func main() {
+ flag.Parse()
+
+ f, err := os.OpenFile(*lpDev, os.O_WRONLY, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ ep, err = escpos.NewPrinter(f)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ ep.Init()
+
+ defer func() {
+ ep.Cut()
+ ep.End()
+ }()
+
+ ep.Text(nil, "sample text...\n\n")
+
+ for _, font := range []string{"A", "B", "C"} {
+ ep.SetFont(font)
+ ep.Text(nil, fmt.Sprintf("sample text, font %s...\n\n", font))
+ }
+ ep.SetFont("B")
+
+ for _, format := range []int{0, 1, 2, 3, 4, 73} {
+ ep.Text(nil, fmt.Sprintf("sample barcode, format %d:\n", format))
+ ep.Barcode("123456", format)
+ ep.Linefeed()
+ }
+
+ ep.Text(nil, "sample image:\n")
+ rasterImage()
+
+ ep.Text(nil, "cash code:\n\n")
+ ep.Cash()
+}
+
+func rasterImage() {
+ imgFile, err := os.Open("crab_nebula.jpg")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ img, imgFormat, err := image.Decode(imgFile)
+ imgFile.Close()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Print("Loaded image, format: ", imgFormat)
+
+ rasterConv := &raster.Converter{
+ MaxWidth: *maxWidth,
+ Threshold: 0.5,
+ }
+
+ rasterConv.Print(img, ep)
+}
diff --git a/example/all-in-one-print/outputs/Epson_TM-T88V_M244A.jpg b/example/all-in-one-print/outputs/Epson_TM-T88V_M244A.jpg
new file mode 100644
index 0000000..5709a21
Binary files /dev/null and b/example/all-in-one-print/outputs/Epson_TM-T88V_M244A.jpg differ
diff --git a/example/image2pos/main.go b/example/image2pos/main.go
new file mode 100644
index 0000000..b139abc
--- /dev/null
+++ b/example/image2pos/main.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+ "flag"
+ "image"
+ "log"
+ "os"
+
+ _ "image/gif"
+ _ "image/jpeg"
+ _ "image/png"
+
+ "github.com/kenshaw/escpos"
+ "github.com/kenshaw/escpos/raster"
+)
+
+var (
+ lpDev = flag.String("p", "/dev/usb/lp0", "Printer dev file")
+ imgPath = flag.String("i", "image.png", "Input image")
+ threshold = flag.Float64("t", 0.5, "Black/white threshold")
+ align = flag.String("a", "center", "Alignment (left, center, right)")
+ doCut = flag.Bool("c", false, "Cut after print")
+ maxWidth = flag.Int("printer-max-width", 512, "Printer max width in pixels")
+)
+
+func main() {
+ flag.Parse()
+
+ imgFile, err := os.Open(*imgPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ img, imgFormat, err := image.Decode(imgFile)
+ imgFile.Close()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Print("Loaded image, format: ", imgFormat)
+
+ // ----------------------------------------------------------------------
+
+ f, err := os.OpenFile(*lpDev, os.O_WRONLY, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer f.Close()
+ log.Print(*lpDev, " open.")
+
+ ep, err := escpos.NewPrinter(f)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ ep.Init()
+
+ ep.SetAlign(*align)
+
+ rasterConv := &raster.Converter{
+ MaxWidth: *maxWidth,
+ Threshold: *threshold,
+ }
+
+ rasterConv.Print(img, ep)
+
+ if *doCut {
+ ep.Cut()
+ }
+ ep.End()
+}
diff --git a/opts.go b/opts.go
new file mode 100644
index 0000000..609057d
--- /dev/null
+++ b/opts.go
@@ -0,0 +1,12 @@
+package escpos
+
+// ServerOption is a server option.
+type ServerOption func(*Server) error
+
+// WithLog is a server option to set a logging func.
+func WithLog(f func(string, ...interface{})) ServerOption {
+ return func(s *Server) error {
+ s.logger = f
+ return nil
+ }
+}
diff --git a/raster.go b/raster.go
index 2f0957f..84953ce 100644
--- a/raster.go
+++ b/raster.go
@@ -1,50 +1,44 @@
package escpos
const (
- GS8L_MAX_Y = 1662
+ gs8lMaxY = 1662
)
-func (e *Escpos) Raster(width, height, bytesWidth int, img_bw []byte) {
- flushCmd := []byte{
- /* GS ( L, Print the graphics data in the print buffer,
- p. 241 Moves print position to the left side of the
- print area after printing of graphics data is
- completed */
- 0x1d, 0x28, 0x4c, 0x02, 0x00, 0x30,
- /* Fn 50 */
- 0x32,
- }
-
+// Raster writes a rasterized version of a black and white image to the printer
+// with the specified width, height, and lineWidth bytes per line.
+func (p *Printer) Raster(width, height, lineWidth int, imgBw []byte) {
for l := 0; l < height; {
- n_lines := GS8L_MAX_Y
- if n_lines > height-l {
- n_lines = height - l
+ lines := gs8lMaxY
+ if lines > height-l {
+ lines = height - l
}
- f112_p := 10 + n_lines*bytesWidth
- storeCmd := []byte{
- /* GS 8 L, Store the graphics data in the print buffer
- (raster format), p. 252 */
- 0x1d, 0x38, 0x4c,
- /* p1 p2 p3 p4 */
- byte(f112_p), byte(f112_p >> 8),
- byte(f112_p >> 16), byte(f112_p >> 24),
- /* Function 112 */
- 0x30, 0x70, 0x30,
- /* bx by, zoom */
- 0x01, 0x01,
- /* c, single-color printing model */
- 0x31,
- /* xl, xh, number of dots in the horizontal direction */
- byte(width), byte(width >> 8),
- /* yl, yh, number of dots in the vertical direction */
- byte(n_lines), byte(n_lines >> 8),
- }
+ f112P := 10 + lines*lineWidth
+
+ p.Write([]byte{
+ 0x1d, 0x38, 0x4c, // GS 8 L, Store the graphics data in the print buffer -- (raster format), p. 252
+ byte(f112P), byte(f112P >> 8), byte(f112P >> 16), byte(f112P >> 24), // p1 p2 p3 p4
+ 0x30, 0x70, 0x30, // function 112
+ 0x01, 0x01, // bx, by -- zoom
+ 0x31, // c -- single-color printing model
+ byte(width), byte(width >> 8), // xl, xh -- number of dots in the horizontal direction
+ byte(lines), byte(lines >> 8), // yl, yh -- number of dots in the vertical direction
+ })
+
+ // write line
+ p.Write(imgBw[l*lineWidth : (l+lines)*lineWidth])
- e.WriteRaw(storeCmd)
- e.WriteRaw(img_bw[l*bytesWidth : (l+n_lines)*bytesWidth])
- e.WriteRaw(flushCmd)
+ // flush
+ //
+ // GS ( L, Print the graphics data in the print buffer,
+ // p. 241 Moves print position to the left side of the
+ // print area after printing of graphics data is
+ // completed
+ p.Write([]byte{
+ 0x1d, 0x28, 0x4c, 0x02, 0x00, 0x30,
+ 0x32, // Fn 50
+ })
- l += n_lines
+ l += lines
}
}
diff --git a/server.go b/server.go
new file mode 100644
index 0000000..f0b62b2
--- /dev/null
+++ b/server.go
@@ -0,0 +1,134 @@
+package escpos
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+
+ "github.com/moovweb/gokogiri"
+)
+
+const (
+ // DefaultEndpoint is the default server endpoint for ePOS printers.
+ DefaultEndpoint = "/cgi-bin/epos/service.cgi"
+)
+
+// Server wrap
+type Server struct {
+ p *Printer
+ w *bufio.Writer
+ logger func(string, ...interface{})
+}
+
+// NewServer creates a new ePOS server.
+func NewServer(w io.Writer, opts ...ServerOption) (*Server, error) {
+ var err error
+
+ // create printer
+ p, err := NewPrinter(w)
+ if err != nil {
+ return nil, err
+ }
+
+ s := &Server{
+ p: p,
+ w: bufio.NewWriter(p),
+ }
+
+ // apply opts
+ for _, o := range opts {
+ err = o(s)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if s.logger == nil {
+ s.logger = func(string, ...interface{}) {}
+ }
+
+ return s, nil
+}
+
+// ServeHTTP handles OPTIONS, Origin, and POST for an ePOS server.
+func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
+ s.logger("%s %s", req.Method, req.URL)
+
+ // send origin headers
+ if origin := req.Header.Get("Origin"); origin != "" {
+ res.Header().Set("Access-Control-Allow-Origin", origin)
+ res.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
+ res.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, If-Modified-Since, SOAPAction")
+ }
+
+ // stop if its options
+ if req.Method == "OPTIONS" {
+ return
+ }
+
+ // bail if not POST
+ if req.Method != "POST" {
+ http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
+ return
+ }
+
+ // grab posted body
+ body, err := ioutil.ReadAll(req.Body)
+ if err != nil {
+ http.Error(res, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+ defer req.Body.Close()
+
+ // parse xml with gokogiri
+ doc, err := gokogiri.ParseXml(body)
+ if err != nil {
+ http.Error(res, "cannot load XML", http.StatusBadRequest)
+ return
+ }
+ defer doc.Free()
+
+ // load print nodes from xml doc
+ nodes, err := getBodyChildren(doc)
+ if err != nil {
+ http.Error(res, "cannot find SOAP request Body", http.StatusBadRequest)
+ return
+ }
+
+ // init printer
+ s.p.Init()
+
+ // loop over nodes
+ for _, n := range nodes {
+ // grab parameters
+ params := make(map[string]string)
+ for _, attr := range n.Attributes() {
+ params[attr.Name()] = attr.Value()
+ }
+
+ // write data to printer
+ s.p.WriteNode(n.Name(), params, n.Content())
+ }
+
+ // end
+ s.p.End()
+
+ // flush writer
+ s.w.Flush()
+
+ // write soap response
+ res.Header().Set("Content-Type", req.Header.Get("Content-Type"))
+ fmt.Fprintf(res, soapBody, true, "")
+}
+
+const (
+ // soapBody is a basic SOAP response body for an ePOS server response.
+ soapBody = `
+
+
+
+
+`
+)
diff --git a/util.go b/util.go
new file mode 100644
index 0000000..4980d0e
--- /dev/null
+++ b/util.go
@@ -0,0 +1,33 @@
+package escpos
+
+import (
+ "errors"
+
+ "github.com/moovweb/gokogiri/xml"
+ "github.com/moovweb/gokogiri/xpath"
+)
+
+var (
+ // ErrBodyElementEmpty is the body element empty error.
+ ErrBodyElementEmpty = errors.New("Body element empty")
+
+ // bodyPath is the xpath selector for the
+ bodyPath = xpath.Compile("*[local-name()='Body']")
+)
+
+// getBodyChildren returns the child nodes contained in the Body element in a XML document.
+func getBodyChildren(doc *xml.XmlDocument) ([]xml.Node, error) {
+ // grab nodes
+ nodes, err := doc.Root().Search(bodyPath)
+ if err != nil {
+ return nil, err
+ }
+
+ // check that the data is present
+ if len(nodes) < 1 || nodes[0].CountChildren() < 1 {
+ return nil, ErrBodyElementEmpty
+ }
+
+ // get body children
+ return nodes[0].FirstChild().Search("./*")
+}