1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-09-13 09:09:58 +00:00

31 Commits

Author SHA1 Message Date
Patrick Kanzler
cbe38648f5 Merge pull request #169 from python-escpos/development
v2.2.0
2016-08-26 15:53:50 +02:00
Patrick Kanzler
d43bcd187e Merge branch 'master' into development 2016-08-26 14:46:23 +02:00
Patrick Kanzler
6b069a4529 update CHANGELOG 2016-08-26 14:27:17 +02:00
Patrick Kanzler
cf41069829 Merge branch 'linespacing' of git://github.com/miracle2k/python-escpos into miracle2k-linespacing
Conflicts:
	src/escpos/constants.py
2016-08-26 14:16:06 +02:00
Patrick Kanzler
bde6eaa336 Merge pull request #164 from miracle2k/constants
Constants
2016-08-26 12:25:24 +02:00
Michael Elsdörfer
632a104219 Fix docstring warning. 2016-08-26 11:59:40 +02:00
Michael Elsdörfer
ae9b3785c2 Fix broken tests. 2016-08-26 11:48:58 +02:00
Michael Elsdörfer
07d47765aa Allow linespacing reset. Make this the default. 2016-08-26 10:38:36 +02:00
Michael Elsdörfer
854b75be30 Support changing the line spacing. 2016-08-26 10:34:52 +02:00
Michael Elsdörfer
df0c874f6e Add constants for sheet mode, colors. 2016-08-26 10:30:29 +02:00
Michael Elsdörfer
5c3d7dab72 Change setup.py shebang.
This supports using the current virtualenv.
2016-08-26 10:30:29 +02:00
Patrick Kanzler
798893caee refactor access of private member _img of qrcode
Since version 2.5 python-qrcode allows the direct access of the
PIL-functions. (We require version 4 and above).
Thus, we can simply call qr_img.convert() without accessing the private
member.
This refactoring is identical in functionality.
2016-08-15 23:24:06 +02:00
Patrick Kanzler
e8d91a6735 test add type-check for the qr-printing 2016-08-15 23:23:07 +02:00
Patrick Kanzler
996b3fd332 DOC fix demo-code in README.rst
fixes #159
2016-08-11 11:25:40 +02:00
Patrick Kanzler
a38c124bb1 Merge branch 'master' of github.com:python-escpos/python-escpos 2016-08-10 01:31:56 +02:00
Patrick Kanzler
e44d89bd33 DOC update changelog for v2.1.3 2016-08-10 01:30:19 +02:00
Patrick Kanzler
7312db4adb Merge pull request #158 from python-escpos/development
v2.1.3
2016-08-10 01:23:47 +02:00
Patrick Kanzler
59dccd79da test add test for image-splitting-method 2016-08-07 14:39:58 +02:00
Patrick Kanzler
603b34cadb test add test for the fragment-splitting 2016-08-07 13:49:46 +02:00
Patrick Kanzler
340a47d2f6 Merge pull request #152 from python-escpos/fix/large-image-printing
Fix/large image printing
2016-08-07 12:22:25 +02:00
Patrick Kanzler
dfe2cdbff8 configure readthedocs with yml 2016-08-02 18:39:56 +02:00
Patrick Kanzler
eea2a6f9c0 travis: configure email-notifications 2016-08-02 16:41:47 +02:00
Patrick Kanzler
11452034a3 Merge pull request #157 from python-escpos/development
v2.1.2
2016-08-02 16:32:19 +02:00
Patrick Kanzler
d2e2ea88a6 doc write changelog for version v2.1.2 2016-08-02 16:07:05 +02:00
Patrick Kanzler
2416303805 Merge pull request #156 from python-escpos/fix/code128-printing
fix printing of CODE128
2016-08-02 15:57:52 +02:00
Patrick Kanzler
38f9835931 fix printing of CODE128
The control sequence {A or {B or {C can't be part of the qr code.
For this the user has to supply this sequence.
2016-08-02 15:39:31 +02:00
Patrick Kanzler
7c732ee615 doc fix lists 2016-08-02 04:38:04 +02:00
Patrick Kanzler
3d98eb8b9c fix file-printer did not flush
The file-printer did not automatically flush and thus behaved
differently to the other printer-classes.
Now the default behaviour is to flush after every call of _raw(). This
can be disabled by calling the file-printer with auto_flush=False.

fixes #106
2016-08-02 04:25:54 +02:00
Patrick Kanzler
619d80a867 doc add changelog-stump for post-2.1.1 release 2016-08-02 00:45:36 +02:00
Patrick Kanzler
2ecf73074c improve large image printing
images longer than 1024 pixels will be split into multiple fragments.
2016-08-02 00:04:43 +02:00
Patrick Kanzler
10977b06e7 doc add hint on image preprocessing 2016-08-01 14:02:49 +02:00
19 changed files with 377 additions and 15 deletions

View File

@@ -34,3 +34,7 @@ before_install:
script:
- tox
- codecov
notifications:
email:
on_success: never
on_failure: change

View File

@@ -1,6 +1,46 @@
*********
Changelog
*********
2016-08-26 - Version 2.2.0 - "Fate Amenable To Change"
------------------------------------------------------
changes
^^^^^^^
- fix improper API-use in qrcode()
- change setup.py shebang to make it compatible with virtualenvs.
- add constants for sheet mode and colors
- support changing the linespacing
contributors
^^^^^^^^^^^^
- Michael Elsdörfer
- Patrick Kanzler
2016-08-10 - Version 2.1.3 - "Ethics Gradient"
----------------------------------------------
changes
^^^^^^^
- configure readthedocs and travis
- update doc with hint on image preprocessing
- add fix for printing large images (by splitting them into multiple images)
contributors
^^^^^^^^^^^^
- Patrick Kanzler
2016-08-02 - Version 2.1.2 - "Death and Gravity"
------------------------------------------------
changes
^^^^^^^
- fix File-printer: flush after every call of _raw()
- fix lists in documentation
- fix CODE128: by adding the control character to the barcode-selection-sequence the barcode became unusable
contributors
^^^^^^^^^^^^
- Patrick Kanzler
2016-08-02 - Version 2.1.1 - "Contents May Differ"
--------------------------------------------------

View File

@@ -54,14 +54,14 @@ The basic usage is:
.. code:: python
from escpos import *
from escpos.printer import Usb
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
Epson = escpos.Escpos(0x04b8,0x0202,0)
Epson.text("Hello World\n")
Epson.image("logo.gif")
Epson.barcode('1324354657687','EAN13',64,2,'','')
Epson.cut()
p = Usb(0x04b8,0x0202,0)
p.text("Hello World\n")
p.image("logo.gif")
p.barcode('1324354657687','EAN13',64,2,'','')
p.cut()
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.

View File

@@ -18,6 +18,7 @@ Content
user/raspi
user/todo
user/usage
user/barcode
.. toctree::
:maxdepth: 1

34
doc/user/barcode.rst Normal file
View File

@@ -0,0 +1,34 @@
Printing Barcodes
-----------------
:Last Reviewed: 2016-07-31
Most ESC/POS-printers implement barcode-printing.
The barcode-commandset is implemented in the barcode-method.
For a list of compatible barcodes you should check the manual of your printer.
As a rule of thumb: even older Epson-models support most 1D-barcodes.
To be sure just try some implementations and have a look at the notices below.
barcode-method
~~~~~~~~~~~~~~
The barcode-method is rather low-level and orients itself on the implementation of ESC/POS.
In the future this class could be supplemented by a high-level class that helps the user generating the payload.
.. py:currentmodule:: escpos.escpos
.. automethod:: Escpos.barcode
:noindex:
CODE128
~~~~~~~
Code128 barcodes need a certain format.
For now the user has to make sure that the payload is correct.
For alphanumeric CODE128 you have to preface your payload with `{B`.
.. code-block:: Python
from escpos.printer import Dummy, Serial
p = Serial()
# print CODE128 012ABCDabcd
p.barcode("{B012ABCDabcd", "CODE128", function_type="B")
A very good description on CODE128 is also on `Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.

View File

@@ -88,6 +88,7 @@ set("align", "font", "type", width, height, invert, smooth, flip)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Set text properties.
* ``align`` set horizontal position for text, the possible values are:
* CENTER
@@ -106,6 +107,7 @@ cut("mode")
^^^^^^^^^^^
Cut paper.
* ``mode`` set a full or partial cut. *Default:* full
**Partial cut is not implemented in all printers.**
@@ -132,6 +134,7 @@ control("align")
^^^^^^^^^^^^^^^^
Carrier feed and tabs.
* ``align`` is a string which takes any of the following values:
* LF *for Line Feed*

View File

@@ -32,6 +32,7 @@ Network("host", port)
^^^^^^^^^^^^^^^^^^^^^
Based on socket
* ``host`` is an alphanumeric host name, could be either DNS host name or IP address.
* ``port`` to write to (default = 9100)
@@ -40,7 +41,8 @@ Problems with a network-attached printer can have numerous causes. Make sure tha
Often you can check the IP address by triggering the self-test of the device. As a next step try to send text
manually to the device. You could use for example:
::
.. ::
echo "OK\n" | nc IPADDRESS 9100
# the port number is often 9100
@@ -50,4 +52,5 @@ File("file\_name")
^^^^^^^^^^^^^^^^^^
Printcap printers
* ``file_name`` is the full path to the device file name

View File

@@ -204,6 +204,37 @@ Here you can download an example, that will print a set of common barcodes:
* :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_
Hint: preprocess printing
-------------------------
Printing images directly to the printer is rather slow.
One factor that slows down the process is the transmission over e.g. serial port.
Apart from configuring your printer to use the maximum baudrate (in the case of serial-printers), there is not much
that you can do.
However you could use the :py:class:`escpos.printer.Dummy`-printer to preprocess your image.
This is probably best explained by an example:
.. code-block:: Python
from escpos.printer import Serial, Dummy
p = Serial()
d = Dummy()
# create ESC/POS for the print job, this should go really fast
d.text("This is my image:\n")
d.image("funny_cat.png")
d.cut()
# send code to printer
p._raw(d.output)
This way you could also store the code in a file and print later.
You could then for example print the code from another process than your main-program and thus reduce the waiting time.
(Of course this will not make the printer print faster.)
How to update your code for USB printers
----------------------------------------

7
readthedocs.yml Normal file
View File

@@ -0,0 +1,7 @@
formats:
- pdf
- epub
requirements_file: doc/requirements.txt
python:
version: 2
setup_py_install: true

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python
import os
import sys

View File

@@ -55,11 +55,18 @@ _CUT_PAPER = lambda m: GS + b'V' + m
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
# Beep
BEEP = b'\x07'
# Panel buttons (e.g. the FEED button)
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
# Sheet modes
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
# Text format
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
# Printers" and tidy up this stuff too.
@@ -101,6 +108,18 @@ TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification
TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON
TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF
# Text colors
TXT_COLOR_BLACK = ESC + b'\x72\x00' # Default Color
TXT_COLOR_RED = ESC + b'\x72\x01' # Alternative Color (Usually Red)
# Spacing
LINESPACING_RESET = ESC + b'2'
LINESPACING_FUNCS = {
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255
}
# Char code table
CHARCODE_PC437 = ESC + b'\x74\x00' # USA: Standard Europe
CHARCODE_JIS = ESC + b'\x74\x01' # Japanese Katakana
@@ -168,10 +187,7 @@ BARCODE_TYPE_B = {
'NW7': _SET_BARCODE_TYPE(71),
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
'CODE93': _SET_BARCODE_TYPE(72),
# These are all the same barcode, but using different charcter sets
'CODE128A': _SET_BARCODE_TYPE(73) + b'{A', # CODE128 character set A
'CODE128B': _SET_BARCODE_TYPE(73) + b'{B', # CODE128 character set B
'CODE128C': _SET_BARCODE_TYPE(73) + b'{C', # CODE128 character set C
'CODE128': _SET_BARCODE_TYPE(73),
'GS1-128': _SET_BARCODE_TYPE(74),
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),

View File

@@ -56,7 +56,8 @@ class Escpos(object):
"""
pass
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster"):
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
fragment_height=1024):
""" Print an image
You can select whether the printer should print in high density or not. The default value is high density.
@@ -76,10 +77,21 @@ class Escpos(object):
:param high_density_vertical: print in high density in vertical direction *default:* True
:param high_density_horizontal: print in high density in horizontal direction *default:* True
:param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn`
:param fragment_height: Images larger than this will be split into multiple fragments *default:* 1024
"""
im = EscposImage(img_source)
if im.height > fragment_height:
fragments = im.split(fragment_height)
for fragment in fragments:
self.image(fragment,
high_density_vertical=high_density_vertical,
high_density_horizontal=high_density_horizontal,
impl=impl,
fragment_height=fragment_height)
return
if impl == "bitImageRaster":
# GS v 0, raster format bit image
density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2)
@@ -553,6 +565,35 @@ class Escpos(object):
else:
self._raw(TXT_INVERT_OFF)
def line_spacing(self, spacing=None, divisor=180):
""" Set line character spacing.
If no spacing is given, we reset it to the default.
There are different commands for setting the line spacing, using
a different denominator:
'+'' line_spacing/360 of an inch, 0 <= line_spacing <= 255
'3' line_spacing/180 of an inch, 0 <= line_spacing <= 255
'A' line_spacing/60 of an inch, 0 <= line_spacing <= 85
Some printers may not support all of them. The most commonly
available command (using a divisor of 180) is chosen.
"""
if spacing is None:
self._raw(LINESPACING_RESET)
return
if divisor not in LINESPACING_FUNCS:
raise ValueError("divisor must be either 360, 180 or 60")
if (divisor in [360, 180] \
and (not(0 <= spacing <= 255))):
raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180")
if divisor == 60 and (not(0 <= spacing <= 85)):
raise ValueError("spacing must be a int between 0 and 85 when divisor is 60")
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
def cut(self, mode=''):
""" Cut paper.

View File

@@ -8,6 +8,12 @@ This module contains the image format handler :py:class:`EscposImage`.
:license: GNU GPL v3
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import math
from PIL import Image, ImageOps
@@ -30,6 +36,9 @@ class EscposImage(object):
else:
img_original = Image.open(img_source)
# store image for eventual further processing (splitting)
self.img_original = img_original
# Convert to white RGB background, paste over white background
# to strip alpha.
img_original = img_original.convert('RGBA')
@@ -88,3 +97,21 @@ class EscposImage(object):
Convert image to raster-format binary
"""
return self._im.tobytes()
def split(self, fragment_height):
"""
Split an image into multiple fragments after fragment_height pixels
:param fragment_height: height of fragment
:return: list of PIL objects
"""
passes = int(math.ceil(self.height/fragment_height))
fragments = []
for n in range(0, passes):
left = 0
right = self.width
upper = n * fragment_height
lower = min((n + 1) * fragment_height, self.height)
box = (left, upper, right, lower)
fragments.append(self.img_original.crop(box))
return fragments

View File

@@ -229,13 +229,15 @@ class File(Escpos):
"""
def __init__(self, devfile="/dev/usb/lp0", *args, **kwargs):
def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
"""
:param devfile : Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw()
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.auto_flush = auto_flush
self.open()
def open(self):
@@ -256,6 +258,8 @@ class File(Escpos):
:type msg: bytes
"""
self.device.write(msg)
if self.auto_flush:
self.flush()
def close(self):
""" Close system file """

View File

@@ -130,3 +130,12 @@ def test_graphics_transparent():
instance = printer.Dummy()
instance.image('test/resources/black_transparent.png', impl="graphics")
assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002')
def test_large_graphics():
"""
Test whether 'large' graphics that induce a fragmentation are handled correctly.
"""
instance = printer.Dummy()
instance.image('test/resources/black_white.png', impl="bitImageRaster", fragment_height=1)
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00')

View File

@@ -0,0 +1,32 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""tests for the non-native part of qr()
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: GNU GPL v3
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import mock
from escpos.printer import Dummy
from PIL import Image
@mock.patch('escpos.printer.Dummy.image', spec=Dummy)
def test_type_of_object_passed_to_image_function(img_function):
"""
Test the type of object that is passed to the image function during non-native qr-printing.
The type should be PIL.Image
"""
d = Dummy()
d.qr("LoremIpsum")
args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image)

26
test/test_functions.py Normal file
View File

@@ -0,0 +1,26 @@
from nose.tools import assert_raises
from escpos.printer import Dummy
def test_line_spacing_code_gen():
printer = Dummy()
printer.line_spacing(10)
assert printer.output == b'\x1b3\n'
def test_line_spacing_rest():
printer = Dummy()
printer.line_spacing()
assert printer.output == b'\x1b2'
def test_line_spacing_error_handling():
printer = Dummy()
with assert_raises(ValueError):
printer.line_spacing(99, divisor=44)
with assert_raises(ValueError):
printer.line_spacing(divisor=80, spacing=86)
with assert_raises(ValueError):
printer.line_spacing(divisor=360, spacing=256)
with assert_raises(ValueError):
printer.line_spacing(divisor=180, spacing=256)

View File

@@ -43,6 +43,20 @@ def test_image_white():
_load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00'])
def test_split():
"""
test whether the split-function works as expected
"""
im = EscposImage('test/resources/black_white.png')
(upper_part, lower_part) = im.split(1)
upper_part = EscposImage(upper_part)
lower_part = EscposImage(lower_part)
assert(upper_part.width == lower_part.width == 2)
assert(upper_part.height == lower_part.height == 1)
assert(upper_part.to_raster_format() == b'\xc0')
assert(lower_part.to_raster_format() == b'\x00')
def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected):
"""
Load an image, and test whether raster & column formatted output, sizes, etc match expectations.

70
test/test_printer_file.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""tests for the File printer
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: GNU GPL v3
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six
import mock
from hypothesis import given
from hypothesis.strategies import text
import escpos.printer as printer
if six.PY3:
mock_open_call = 'builtins.open'
else:
mock_open_call = '__builtin__.open'
@given(path=text())
@mock.patch(mock_open_call)
@mock.patch('escpos.escpos.Escpos.__init__')
def test_load_file_printer(mock_escpos, mock_open, path):
"""test the loading of the file-printer"""
printer.File(devfile=path)
assert mock_escpos.called
mock_open.assert_called_with(path, "wb")
@given(txt=text())
@mock.patch.object(printer.File, 'device')
@mock.patch(mock_open_call)
@mock.patch('escpos.escpos.Escpos.__init__')
def test_auto_flush(mock_escpos, mock_open, mock_device, txt):
"""test auto_flush in file-printer"""
p = printer.File(auto_flush=False)
# inject the mocked device-object
p.device = mock_device
p._raw(txt)
assert not mock_device.flush.called
mock_device.reset_mock()
p = printer.File(auto_flush=True)
# inject the mocked device-object
p.device = mock_device
p._raw(txt)
assert mock_device.flush.called
@given(txt=text())
@mock.patch.object(printer.File, 'device')
@mock.patch(mock_open_call)
def test_flush_on_close(mock_open, mock_device, txt):
"""test flush on close in file-printer"""
p = printer.File(auto_flush=False)
# inject the mocked device-object
p.device = mock_device
p._raw(txt)
assert not mock_device.flush.called
p.close()
assert mock_device.flush.called
assert mock_device.close.called