From b3b8606f230f88b4e1f71724a63ccc2d22976300 Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Sat, 13 May 2017 18:42:05 +0200 Subject: [PATCH 01/33] Fixed bad format of :code: in documentation --- src/escpos/escpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index c238259..3faba40 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -414,7 +414,7 @@ class Escpos(object): Text has to be encoded in unicode. :param txt: text to be printed - :param font: font to be used, can be :code:`a` or :code`b` + :param font: font to be used, can be :code:`a` or :code:`b` :param columns: amount of columns :return: None """ From e16d1584018ddae53e2c2a9d2bf8b747a509001f Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Tue, 16 May 2017 20:55:30 +0200 Subject: [PATCH 02/33] First implementation of software barcode Actually the hardware barcode implementation is very specific and not generic enough for just adding a `soft_render=True` argument to it. This is a first work that can be improved with other commits, maybe for merging this method in the `barcode` method after some cleanup. The width, height and text_distance were set using empiric print-and-retry tests so that the generated barcode looks nice to the eye (and to the eye of an Android scanner tool. !WARNING! Printing a barcode that is too large in width will result in the printer to go crazy trying to print an image that is too large for it. This may be fixed by raising an exception in the `image` method. --- examples/software_barcode.py | 8 ++++++++ setup.py | 3 ++- src/escpos/escpos.py | 23 +++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 examples/software_barcode.py diff --git a/examples/software_barcode.py b/examples/software_barcode.py new file mode 100644 index 0000000..8476bac --- /dev/null +++ b/examples/software_barcode.py @@ -0,0 +1,8 @@ +from escpos.printer import Usb + +# Adapt to your needs +p = Usb(0x0416, 0x5011, profile="POS-5890") + +# Some software barcodes +p.soft_barcode('code128', 'Hello') +p.soft_barcode('code39', '123456') \ No newline at end of file diff --git a/setup.py b/setup.py index c5d77f6..bd4a3ed 100755 --- a/setup.py +++ b/setup.py @@ -115,7 +115,8 @@ setup( 'pyyaml', 'argparse', 'argcomplete', - 'future' + 'future', + 'pyBarcode==0.8b1' ], setup_requires=[ 'setuptools_scm', diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 3faba40..d23bb05 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -19,6 +19,9 @@ import qrcode import textwrap import six +import barcode +from barcode.writer import ImageWriter + from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH from .constants import TXT_ALIGN_CT, TXT_ALIGN_LT, TXT_ALIGN_RT, BARCODE_FONT_A, BARCODE_FONT_B @@ -396,6 +399,26 @@ class Escpos(object): if function_type.upper() == "A": self._raw(NUL) + def soft_barcode(self, barcode_type, data, module_height=5, module_width=0.2, text_distance=1): + image_writer = ImageWriter() + + if barcode_type not in barcode.PROVIDED_BARCODES: + raise BarcodeTypeError( + 'Barcode type {} not supported by software barcode renderer' + .format(barcode_type)) + + barcode_class = barcode.get_barcode_class(barcode_type) + my_code = barcode_class(data, writer=image_writer) + + my_code.write("/dev/null", { + 'module_height': module_height, + 'module_width': module_width, + 'text_distance': text_distance + }) + + image = my_code.writer._image + self.image(image, impl='bitImageColumn') + def text(self, txt): """ Print alpha-numeric text From 0f6e799bf74e4240036034631745119575d6f267 Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Sun, 14 May 2017 10:38:21 +0200 Subject: [PATCH 03/33] Refactor of the set method, with tests --- src/escpos/constants.py | 124 ++++++++++------- src/escpos/escpos.py | 120 +++++----------- test/test_function_set.py | 280 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 387 insertions(+), 137 deletions(-) create mode 100644 test/test_function_set.py diff --git a/src/escpos/constants.py b/src/escpos/constants.py index e3225f8..821c143 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -71,51 +71,90 @@ 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. -TXT_FLIP_ON = ESC + b'\x7b\x01' -TXT_FLIP_OFF = ESC + b'\x7b\x00' -TXT_SMOOTH_ON = GS + b'\x62\x01' -TXT_SMOOTH_OFF = GS + b'\x62\x00' TXT_SIZE = GS + b'!' -TXT_WIDTH = {1: 0x00, - 2: 0x10, - 3: 0x20, - 4: 0x30, - 5: 0x40, - 6: 0x50, - 7: 0x60, - 8: 0x70} -TXT_HEIGHT = {1: 0x00, - 2: 0x01, - 3: 0x02, - 4: 0x03, - 5: 0x04, - 6: 0x05, - 7: 0x06, - 8: 0x07} + TXT_NORMAL = ESC + b'!\x00' # Normal text -TXT_2HEIGHT = ESC + b'!\x10' # Double height text -TXT_2WIDTH = ESC + b'!\x20' # Double width text -TXT_4SQUARE = ESC + b'!\x30' # Quad area text -TXT_UNDERL_OFF = ESC + b'\x2d\x00' # Underline font OFF -TXT_UNDERL_ON = ESC + b'\x2d\x01' # Underline font 1-dot ON -TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON -TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF -TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON -TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification -TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering -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 + + +TXT_STYLE = { + 'bold': { + False: ESC + b'\x45\x00', # Bold font OFF + True: ESC + b'\x45\x01' # Bold font ON + }, + 'underline': { + 0: ESC + b'\x2d\x00', # Underline font OFF + 1: ESC + b'\x2d\x01', # Underline font 1-dot ON + 2: ESC + b'\x2d\x02' # Underline font 2-dot ON + }, + 'size': { + 'normal': TXT_NORMAL + ESC + b'!\x00', # Normal text + '2h': TXT_NORMAL + ESC + b'!\x10', # Double height text + '2w': TXT_NORMAL + ESC + b'!\x20', # Double width text + '2x': TXT_NORMAL + ESC + b'!\x30' # Quad area text + }, + 'font': { + 'a': ESC + b'\x4d\x00', # Font type A + 'b': ESC + b'\x4d\x00' # Font type B + }, + 'align': { + 'left': ESC + b'\x61\x00', # Left justification + 'center': ESC + b'\x61\x01', # Centering + 'right': ESC + b'\x61\x02' # Right justification + }, + 'invert': { + True: GS + b'\x42\x01', # Inverse Printing ON + False: GS + b'\x42\x00' # Inverse Printing OFF + }, + 'color': { + 'black': ESC + b'\x72\x00', # Default Color + 'red': ESC + b'\x72\x01' # Alternative Color, Usually Red + }, + 'flip': { + True: ESC + b'\x7b\x01', # Flip ON + False: ESC + b'\x7b\x00' # Flip OFF + }, + 'density': { + 0: GS + b'\x7c\x00', # Printing Density -50% + 1: GS + b'\x7c\x01', # Printing Density -37.5% + 2: GS + b'\x7c\x02', # Printing Density -25% + 3: GS + b'\x7c\x03', # Printing Density -12.5% + 4: GS + b'\x7c\x04', # Printing Density 0% + 5: GS + b'\x7c\x08', # Printing Density +50% + 6: GS + b'\x7c\x07', # Printing Density +37.5% + 7: GS + b'\x7c\x06', # Printing Density +25% + 8: GS + b'\x7c\x05' # Printing Density +12.5% + }, + 'smooth': { + True: GS + b'\x62\x01', # Smooth ON + False: GS + b'\x62\x00' # Smooth OFF + }, + 'height': { # Custom text height + 1: 0x00, + 2: 0x01, + 3: 0x02, + 4: 0x03, + 5: 0x04, + 6: 0x05, + 7: 0x06, + 8: 0x07 + }, + 'width': { # Custom text width + 1: 0x00, + 2: 0x10, + 3: 0x20, + 4: 0x30, + 5: 0x40, + 6: 0x50, + 7: 0x60, + 8: 0x70 + } +} # Fonts SET_FONT = lambda n: ESC + b'\x4d' + n TXT_FONT_A = SET_FONT(b'\x00') # Font type A TXT_FONT_B = SET_FONT(b'\x01') # Font type B -# 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 = { @@ -205,14 +244,3 @@ S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01') # Set raster image double width S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple - -# Printing Density -PD_N50 = GS + b'\x7c\x00' # Printing Density -50% -PD_N37 = GS + b'\x7c\x01' # Printing Density -37.5% -PD_N25 = GS + b'\x7c\x02' # Printing Density -25% -PD_N12 = GS + b'\x7c\x03' # Printing Density -12.5% -PD_0 = GS + b'\x7c\x04' # Printing Density 0% -PD_P50 = GS + b'\x7c\x08' # Printing Density +50% -PD_P37 = GS + b'\x7c\x07' # Printing Density +37.5% -PD_P25 = GS + b'\x7c\x06' # Printing Density +25% -PD_P12 = GS + b'\x7c\x05' # Printing Density +12.5% diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index d23bb05..6a083d6 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -24,16 +24,15 @@ from barcode.writer import ImageWriter from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH -from .constants import TXT_ALIGN_CT, TXT_ALIGN_LT, TXT_ALIGN_RT, BARCODE_FONT_A, BARCODE_FONT_B +from .constants import BARCODE_FONT_A, BARCODE_FONT_B from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW -from .constants import TXT_HEIGHT, TXT_WIDTH, TXT_SIZE, TXT_NORMAL, TXT_SMOOTH_OFF, TXT_SMOOTH_ON -from .constants import TXT_FLIP_OFF, TXT_FLIP_ON, TXT_2WIDTH, TXT_2HEIGHT, TXT_4SQUARE -from .constants import TXT_UNDERL_OFF, TXT_UNDERL_ON, TXT_BOLD_OFF, TXT_BOLD_ON, SET_FONT, TXT_UNDERL2_ON -from .constants import TXT_INVERT_OFF, TXT_INVERT_ON, LINESPACING_FUNCS, LINESPACING_RESET -from .constants import PD_0, PD_N12, PD_N25, PD_N37, PD_N50, PD_P50, PD_P37, PD_P25, PD_P12 +from .constants import TXT_SIZE, TXT_NORMAL +from .constants import SET_FONT +from .constants import LINESPACING_FUNCS, LINESPACING_RESET from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT from .constants import HW_RESET, HW_SELECT, HW_INIT from .constants import CTL_VT, CTL_HT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON +from .constants import TXT_STYLE from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError @@ -359,7 +358,7 @@ class Escpos(object): # Align Bar Code() if align_ct: - self._raw(TXT_ALIGN_CT) + self._raw(TXT_STYLE['align']['center']) # Height if 1 <= height <= 255: self._raw(BARCODE_HEIGHT + six.int2byte(height)) @@ -444,8 +443,9 @@ class Escpos(object): col_count = self.profile.get_columns(font) if columns is None else columns self.text(textwrap.fill(txt, col_count)) - def set(self, align='left', font='a', text_type='normal', width=1, - height=1, density=9, invert=False, smooth=False, flip=False): + def set(self, align='left', font='a', bold=False, underline=0, width=1, + height=1, density=9, invert=False, smooth=False, flip=False, + size='normal'): """ Set text properties by sending them to the printer :param align: horizontal position for text, possible values are: @@ -476,87 +476,29 @@ class Escpos(object): :param flip: True enables upside-down printing, *default*: False :type invert: bool """ - # Width - if height == 2 and width == 2: - self._raw(TXT_NORMAL) - self._raw(TXT_4SQUARE) - elif height == 2 and width == 1: - self._raw(TXT_NORMAL) - self._raw(TXT_2HEIGHT) - elif width == 2 and height == 1: - self._raw(TXT_NORMAL) - self._raw(TXT_2WIDTH) - elif width == 1 and height == 1: - self._raw(TXT_NORMAL) - elif 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and isinstance(height, int): - self._raw(TXT_SIZE + six.int2byte(TXT_WIDTH[width] + TXT_HEIGHT[height])) - else: - raise SetVariableError() - # Upside down - if flip: - self._raw(TXT_FLIP_ON) - else: - self._raw(TXT_FLIP_OFF) - # Smoothing - if smooth: - self._raw(TXT_SMOOTH_ON) - else: - self._raw(TXT_SMOOTH_OFF) - # Type - if text_type.upper() == "B": - self._raw(TXT_BOLD_ON) - self._raw(TXT_UNDERL_OFF) - elif text_type.upper() == "U": - self._raw(TXT_BOLD_OFF) - self._raw(TXT_UNDERL_ON) - elif text_type.upper() == "U2": - self._raw(TXT_BOLD_OFF) - self._raw(TXT_UNDERL2_ON) - elif text_type.upper() == "BU": - self._raw(TXT_BOLD_ON) - self._raw(TXT_UNDERL_ON) - elif text_type.upper() == "BU2": - self._raw(TXT_BOLD_ON) - self._raw(TXT_UNDERL2_ON) - elif text_type.upper() == "NORMAL": - self._raw(TXT_BOLD_OFF) - self._raw(TXT_UNDERL_OFF) - # Font - self._raw(SET_FONT(six.int2byte(self.profile.get_font(font)))) - # Align - if align.upper() == "CENTER": - self._raw(TXT_ALIGN_CT) - elif align.upper() == "RIGHT": - self._raw(TXT_ALIGN_RT) - elif align.upper() == "LEFT": - self._raw(TXT_ALIGN_LT) - # Density - if density == 0: - self._raw(PD_N50) - elif density == 1: - self._raw(PD_N37) - elif density == 2: - self._raw(PD_N25) - elif density == 3: - self._raw(PD_N12) - elif density == 4: - self._raw(PD_0) - elif density == 5: - self._raw(PD_P12) - elif density == 6: - self._raw(PD_P25) - elif density == 7: - self._raw(PD_P37) - elif density == 8: - self._raw(PD_P50) - else: # DEFAULT: DOES NOTHING - pass - # Invert Printing - if invert: - self._raw(TXT_INVERT_ON) - else: - self._raw(TXT_INVERT_OFF) + if size in TXT_STYLE['size']: + self._raw(TXT_NORMAL) + self._raw(TXT_STYLE['size'][size]) + elif size == 'custom': + if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\ + isinstance(height, int): + size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height] + self._raw(TXT_SIZE + six.int2byte(size_byte)) + else: + raise SetVariableError() + + self._raw(TXT_STYLE['flip'][flip]) + self._raw(TXT_STYLE['smooth'][smooth]) + self._raw(TXT_STYLE['bold'][bold]) + self._raw(TXT_STYLE['underline'][underline]) + self._raw(SET_FONT(six.int2byte(self.profile.get_font(font)))) + self._raw(TXT_STYLE['align'][align]) + + if density != 9: + self._raw(TXT_STYLE['density'][density]) + + self._raw(TXT_STYLE['invert'][invert]) def line_spacing(self, spacing=None, divisor=180): """ Set line character spacing. diff --git a/test/test_function_set.py b/test/test_function_set.py new file mode 100644 index 0000000..274011e --- /dev/null +++ b/test/test_function_set.py @@ -0,0 +1,280 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import six + +import escpos.printer as printer +from escpos.constants import TXT_NORMAL, TXT_STYLE, SET_FONT +from escpos.constants import TXT_SIZE + + +# Default test, please copy and paste this block to test set method calls + +def test_default_values(): + instance = printer.Dummy() + instance.set() + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert(instance.output == b''.join(expected_sequence)) + +# Size tests + +def test_set_size_2h(): + instance = printer.Dummy() + instance.set(size='2h') + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['2h'], # Double height text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + + +def test_set_size_2w(): + instance = printer.Dummy() + instance.set(size='2w') + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['2w'], # Double width text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + + +def test_set_size_2x(): + instance = printer.Dummy() + instance.set(size='2x') + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['2x'], # Double text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + + +def test_set_size_custom(): + instance = printer.Dummy() + instance.set(size='custom', width=8, height=7) + + expected_sequence = ( + TXT_SIZE, # Custom text size, no normal reset + six.int2byte(TXT_STYLE['width'][8] + TXT_STYLE['height'][7]), + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + +# Flip + +def test_set_flip(): + instance = printer.Dummy() + instance.set(flip=True) + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][True], # Flip ON + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + +# Smooth + +def test_smooth(): + instance = printer.Dummy() + instance.set(smooth=True) + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][True], # Smooth ON + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert(instance.output == b''.join(expected_sequence)) + + +# Type + + +def test_set_bold(): + instance = printer.Dummy() + instance.set(bold=True) + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][True], # Bold ON + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + + +def test_set_underline(): + instance = printer.Dummy() + instance.set(underline=1) + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][1], # Underline ON, type 1 + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + + +def test_set_underline2(): + instance = printer.Dummy() + instance.set(underline=2) + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][2], # Underline ON, type 2 + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert (instance.output == b''.join(expected_sequence)) + + +# Align + +def test_align_center(): + instance = printer.Dummy() + instance.set(align='center') + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['center'], # Align center + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert(instance.output == b''.join(expected_sequence)) + + +def test_align_right(): + instance = printer.Dummy() + instance.set(align='right') + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['right'], # Align right + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert(instance.output == b''.join(expected_sequence)) + + +# Densities + +def test_densities(): + + for density in range(8): + instance = printer.Dummy() + instance.set(density=density) + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['density'][density], # Custom density from 0 to 8 + TXT_STYLE['invert'][False] # Inverted OFF + ) + + assert(instance.output == b''.join(expected_sequence)) + + +# Invert + +def test_invert(): + instance = printer.Dummy() + instance.set(invert=True) + + expected_sequence = ( + TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size + TXT_STYLE['flip'][False], # Flip OFF + TXT_STYLE['smooth'][False], # Smooth OFF + TXT_STYLE['bold'][False], # Bold OFF + TXT_STYLE['underline'][0], # Underline OFF + SET_FONT(b'\x00'), # Default font + TXT_STYLE['align']['left'], # Align left + TXT_STYLE['invert'][True] # Inverted ON + ) + + assert(instance.output == b''.join(expected_sequence)) \ No newline at end of file From 6815e76bd0d4e285cf571b138df9b5a09f68b172 Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Sun, 14 May 2017 14:41:56 +0200 Subject: [PATCH 04/33] Updated documentation of set method --- src/escpos/escpos.py | 47 ++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 6a083d6..14a7e2c 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -450,31 +450,44 @@ class Escpos(object): :param align: horizontal position for text, possible values are: - * CENTER - * LEFT - * RIGHT + * 'center' + * 'left' + * 'right' - *default*: LEFT + *default*: 'left' + + :param size: size modifier for text, possible values are: + + * 'normal' + * '2h' for double text height + * '2w' for double text width + * '2x' for double text height and width (doubles the text surface) + * 'custom' for custom text height and width + + In this last case, see the width and height parameters. + *default*: 'normal' :param font: font given as an index, a name, or one of the - special values 'a' or 'b', refering to fonts 0 and 1. - :param text_type: text type, possible values are: - - * B for bold - * U for underlined - * U2 for underlined, version 2 - * BU for bold and underlined - * BU2 for bold and underlined, version 2 - * NORMAL for normal text - - *default*: NORMAL - :param width: text width multiplier, decimal range 1-8, *default*: 1 - :param height: text height multiplier, decimal range 1-8, *default*: 1 + special values 'a' or 'b', referring to fonts 0 and 1. + :param bold: text in bold, *default*: False + :param underline: underline mode for text, decimal range 0-2, *default*: 0 + :param width: text width multiplier when size is set to custom, decimal range 1-8, *default*: 1 + :param height: text height multiplier when size is set to custom, decimal range 1-8, *default*: 1 :param density: print density, value from 0-8, if something else is supplied the density remains unchanged :param invert: True enables white on black printing, *default*: False :param smooth: True enables text smoothing. Effective on 4x4 size text and larger, *default*: False :param flip: True enables upside-down printing, *default*: False + :type invert: bool + :type bold: bool + :type underline: bool + :type smooth: bool + :type flip: bool + :type size: str + :type align: str + :type width: int + :type height: int + :type density: int """ if size in TXT_STYLE['size']: From ca7c257e186aca0e264e7a9abfb3b5e41d3e55d7 Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Mon, 15 May 2017 18:54:54 +0200 Subject: [PATCH 05/33] Using booleans for handling text size --- src/escpos/escpos.py | 41 +++++++++++++++++++++------------------ test/test_function_set.py | 8 ++++---- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 14a7e2c..ed75bb2 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -445,7 +445,7 @@ class Escpos(object): def set(self, align='left', font='a', bold=False, underline=0, width=1, height=1, density=9, invert=False, smooth=False, flip=False, - size='normal'): + double_width=False, double_height=False, custom_size=False): """ Set text properties by sending them to the printer :param align: horizontal position for text, possible values are: @@ -456,50 +456,53 @@ class Escpos(object): *default*: 'left' - :param size: size modifier for text, possible values are: - - * 'normal' - * '2h' for double text height - * '2w' for double text width - * '2x' for double text height and width (doubles the text surface) - * 'custom' for custom text height and width - - In this last case, see the width and height parameters. - *default*: 'normal' - :param font: font given as an index, a name, or one of the special values 'a' or 'b', referring to fonts 0 and 1. :param bold: text in bold, *default*: False :param underline: underline mode for text, decimal range 0-2, *default*: 0 - :param width: text width multiplier when size is set to custom, decimal range 1-8, *default*: 1 - :param height: text height multiplier when size is set to custom, decimal range 1-8, *default*: 1 + :param double_height: doubles the height of the text + :param double_width: doubles the width of the text + :param custom_size: uses custom size specified by width and height + parameters. Cannot be used with double_width or double_height. + :param width: text width multiplier when custom_size is used, decimal range 1-8, *default*: 1 + :param height: text height multiplier when custom_size is used, decimal range 1-8, *default*: 1 :param density: print density, value from 0-8, if something else is supplied the density remains unchanged :param invert: True enables white on black printing, *default*: False :param smooth: True enables text smoothing. Effective on 4x4 size text and larger, *default*: False :param flip: True enables upside-down printing, *default*: False + :type font: str :type invert: bool :type bold: bool :type underline: bool :type smooth: bool :type flip: bool - :type size: str + :type custom_size: bool + :type double_width: bool + :type double_height: bool :type align: str :type width: int :type height: int :type density: int """ - if size in TXT_STYLE['size']: - self._raw(TXT_NORMAL) - self._raw(TXT_STYLE['size'][size]) - elif size == 'custom': + if custom_size: if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\ isinstance(height, int): size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height] self._raw(TXT_SIZE + six.int2byte(size_byte)) else: raise SetVariableError() + else: + self._raw(TXT_NORMAL) + if double_width and double_height: + self._raw(TXT_STYLE['size']['2x']) + elif double_width: + self._raw(TXT_STYLE['size']['2w']) + elif double_height: + self._raw(TXT_STYLE['size']['2h']) + else: + self._raw(TXT_STYLE['size']['normal']) self._raw(TXT_STYLE['flip'][flip]) self._raw(TXT_STYLE['smooth'][smooth]) diff --git a/test/test_function_set.py b/test/test_function_set.py index 274011e..777eb32 100644 --- a/test/test_function_set.py +++ b/test/test_function_set.py @@ -33,7 +33,7 @@ def test_default_values(): def test_set_size_2h(): instance = printer.Dummy() - instance.set(size='2h') + instance.set(double_height=True) expected_sequence = ( TXT_NORMAL, TXT_STYLE['size']['2h'], # Double height text size @@ -51,7 +51,7 @@ def test_set_size_2h(): def test_set_size_2w(): instance = printer.Dummy() - instance.set(size='2w') + instance.set(double_width=True) expected_sequence = ( TXT_NORMAL, TXT_STYLE['size']['2w'], # Double width text size @@ -69,7 +69,7 @@ def test_set_size_2w(): def test_set_size_2x(): instance = printer.Dummy() - instance.set(size='2x') + instance.set(double_height=True, double_width=True) expected_sequence = ( TXT_NORMAL, TXT_STYLE['size']['2x'], # Double text size @@ -87,7 +87,7 @@ def test_set_size_2x(): def test_set_size_custom(): instance = printer.Dummy() - instance.set(size='custom', width=8, height=7) + instance.set(custom_size=True, width=8, height=7) expected_sequence = ( TXT_SIZE, # Custom text size, no normal reset From 386b138a8606d523d39cf4d356e13fba0de0ad49 Mon Sep 17 00:00:00 2001 From: TAHRI Ahmed Date: Tue, 15 Nov 2016 16:24:44 +0100 Subject: [PATCH 06/33] Preliminary support of pos 'line display' printing --- src/escpos/constants.py | 5 +++++ src/escpos/escpos.py | 36 +++++++++++++++++++++++++++++++ test/test_function_linedisplay.py | 35 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 test/test_function_linedisplay.py diff --git a/src/escpos/constants.py b/src/escpos/constants.py index 821c143..4e61419 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -64,6 +64,11 @@ _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 +# Line display printing +LINE_DISPLAY_OPEN = ESC + b'\x3d\x02' +LINE_DISPLAY_CLEAR = ESC + b'\x40' +LINE_DISPLAY_CLOSE = ESC + b'\x3d\x01' + # Sheet modes SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index ed75bb2..1d80094 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -29,6 +29,7 @@ from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCOD from .constants import TXT_SIZE, TXT_NORMAL from .constants import SET_FONT from .constants import LINESPACING_FUNCS, LINESPACING_RESET +from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT from .constants import HW_RESET, HW_SELECT, HW_INIT from .constants import CTL_VT, CTL_HT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON @@ -593,6 +594,41 @@ class Escpos(object): except: raise CashDrawerError() + def linedisplay_select(self, select_display=False): + """ Selects the line display or the printer + + This method is used for line displays that are daisy-chained between your computer and printer. + If you set `select_display` to true, only the display is selected and if you set it to false, + only the printer is selected. + + :param select_display: whether the display should be selected or the printer + :type select_display: bool + """ + if select_display: + self._raw(LINE_DISPLAY_OPEN) + else: + self._raw(LINE_DISPLAY_CLOSE) + + def linedisplay_clear(self): + """ Clears the line display and resets the cursor + + This method is used for line displays that are daisy-chained between your computer and printer. + """ + self._raw(LINE_DISPLAY_CLEAR) + + def linedisplay(self, text): + """ + Display text on a line display connected to your printer + + You should connect a line display to your printer. You can do this by daisy-chaining + the display between your computer and printer. + :param text: Text to display + """ + self.linedisplay_select(select_display=True) + self.linedisplay_clear() + self.text(text) + self.linedisplay_select(select_display=False) + def hw(self, hw): """ Hardware operations diff --git a/test/test_function_linedisplay.py b/test/test_function_linedisplay.py new file mode 100644 index 0000000..f5e92ca --- /dev/null +++ b/test/test_function_linedisplay.py @@ -0,0 +1,35 @@ +#!/usr/bin/python +"""tests for line display + +:author: `Patrick Kanzler `_ +:organization: `python-escpos `_ +:copyright: Copyright (c) 2017 `python-escpos `_ +:license: MIT +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import escpos.printer as printer + + +def test_function_linedisplay_select_on(): + """test the linedisplay_select function (activate)""" + instance = printer.Dummy() + instance.linedisplay_select(select_display=True) + assert(instance.output == b'\x1B\x3D\x02') + +def test_function_linedisplay_select_off(): + """test the linedisplay_select function (deactivate)""" + instance = printer.Dummy() + instance.linedisplay_select(select_display=False) + assert(instance.output == b'\x1B\x3D\x01') + +def test_function_linedisplay_clear(): + """test the linedisplay_clear function""" + instance = printer.Dummy() + instance.linedisplay_clear() + assert(instance.output == b'\x1B\x40') + From e2f0aad1c90fa6f31f0285bad5f353375ae7e22b Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 22 May 2017 00:44:22 +0200 Subject: [PATCH 07/33] rewrite to Dummy() --- test/test_function_panel_button.py | 33 ++++-------------------------- test/test_load_module.py | 22 +------------------- 2 files changed, 5 insertions(+), 50 deletions(-) diff --git a/test/test_function_panel_button.py b/test/test_function_panel_button.py index 1006e5b..cdf840b 100644 --- a/test/test_function_panel_button.py +++ b/test/test_function_panel_button.py @@ -12,43 +12,18 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -from nose.tools import with_setup - import escpos.printer as printer -import os - -devfile = 'testfile' -def setup_testfile(): - """create a testfile as devfile""" - fhandle = open(devfile, 'a') - try: - os.utime(devfile, None) - finally: - fhandle.close() - - -def teardown_testfile(): - """destroy testfile again""" - os.remove(devfile) - - -@with_setup(setup_testfile, teardown_testfile) def test_function_panel_button_on(): """test the panel button function (enabling) by comparing output""" - instance = printer.File(devfile=devfile) + instance = printer.Dummy() instance.panel_buttons() - instance.flush() - with open(devfile, "rb") as f: - assert(f.read() == b'\x1B\x63\x35\x00') + assert(instance.output == b'\x1B\x63\x35\x00') -@with_setup(setup_testfile, teardown_testfile) def test_function_panel_button_off(): """test the panel button function (disabling) by comparing output""" - instance = printer.File(devfile=devfile) + instance = printer.Dummy() instance.panel_buttons(False) - instance.flush() - with open(devfile, "rb") as f: - assert(f.read() == b'\x1B\x63\x35\x01') + assert(instance.output == b'\x1B\x63\x35\x01') diff --git a/test/test_load_module.py b/test/test_load_module.py index aeffc5b..efae1b3 100644 --- a/test/test_load_module.py +++ b/test/test_load_module.py @@ -12,30 +12,10 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -from nose.tools import with_setup - import escpos.printer as printer -import os - -devfile = 'testfile' -def setup_testfile(): - """create a testfile as devfile""" - fhandle = open(devfile, 'a') - try: - os.utime(devfile, None) - finally: - fhandle.close() - - -def teardown_testfile(): - """destroy testfile again""" - os.remove(devfile) - - -@with_setup(setup_testfile, teardown_testfile) def test_instantiation(): """test the instantiation of a escpos-printer class and basic printing""" - instance = printer.File(devfile=devfile) + instance = printer.Dummy() instance.text('This is a test\n') From 760df24eeef61a15c0f7466a8b51e95bc43e7d1e Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Mon, 22 May 2017 20:21:35 +0200 Subject: [PATCH 08/33] Allow users to change impl for soft_barcode --- src/escpos/escpos.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 1d80094..ada446b 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -399,14 +399,18 @@ class Escpos(object): if function_type.upper() == "A": self._raw(NUL) - def soft_barcode(self, barcode_type, data, module_height=5, module_width=0.2, text_distance=1): + def soft_barcode(self, barcode_type, data, impl='bitImageColumn', + module_height=5, module_width=0.2, text_distance=1): + image_writer = ImageWriter() + # Check if barcode type exists if barcode_type not in barcode.PROVIDED_BARCODES: raise BarcodeTypeError( 'Barcode type {} not supported by software barcode renderer' .format(barcode_type)) + # Render the barcode to a fake file barcode_class = barcode.get_barcode_class(barcode_type) my_code = barcode_class(data, writer=image_writer) @@ -416,8 +420,9 @@ class Escpos(object): 'text_distance': text_distance }) + # Retrieve the Pillow image and print it image = my_code.writer._image - self.image(image, impl='bitImageColumn') + self.image(image, impl=impl) def text(self, txt): """ Print alpha-numeric text From 89401418ca8db040a1248ddb35d963ac4ada20d2 Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Mon, 22 May 2017 20:25:51 +0200 Subject: [PATCH 09/33] PEP8 software barcode example --- examples/software_barcode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/software_barcode.py b/examples/software_barcode.py index 8476bac..2fd3c18 100644 --- a/examples/software_barcode.py +++ b/examples/software_barcode.py @@ -1,8 +1,9 @@ from escpos.printer import Usb + # Adapt to your needs p = Usb(0x0416, 0x5011, profile="POS-5890") # Some software barcodes p.soft_barcode('code128', 'Hello') -p.soft_barcode('code39', '123456') \ No newline at end of file +p.soft_barcode('code39', '123456') From 9df37bbc2cb124b3def5c263fc55f152702ade1a Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Tue, 23 May 2017 15:13:28 +0200 Subject: [PATCH 10/33] Added ImageWidthError and its implementation (#226) * Added ImageWidthError and its implementation * Added unit tests for ImageWidthError * Parse max_width to int before compare --- src/escpos/escpos.py | 12 ++++++++++++ src/escpos/exceptions.py | 15 +++++++++++++++ test/test_function_image.py | 25 ++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index ada446b..0af1e55 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -37,6 +37,7 @@ from .constants import TXT_STYLE from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError +from .exceptions import ImageWidthError from .magicencode import MagicEncode @@ -102,6 +103,17 @@ class Escpos(object): """ im = EscposImage(img_source) + try: + max_width = int(self.profile.profile_data['media']['width']['pixels']) + if im.width > max_width: + raise ImageWidthError('{} > {}'.format(im.width, max_width)) + except KeyError: + # If the printer's pixel width is not known, print anyways... + pass + except ValueError: + # If the max_width cannot be converted to an int, print anyways... + pass + if im.height > fragment_height: fragments = im.split(fragment_height) for fragment in fragments: diff --git a/src/escpos/exceptions.py b/src/escpos/exceptions.py index 8c574ff..0662792 100644 --- a/src/escpos/exceptions.py +++ b/src/escpos/exceptions.py @@ -8,6 +8,7 @@ Result/Exit codes: - `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError` - `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError` - `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError` + - `41` = Image width is too large :py:exc:`~escpos.exceptions.ImageWidthError` - `50` = No string supplied to be printed :py:exc:`~escpos.exceptions.TextError` - `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError` - `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError` @@ -104,6 +105,20 @@ class ImageSizeError(Error): return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg) +class ImageWidthError(Error): + """ Image width is too large. + + The return code for this exception is `41`. + """ + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 41 + + def __str__(self): + return "Image width is too large ({msg})".format(msg=self.msg) + + class TextError(Error): """ Text string must be supplied to the `text()` method. diff --git a/test/test_function_image.py b/test/test_function_image.py index 27b4fd7..5aac41b 100644 --- a/test/test_function_image.py +++ b/test/test_function_image.py @@ -12,9 +12,13 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -import escpos.printer as printer +import pytest + from PIL import Image +import escpos.printer as printer +from escpos.exceptions import ImageWidthError + # Raster format print def test_bit_image_black(): @@ -139,3 +143,22 @@ def test_large_graphics(): 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') + + +def test_width_too_large(): + """ + Test printing an image that is too large in width. + """ + instance = printer.Dummy() + instance.profile.profile_data = { + 'media': { + 'width': { + 'pixels': 384 + } + } + } + + with pytest.raises(ImageWidthError): + instance.image(Image.new("RGB", (385, 200))) + + instance.image(Image.new("RGB", (384, 200))) \ No newline at end of file From 5cbbabfac8e3e4b03ce8027b05ff475be72238c2 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 24 May 2017 10:23:01 +0200 Subject: [PATCH 11/33] add .mailmap in order to normalize shortlog --- .mailmap | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..46c0229 --- /dev/null +++ b/.mailmap @@ -0,0 +1,10 @@ + + +Manuel F Martinez manpaz + +Davis Goglin davisgoglin +Michael Billington Michael +Cody (Quantified Code Bot) Cody +Renato Lorenzi Renato.Lorenzi +Ahmed Tahri TAHRI Ahmed +Michael Elsdörfer Michael Elsdörfer From c93164e26bd4e227664c29950c4465a2e2add1a1 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 24 May 2017 10:58:55 +0200 Subject: [PATCH 12/33] added new trove for 3.6 and 3.7 --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index bd4a3ed..f163488 100755 --- a/setup.py +++ b/setup.py @@ -100,6 +100,8 @@ setup( 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', From 667ef1d5bb5affc8f438ab2b608deb5a75c68a50 Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Wed, 24 May 2017 20:24:51 +0200 Subject: [PATCH 13/33] Lists should not be right-espaced in reST --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 1a10540..d313390 100644 --- a/README.rst +++ b/README.rst @@ -47,10 +47,10 @@ Dependencies This library makes use of: - * pyusb for USB-printers - * Pillow for image printing - * qrcode for the generation of QR-codes - * pyserial for serial printers +* pyusb for USB-printers +* Pillow for image printing +* qrcode for the generation of QR-codes +* pyserial for serial printers Documentation and Usage ----------------------- From 5c6031c67bad2ccfbc04fcd0b4e46e44d061a560 Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Fri, 26 May 2017 00:27:17 +0200 Subject: [PATCH 14/33] Added authors file and generate_authors.sh (#227) * Added authors file Generated using `git shortlog -s -n` and sorted by alphabetical order using vim. * Added generate_authors.sh script and ordered author list * Regenerated AUTHORS with .mailmap --- AUTHORS | 26 ++++++++++++++++++++++++++ generate_authors.sh | 3 +++ 2 files changed, 29 insertions(+) create mode 100644 AUTHORS create mode 100755 generate_authors.sh diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..dd865d0 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,26 @@ +Ahmed Tahri +Asuki Kono +belono +Christoph Heuel +Cody (Quantified Code Bot) +Curtis // mashedkeyboard +Davis Goglin +Dean Rispin +Dmytro Katyukha +Hark +Joel Lehtonen +Kristi +ldos +Manuel F Martinez +Michael Billington +Michael Elsdörfer +Nathan Bookham +Patrick Kanzler +Qian Linfeng +Renato Lorenzi +Romain Porte +Sam Cheng +Stephan Sokolow +Thijs Triemstra +Thomas van den Berg +ysuolmai diff --git a/generate_authors.sh b/generate_authors.sh new file mode 100755 index 0000000..6ab31c4 --- /dev/null +++ b/generate_authors.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +git shortlog -s -n | cut -f2 | sort From 8cf2563208b91068a9c16d054e7b1c44c3ff78f7 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 26 May 2017 01:55:30 +0200 Subject: [PATCH 15/33] integrate author check into travis --- .travis.yml | 3 +++ CONTRIBUTING.rst | 9 +++++++++ doc/generate_authors.sh | 14 ++++++++++++++ generate_authors.sh | 3 --- 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100755 doc/generate_authors.sh delete mode 100755 generate_authors.sh diff --git a/.travis.yml b/.travis.yml index 6f94072..f8fca72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: python sudo: false cache: pip +git: + depth: 100000 addons: apt: packages: @@ -37,6 +39,7 @@ matrix: - python: pypy3 before_install: - pip install tox codecov 'sphinx>=1.5.1' + - ./doc/generate_authors.sh --check script: - tox - codecov diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9d5de8b..ad16384 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -12,6 +12,15 @@ The pull requests and issues will be prefilled with templates. Please fill in yo This project uses `semantic versioning `_ and tries to adhere to the proposed rules as well as possible. +Author-list +----------- + +This project keeps a list of authors. This can be auto-generated by calling `./doc/generate-authors.sh`. +When contributing the first time, please include a commit with the output of this script in place. +Otherwise the integration-check will fail. + +When you change your username or mail-address, please also update the `.mailmap` and the authors-list. + Style-Guide ----------- diff --git a/doc/generate_authors.sh b/doc/generate_authors.sh new file mode 100755 index 0000000..b93e920 --- /dev/null +++ b/doc/generate_authors.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +GENLIST=$(git shortlog -s -n | cut -f2 | sort) +AUTHORSFILE="$(dirname $0)/../AUTHORS" +TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile" + +if [ "$#" -eq 1 ] + then + echo "$GENLIST">$TEMPAUTHORSFILE + diff -q --from-file $AUTHORSFILE $TEMPAUTHORSFILE + else + echo "$GENLIST">$AUTHORSFILE +fi + diff --git a/generate_authors.sh b/generate_authors.sh deleted file mode 100755 index 6ab31c4..0000000 --- a/generate_authors.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -git shortlog -s -n | cut -f2 | sort From f38e720fc79efcd692e16cc3735078f76b7b5e5c Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Sat, 10 Jun 2017 23:35:26 +0000 Subject: [PATCH 16/33] Clarifiy and update usage.rst relevant to #230 clarifies the config-file in the usage.rst --- doc/user/usage.rst | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/doc/user/usage.rst b/doc/user/usage.rst index 6d4e462..203389b 100644 --- a/doc/user/usage.rst +++ b/doc/user/usage.rst @@ -1,6 +1,7 @@ ***** Usage ***** +:Last Reviewed: 2017-06-10 Define your printer ------------------- @@ -133,13 +134,13 @@ format. For windows it is probably at:: And for linux:: - $HOME/.config/python-escpos/config.yaml + $HOME/.config/python-escpos/config.yaml If you aren't sure, run:: - from escpos import config - c = config.Config() - c.load() + from escpos import config + c = config.Config() + c.load() If it can't find the configuration file in the default location, it will tell you where it's looking. You can always pass a path, or a list of paths, to @@ -147,9 +148,9 @@ the ``load()`` method. To load the configured printer, run:: - from escpos import config - c = config.Config() - printer = c.printer() + from escpos import config + c = config.Config() + printer = c.printer() The printer section @@ -157,23 +158,34 @@ The printer section The ``printer`` configuration section defines a default printer to create. -The only required paramter is ``type``. The value of this should be one of the +The only required paramter is ``type``. The value of this has to be one of the printers defined in :doc:`/user/printers`. -The rest of the parameters are whatever you want to pass to the printer. +The rest of the given parameters will be passed on to the initialization of the printer class. +Use these to overwrite the default values as specified in :doc:`/user/printers`. +This implies that the parameters have to match the parameter-names of the respective printer class. An example file printer:: - printer: - type: File - devfile: /dev/someprinter + printer: + type: File + devfile: /dev/someprinter And for a network printer:: - printer: - type: network - host: 127.0.0.1 - port: 9000 + printer: + type: Network + host: 127.0.0.1 + port: 9000 + +An USB-printer could be defined by:: + + printer: + type: Usb + idVendor: 0x1234 + idProduct: 0x5678 + in_ep: 0x66 + out_ep: 0x01 Printing text right ------------------- From b27adf1cadd282102eebdd6023119074456da48f Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Sun, 11 Jun 2017 10:06:57 +0200 Subject: [PATCH 17/33] Using viivakoodi instead of pyBarcode --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f163488..60a9209 100755 --- a/setup.py +++ b/setup.py @@ -118,7 +118,7 @@ setup( 'argparse', 'argcomplete', 'future', - 'pyBarcode==0.8b1' + 'viivakoodi>=0.8' ], setup_requires=[ 'setuptools_scm', From ecf701d3806be2a5adb2c7c269739a7a41b19d1d Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 19 Jun 2017 11:13:39 +0000 Subject: [PATCH 18/33] cat authorsfiles during check --- doc/generate_authors.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/generate_authors.sh b/doc/generate_authors.sh index b93e920..bd770e7 100755 --- a/doc/generate_authors.sh +++ b/doc/generate_authors.sh @@ -7,6 +7,11 @@ TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile" if [ "$#" -eq 1 ] then echo "$GENLIST">$TEMPAUTHORSFILE + echo "\nAuthorsfile in version control:\n" + cat $AUTHORSFILE + echo "\nNew authorsfile:\n" + cat $TEMPAUTHORSFILE + echo "\nUsing diff on files...\n" diff -q --from-file $AUTHORSFILE $TEMPAUTHORSFILE else echo "$GENLIST">$AUTHORSFILE From 69462bbe133a924fbd090952fc8453cb0b614a90 Mon Sep 17 00:00:00 2001 From: TAHRI Ahmed Date: Sat, 17 Jun 2017 00:58:47 +0200 Subject: [PATCH 19/33] Fix SerialException when trying to close device on __del__ without verifing if is actually opened. --- src/escpos/printer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/escpos/printer.py b/src/escpos/printer.py index d14b93d..a15fe21 100644 --- a/src/escpos/printer.py +++ b/src/escpos/printer.py @@ -131,6 +131,8 @@ class Serial(Escpos): def open(self): """ Setup serial port and set is as escpos device """ + if self.device is not None and self.device.is_open: + self.close() self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits, timeout=self.timeout, @@ -151,7 +153,7 @@ class Serial(Escpos): def close(self): """ Close Serial interface """ - if self.device is not None: + if self.device is not None and self.device.is_open: self.device.flush() self.device.close() From 962208063bef81707f200ab086fede706b32ba61 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 22 Jun 2017 15:54:21 +0200 Subject: [PATCH 20/33] Update readme list of dependencies add viivakoodi and links --- README.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index d313390..9f656ba 100644 --- a/README.rst +++ b/README.rst @@ -47,10 +47,11 @@ Dependencies This library makes use of: -* pyusb for USB-printers -* Pillow for image printing -* qrcode for the generation of QR-codes -* pyserial for serial printers +* `pyusb `_ for USB-printers +* `Pillow `_ for image printing +* `qrcode `_ for the generation of QR-codes +* `pyserial `_ for serial printers +* `viivakoodi `_ for the generation of barcodes Documentation and Usage ----------------------- From 574d6d86facbd8d8e737f0f1606e894b7bf07294 Mon Sep 17 00:00:00 2001 From: K Date: Sat, 22 Jul 2017 02:37:33 +0200 Subject: [PATCH 21/33] Added the DLE EOT querying command. Added a function to check whether the printer is online or not, as well as a reading method for USB printers. --- src/escpos/constants.py | 5 +++++ src/escpos/escpos.py | 14 ++++++++++++++ src/escpos/printer.py | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/src/escpos/constants.py b/src/escpos/constants.py index 4e61419..7a6eca7 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -249,3 +249,8 @@ S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01') # Set raster image double width S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple + +# Status Command +RT_STATUS_ONLINE = DLE + EOT + b'\x01'; +RT_STATUS_OFFLINECAUSE = DLE + EOT + b'\x02'; +RT_MASK_ONLINE = 8; diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 0af1e55..3639ea1 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -18,6 +18,7 @@ from __future__ import unicode_literals import qrcode import textwrap import six +import time import barcode from barcode.writer import ImageWriter @@ -34,6 +35,7 @@ from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CU from .constants import HW_RESET, HW_SELECT, HW_INIT from .constants import CTL_VT, CTL_HT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON from .constants import TXT_STYLE +from .constants import RT_STATUS_ONLINE, RT_STATUS_OFFLINECAUSE, RT_MASK_ONLINE from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError @@ -733,6 +735,18 @@ class Escpos(object): else: self._raw(PANEL_BUTTON_OFF) + def queryStatus(self): + self._raw(RT_STATUS_ONLINE) + time.sleep(1) + status = self._read(); + if (len(status) > 0): + return status; + else: + return [8]; + + def _isOnline(self): + status = self.queryStatus()[0]; + return ((status & RT_MASK_ONLINE) == 0); class EscposIO(object): """ESC/POS Printer IO object diff --git a/src/escpos/printer.py b/src/escpos/printer.py index a15fe21..b658efb 100644 --- a/src/escpos/printer.py +++ b/src/escpos/printer.py @@ -84,6 +84,10 @@ class Usb(Escpos): """ self.device.write(self.out_ep, msg, self.timeout) + def _read(self): + """ Reads a data buffer and returns it to the caller. """ + return self.device.read(self.in_ep, 16) + def close(self): """ Release USB interface """ if self.device: From 4a3b4353bdb340e698eaae4cbc6f5a8cd6e59c8a Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sat, 22 Jul 2017 11:37:23 +0200 Subject: [PATCH 22/33] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index dd865d0..654fe13 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,6 +9,7 @@ Dean Rispin Dmytro Katyukha Hark Joel Lehtonen +K Kristi ldos Manuel F Martinez From 9ba284cf842125a2199b634634e67fbe09b68df4 Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sat, 22 Jul 2017 11:40:55 +0200 Subject: [PATCH 23/33] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 654fe13..4c5bbc8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,6 +3,7 @@ Asuki Kono belono Christoph Heuel Cody (Quantified Code Bot) +csoft2k Curtis // mashedkeyboard Davis Goglin Dean Rispin From 2d8a774b9f28b90b7f72f7aae37e3ce67854596a Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sat, 22 Jul 2017 11:57:49 +0200 Subject: [PATCH 24/33] Corrections to pull request 237. Create a _read function in the Escpos abtract class, which throws a NotImplementedError. Rename the isOnline function. --- src/escpos/escpos.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 3639ea1..a1055ab 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -79,6 +79,9 @@ class Escpos(object): """ pass + def _read(self, msg): + raise NotImplementedError(); + def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", fragment_height=960): """ Print an image @@ -744,7 +747,7 @@ class Escpos(object): else: return [8]; - def _isOnline(self): + def isOnline(self): status = self.queryStatus()[0]; return ((status & RT_MASK_ONLINE) == 0); From 05cd028ac65c8ab0519eea6433de2b7a96553766 Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sat, 22 Jul 2017 21:38:07 +0200 Subject: [PATCH 25/33] Remove semicolons, syntax enhancements, remove unused constant. --- src/escpos/constants.py | 1 - src/escpos/escpos.py | 27 ++++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/escpos/constants.py b/src/escpos/constants.py index 7a6eca7..a764290 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -252,5 +252,4 @@ S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple # Status Command RT_STATUS_ONLINE = DLE + EOT + b'\x01'; -RT_STATUS_OFFLINECAUSE = DLE + EOT + b'\x02'; RT_MASK_ONLINE = 8; diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index a1055ab..c37355e 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -35,7 +35,7 @@ from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CU from .constants import HW_RESET, HW_SELECT, HW_INIT from .constants import CTL_VT, CTL_HT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON from .constants import TXT_STYLE -from .constants import RT_STATUS_ONLINE, RT_STATUS_OFFLINECAUSE, RT_MASK_ONLINE +from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError @@ -80,7 +80,10 @@ class Escpos(object): pass def _read(self, msg): - raise NotImplementedError(); + """ Returns a NotImplementedError if the instance of the class doesn't override this method. + :raises NotImplementedError + """ + raise NotImplementedError() def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", fragment_height=960): @@ -738,18 +741,20 @@ class Escpos(object): else: self._raw(PANEL_BUTTON_OFF) - def queryStatus(self): + def query_status(self): + """ Queries the printer for its status, and returns an array of integers containing it. + :rtype: array(integer)""" self._raw(RT_STATUS_ONLINE) time.sleep(1) - status = self._read(); - if (len(status) > 0): - return status; - else: - return [8]; + status = self._read() + return status or [8] + + def is_online(self): + """ Queries the printer its online status. + When online, returns True; False otherwise. + :rtype: bool: True if online, False if offline.""" + return (self.query_status()[0] & RT_MASK_ONLINE) == 0 - def isOnline(self): - status = self.queryStatus()[0]; - return ((status & RT_MASK_ONLINE) == 0); class EscposIO(object): """ESC/POS Printer IO object From 7e9cbf611ae8f1bdd2ca809ddd8b505baa628cb6 Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sun, 23 Jul 2017 11:35:12 +0200 Subject: [PATCH 26/33] Syntax changes. Change the value check syntax. Change the offline array returned to use a constant. --- src/escpos/escpos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index c37355e..a986304 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -747,13 +747,13 @@ class Escpos(object): self._raw(RT_STATUS_ONLINE) time.sleep(1) status = self._read() - return status or [8] + return status or [RT_MASK_ONLINE] def is_online(self): """ Queries the printer its online status. When online, returns True; False otherwise. :rtype: bool: True if online, False if offline.""" - return (self.query_status()[0] & RT_MASK_ONLINE) == 0 + return not (self.query_status()[0] & RT_MASK_ONLINE) class EscposIO(object): From c0307e40b1f132abf8766e838a11d37a8268123d Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sun, 23 Jul 2017 11:37:50 +0200 Subject: [PATCH 27/33] Add entry to .mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 46c0229..9de2698 100644 --- a/.mailmap +++ b/.mailmap @@ -8,3 +8,4 @@ Cody (Quantified Code Bot) Cody Renato.Lorenzi Ahmed Tahri TAHRI Ahmed Michael Elsdörfer Michael Elsdörfer +csoft2k \ No newline at end of file From bc046d77ac7d954a88bae84040af730493022049 Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sun, 23 Jul 2017 11:54:17 +0200 Subject: [PATCH 28/33] Remove overriden author --- AUTHORS | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 4c5bbc8..d0ab9b0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,7 +10,6 @@ Dean Rispin Dmytro Katyukha Hark Joel Lehtonen -K Kristi ldos Manuel F Martinez From 759cb5ac64b8952f2021ed4c79969eed7100567d Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Mon, 24 Jul 2017 15:04:54 +0200 Subject: [PATCH 29/33] Ensure QR codes have a border large enough (#235) * Ensure QR codes have a border large enough (The QR code spec requires a border at least 4*box_size thick but we can't just set border=16 because that results in a QR code more than 255px tall and I'm not yet ready to use fullimage() as a backend for it) This fix was originally commited by Stephan Sokolow on 2014-05-22 * Let the user print stuff using qr example * fix tests --- examples/qr_code.py | 19 +++++++++++++++++++ src/escpos/escpos.py | 3 +++ test/test_function_qr_native.py | 6 ++++-- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 examples/qr_code.py diff --git a/examples/qr_code.py b/examples/qr_code.py new file mode 100644 index 0000000..6db8b68 --- /dev/null +++ b/examples/qr_code.py @@ -0,0 +1,19 @@ +import sys + +from escpos.printer import Usb + + +def usage(): + print("usage: qr_code.py ") + + +if __name__ == '__main__': + if len(sys.argv) != 2: + usage() + sys.exit(1) + + content = sys.argv[1] + + # Adapt to your needs + p = Usb(0x0416, 0x5011, profile="POS-5890") + p.qr(content) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index a986304..ec4df3b 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -211,7 +211,10 @@ class Escpos(object): qr_img = qr_code.make_image() im = qr_img._img.convert("RGB") # Convert the RGB image in printable image + self.text('\n') self.image(im) + self.text('\n') + self.text('\n') return # Native 2D code printing cn = b'1' # Code type for QR code diff --git a/test/test_function_qr_native.py b/test/test_function_qr_native.py index 2fbaa64..4aa9c1d 100644 --- a/test/test_function_qr_native.py +++ b/test/test_function_qr_native.py @@ -86,9 +86,11 @@ def test_image(): instance = printer.Dummy() instance.qr("1", native=False, size=1) print(instance.output) - expected = b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \ + expected = b'\x1bt\x00\n' \ + b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \ b']ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00' \ - b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' + b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' \ + b'\n\n' assert(instance.output == expected) From 26502eb45b897c2935abbd97c6ae06bb9681b380 Mon Sep 17 00:00:00 2001 From: csoft2k Date: Mon, 24 Jul 2017 23:29:16 +0200 Subject: [PATCH 30/33] Fix tabs behaviour. The changes done in this commit should help with the open issues: #5, #27 and #161. The old implementation lacked the NUL char at the end of the command, as defined on the Epson ESC/POS Reference Guide (see https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=53 ). Also, the horizontal tab control character (CTL_HT) shouldn't be there. This implementation allows setting up to 32 tabs with a given tab width. Both values are checked to be in the valid ranges defined on the guide. Also, the TabPosError exception text has been rewritten to define the stated above. --- src/escpos/escpos.py | 14 ++++++++------ src/escpos/exceptions.py | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index ec4df3b..0047fec 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -689,7 +689,7 @@ class Escpos(object): else: raise ValueError("n must be betwen 0 and 255") - def control(self, ctl, pos=4): + def control(self, ctl, count=5, tab_size=8): """ Feed control sequences :param ctl: string for the following control sequences: @@ -700,7 +700,8 @@ class Escpos(object): * HT *for Horizontal Tab* * VT *for Vertical Tab* - :param pos: integer between 1 and 16, controls the horizontal tab position + :param count: integer between 1 and 32, controls the horizontal tab count. Defaults to 5. + :param tab_size: integer between 1 and 255, controls the horizontal tab size in characters. Defaults to 8 :raises: :py:exc:`~escpos.exceptions.TabPosError` """ # Set position @@ -711,13 +712,14 @@ class Escpos(object): elif ctl.upper() == "CR": self._raw(CTL_CR) elif ctl.upper() == "HT": - if not (1 <= pos <= 16): + if not (0 <= count <= 32 and 1 <= tab_size <= 255 and count * tab_size < 256): raise TabPosError() else: # Set tab positions - self._raw(CTL_SET_HT + six.int2byte(pos)) - - self._raw(CTL_HT) + self._raw(CTL_SET_HT) + for iterator in range(1, count): + self._raw(six.int2byte(iterator * tab_size)) + self._raw(NUL) elif ctl.upper() == "VT": self._raw(CTL_VT) diff --git a/src/escpos/exceptions.py b/src/escpos/exceptions.py index 0662792..781e3e9 100644 --- a/src/escpos/exceptions.py +++ b/src/escpos/exceptions.py @@ -150,7 +150,8 @@ class CashDrawerError(Error): class TabPosError(Error): - """ Valid tab positions must be in the range 0 to 16. + """ Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. + Both values multiplied must not exceed 255, since it is the maximum tab value. This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. The returncode for this exception is `70`. From f28795daa242b7511684ec538846bfdfc25f7bcf Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sat, 22 Jul 2017 11:37:23 +0200 Subject: [PATCH 31/33] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index d0ab9b0..4c5bbc8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,7 @@ Dean Rispin Dmytro Katyukha Hark Joel Lehtonen +K Kristi ldos Manuel F Martinez From 8a1816476ade8ad4e113a3e2b75b61598efa8352 Mon Sep 17 00:00:00 2001 From: csoft2k Date: Sun, 23 Jul 2017 11:54:17 +0200 Subject: [PATCH 32/33] Remove overriden author --- AUTHORS | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 4c5bbc8..d0ab9b0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,7 +10,6 @@ Dean Rispin Dmytro Katyukha Hark Joel Lehtonen -K Kristi ldos Manuel F Martinez From 8cdcf112e700e9ed8b3c240fde890ba24f1a0b5e Mon Sep 17 00:00:00 2001 From: csoft2k Date: Mon, 24 Jul 2017 23:29:16 +0200 Subject: [PATCH 33/33] Fix tabs behaviour. The changes done in this commit should help with the open issues: #5, #27 and #161. The old implementation lacked the NUL char at the end of the command, as defined on the Epson ESC/POS Reference Guide (see https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=53 ). Also, the horizontal tab control character (CTL_HT) shouldn't be there. This implementation allows setting up to 32 tabs with a given tab width. Both values are checked to be in the valid ranges defined on the guide. Also, the TabPosError exception text has been rewritten to define the stated above. --- src/escpos/escpos.py | 14 ++++++++------ src/escpos/exceptions.py | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index ec4df3b..0047fec 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -689,7 +689,7 @@ class Escpos(object): else: raise ValueError("n must be betwen 0 and 255") - def control(self, ctl, pos=4): + def control(self, ctl, count=5, tab_size=8): """ Feed control sequences :param ctl: string for the following control sequences: @@ -700,7 +700,8 @@ class Escpos(object): * HT *for Horizontal Tab* * VT *for Vertical Tab* - :param pos: integer between 1 and 16, controls the horizontal tab position + :param count: integer between 1 and 32, controls the horizontal tab count. Defaults to 5. + :param tab_size: integer between 1 and 255, controls the horizontal tab size in characters. Defaults to 8 :raises: :py:exc:`~escpos.exceptions.TabPosError` """ # Set position @@ -711,13 +712,14 @@ class Escpos(object): elif ctl.upper() == "CR": self._raw(CTL_CR) elif ctl.upper() == "HT": - if not (1 <= pos <= 16): + if not (0 <= count <= 32 and 1 <= tab_size <= 255 and count * tab_size < 256): raise TabPosError() else: # Set tab positions - self._raw(CTL_SET_HT + six.int2byte(pos)) - - self._raw(CTL_HT) + self._raw(CTL_SET_HT) + for iterator in range(1, count): + self._raw(six.int2byte(iterator * tab_size)) + self._raw(NUL) elif ctl.upper() == "VT": self._raw(CTL_VT) diff --git a/src/escpos/exceptions.py b/src/escpos/exceptions.py index 0662792..781e3e9 100644 --- a/src/escpos/exceptions.py +++ b/src/escpos/exceptions.py @@ -150,7 +150,8 @@ class CashDrawerError(Error): class TabPosError(Error): - """ Valid tab positions must be in the range 0 to 16. + """ Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. + Both values multiplied must not exceed 255, since it is the maximum tab value. This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. The returncode for this exception is `70`.