mirror of
https://github.com/emi420/esc-pos-server-print.git
synced 2025-12-18 00:49:21 +00:00
Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ENV
|
||||||
|
*.DS_Store
|
||||||
|
*.pyc
|
||||||
BIN
OpenSans-Bold.ttf
Executable file
BIN
OpenSans-Bold.ttf
Executable file
Binary file not shown.
BIN
OpenSans-Regular.ttf
Executable file
BIN
OpenSans-Regular.ttf
Executable file
Binary file not shown.
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# ESC-POS Server Print
|
||||||
|
|
||||||
|
## Simple web server for print using ESC-POS.
|
||||||
|
|
||||||
|
## Install and run
|
||||||
|
|
||||||
|
* Install pip requirements
|
||||||
|
* Run 'python server.py'
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
You may use any Mootor project under the terms of either the MIT License or the GNU General Public License (GPL) Version 3.
|
||||||
|
|
||||||
|
(c) 2016 Emilio Mariscal
|
||||||
88
image.py
Normal file
88
image.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
""" Image format handling class
|
||||||
|
This module contains the image format handler :py:class:`EscposImage`.
|
||||||
|
:author: `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 Michael Billington <michael.billington@gmail.com>
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
|
||||||
|
class EscposImage(object):
|
||||||
|
"""
|
||||||
|
Load images in, and output ESC/POS formats.
|
||||||
|
The class is designed to efficiently delegate image processing to
|
||||||
|
PIL, rather than spend CPU cycles looping over pixels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, img_source):
|
||||||
|
"""
|
||||||
|
Load in an image
|
||||||
|
|
||||||
|
:param img_source: PIL.Image, or filename to load one from.
|
||||||
|
"""
|
||||||
|
if isinstance(img_source, Image.Image):
|
||||||
|
img_original = img_source
|
||||||
|
else:
|
||||||
|
img_original = Image.open(img_source)
|
||||||
|
|
||||||
|
# Convert to white RGB background, paste over white background
|
||||||
|
# to strip alpha.
|
||||||
|
img_original = img_original.convert('RGBA')
|
||||||
|
im = Image.new("RGB", img_original.size, (255, 255, 255))
|
||||||
|
im.paste(img_original, mask=img_original.split()[3])
|
||||||
|
# Convert down to greyscale
|
||||||
|
im = im.convert("L")
|
||||||
|
# Invert: Only works on 'L' images
|
||||||
|
im = ImageOps.invert(im)
|
||||||
|
# Pure black and white
|
||||||
|
self._im = im.convert("1")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
"""
|
||||||
|
Width of image in pixels
|
||||||
|
"""
|
||||||
|
width_pixels, _ = self._im.size
|
||||||
|
return width_pixels
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width_bytes(self):
|
||||||
|
"""
|
||||||
|
Width of image if you use 8 pixels per byte and 0-pad at the end.
|
||||||
|
"""
|
||||||
|
return (self.width + 7) >> 3
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
"""
|
||||||
|
Height of image in pixels
|
||||||
|
"""
|
||||||
|
_, height_pixels = self._im.size
|
||||||
|
return height_pixels
|
||||||
|
|
||||||
|
def to_column_format(self, high_density_vertical=True):
|
||||||
|
"""
|
||||||
|
Extract slices of an image as equal-sized blobs of column-format data.
|
||||||
|
:param high_density_vertical: Printed line height in dots
|
||||||
|
"""
|
||||||
|
im = self._im.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
|
line_height = 24 if high_density_vertical else 8
|
||||||
|
width_pixels, height_pixels = im.size
|
||||||
|
top = 0
|
||||||
|
left = 0
|
||||||
|
while left < width_pixels:
|
||||||
|
box = (left, top, left + line_height, top + height_pixels)
|
||||||
|
im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box)
|
||||||
|
im_bytes = im_slice.tobytes()
|
||||||
|
yield(im_bytes)
|
||||||
|
left += line_height
|
||||||
|
|
||||||
|
def to_raster_format(self):
|
||||||
|
"""
|
||||||
|
Convert image to raster-format binary
|
||||||
|
"""
|
||||||
|
return self._im.tobytes()
|
||||||
|
|
||||||
|
|
||||||
4
reqs.txt
Normal file
4
reqs.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Pillow==3.3.0
|
||||||
|
pyserial==3.1.1
|
||||||
|
six==1.10.0
|
||||||
|
wheel==0.24.0
|
||||||
176
server.py
Normal file
176
server.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
ESC-POS Server Print - a web service for print using ESC-POS.
|
||||||
|
|
||||||
|
You may use any Server Print project under the terms
|
||||||
|
of the GNU General Public License (GPL) Version 3.
|
||||||
|
|
||||||
|
(c) 2016 Emilio Mariscal (emi420 [at] gmail.com)
|
||||||
|
|
||||||
|
Module description:
|
||||||
|
|
||||||
|
Server Print
|
||||||
|
|
||||||
|
Simple web server for print using ESC-POS.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
import os
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
from urlparse import urlparse, parse_qs
|
||||||
|
import textwrap
|
||||||
|
import serial
|
||||||
|
import time
|
||||||
|
import image
|
||||||
|
import six
|
||||||
|
|
||||||
|
# Set to false if you want to test the image without printing
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
PORT = 8001
|
||||||
|
|
||||||
|
SERIAL = '/dev/ttyUSB0'
|
||||||
|
SPEED = 38400
|
||||||
|
DENSITY = 3
|
||||||
|
|
||||||
|
H1_FONT = ImageFont.truetype("OpenSans-Bold.ttf", 70)
|
||||||
|
H2_FONT = ImageFont.truetype("OpenSans-Regular.ttf", 30)
|
||||||
|
P_FONT = ImageFont.truetype("OpenSans-Regular.ttf", 23)
|
||||||
|
TMP_FILE = "page.png"
|
||||||
|
LOGO_FILE = "logo.png"
|
||||||
|
W = 300
|
||||||
|
GS = b'\x1d'
|
||||||
|
|
||||||
|
|
||||||
|
def _int_low_high(inp_number, out_bytes):
|
||||||
|
"""
|
||||||
|
Generate multiple bytes for a number: In lower and higher parts, or more parts as needed.
|
||||||
|
Function from python-escpos library (https://github.com/python-escpos/python-escpos)
|
||||||
|
|
||||||
|
:param inp_number: Input number
|
||||||
|
:param out_bytes: The number of bytes to output (1 - 4).
|
||||||
|
"""
|
||||||
|
max_input = (256 << (out_bytes * 8) - 1)
|
||||||
|
if not 1 <= out_bytes <= 4:
|
||||||
|
raise ValueError("Can only output 1-4 bytes")
|
||||||
|
if not 0 <= inp_number <= max_input:
|
||||||
|
raise ValueError("Number too large. Can only output up to {0} in {1} bytes".format(max_input, out_bytes))
|
||||||
|
outp = b''
|
||||||
|
for _ in range(0, out_bytes):
|
||||||
|
outp += six.int2byte(inp_number % 256)
|
||||||
|
inp_number //= 256
|
||||||
|
return outp
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
APIServer create a simple web server to generate and print images
|
||||||
|
'''
|
||||||
|
class APIServer(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
|
||||||
|
mime = "text/html"
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", mime)
|
||||||
|
self.send_header('Allow', 'GET, OPTIONS')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
height = 150
|
||||||
|
|
||||||
|
imgtmp = Image.new("RGBA", (W,100), (255,255,255))
|
||||||
|
drawtmp = ImageDraw.Draw(imgtmp)
|
||||||
|
|
||||||
|
params = parse_qs(urlparse(self.path).query)
|
||||||
|
|
||||||
|
if params:
|
||||||
|
|
||||||
|
h1 = unicode(params.get("h1")[0],"utf8")
|
||||||
|
h2 = unicode(params.get("h2")[0],"utf8")
|
||||||
|
p = unicode(params.get("p")[0],"utf8")
|
||||||
|
|
||||||
|
linesh2 = textwrap.wrap(h2, width=20)
|
||||||
|
lines = textwrap.wrap(p, width=20)
|
||||||
|
|
||||||
|
imglogo = Image.open(LOGO_FILE, 'r')
|
||||||
|
img_w, img_h = imglogo.size
|
||||||
|
|
||||||
|
wh1, hh1 = drawtmp.textsize(h1, font=H1_FONT)
|
||||||
|
wh2, hh2 = drawtmp.textsize(h2, font=H2_FONT)
|
||||||
|
|
||||||
|
height += img_h + hh1 + hh2
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
w, h = drawtmp.textsize(line, font=P_FONT)
|
||||||
|
height += h
|
||||||
|
|
||||||
|
for line in linesh2:
|
||||||
|
w, h = drawtmp.textsize(line, font=H2_FONT)
|
||||||
|
height += h
|
||||||
|
|
||||||
|
del drawtmp
|
||||||
|
|
||||||
|
img = Image.new("RGBA", (W,height), (255,255,255))
|
||||||
|
|
||||||
|
img.paste(imglogo, (((W - img_w) / 2),30))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
draw.text(((W-wh1)/2, img_h + 50), h1, (0,0,0), font=H1_FONT)
|
||||||
|
|
||||||
|
y_text = img_h + hh1 + 80
|
||||||
|
|
||||||
|
for line in linesh2:
|
||||||
|
w, h = draw.textsize(line, font=H2_FONT)
|
||||||
|
draw.text(((W-w)/2, y_text), line, (0,0,0), font=H2_FONT)
|
||||||
|
y_text += h
|
||||||
|
|
||||||
|
y_text += 25
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
w, h = draw.textsize(line, font=P_FONT)
|
||||||
|
draw.text(((W-w)/2, y_text), line, (0,0,0), font=P_FONT)
|
||||||
|
y_text += h
|
||||||
|
|
||||||
|
del draw
|
||||||
|
|
||||||
|
img.save(TMP_FILE, "PNG")
|
||||||
|
|
||||||
|
if not DEBUG:
|
||||||
|
conn = serial.Serial(SERIAL, SPEED, timeout=10)
|
||||||
|
im = image.EscposImage(TMP_FILE)
|
||||||
|
out = im.to_raster_format()
|
||||||
|
header = GS + b"v0" + six.int2byte(DENSITY) + _int_low_high(im.width_bytes, 2) + _int_low_high(im.height, 2)
|
||||||
|
conn.write(header + out)
|
||||||
|
conn.write("\x0a\x0a\x0a\x1d\x56\x00\x0a\x0a")
|
||||||
|
time.sleep(1)
|
||||||
|
conn.close()
|
||||||
|
printer_returns = 1
|
||||||
|
else:
|
||||||
|
printer_returns = 0
|
||||||
|
|
||||||
|
self.wfile.write(printer_returns)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
try:
|
||||||
|
server = HTTPServer(('', PORT), APIServer)
|
||||||
|
print 'Started httpserver on port ' + str(PORT)
|
||||||
|
server.serve_forever()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print '^C received, shutting down server'
|
||||||
|
server.socket.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user