commit 17ab83fb715d7907b811ce1f77f60ca7fcfb4c93 Author: Emilio Mariscal Date: Tue Sep 6 16:15:55 2016 -0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2eeb1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +ENV +*.DS_Store +*.pyc diff --git a/OpenSans-Bold.ttf b/OpenSans-Bold.ttf new file mode 100755 index 0000000..fd79d43 Binary files /dev/null and b/OpenSans-Bold.ttf differ diff --git a/OpenSans-Regular.ttf b/OpenSans-Regular.ttf new file mode 100755 index 0000000..db43334 Binary files /dev/null and b/OpenSans-Regular.ttf differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..87bf2c0 --- /dev/null +++ b/README.md @@ -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 diff --git a/image.py b/image.py new file mode 100644 index 0000000..5a6ad2f --- /dev/null +++ b/image.py @@ -0,0 +1,88 @@ +""" Image format handling class +This module contains the image format handler :py:class:`EscposImage`. +:author: `Michael Billington `_ +:organization: `python-escpos `_ +:copyright: Copyright (c) 2016 Michael Billington +: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() + + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..960ce36 Binary files /dev/null and b/logo.png differ diff --git a/page.png b/page.png new file mode 100644 index 0000000..9616c82 Binary files /dev/null and b/page.png differ diff --git a/reqs.txt b/reqs.txt new file mode 100644 index 0000000..1e9e355 --- /dev/null +++ b/reqs.txt @@ -0,0 +1,4 @@ +Pillow==3.3.0 +pyserial==3.1.1 +six==1.10.0 +wheel==0.24.0 diff --git a/server.py b/server.py new file mode 100644 index 0000000..70a3112 --- /dev/null +++ b/server.py @@ -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() + + + + +