diff --git a/.mailmap b/.mailmap index 9de2698..606ae8a 100644 --- a/.mailmap +++ b/.mailmap @@ -8,4 +8,5 @@ Cody (Quantified Code Bot) Cody Renato.Lorenzi Ahmed Tahri TAHRI Ahmed Michael Elsdörfer Michael Elsdörfer -csoft2k \ No newline at end of file +csoft2k +Sergio Pulgarin diff --git a/AUTHORS b/AUTHORS index 6eb51a1..0a8ecd5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Qian Linfeng Renato Lorenzi Romain Porte Sam Cheng +Sergio Pulgarin Stephan Sokolow Thijs Triemstra Thomas van den Berg diff --git a/examples/codepage_tables.py b/examples/codepage_tables.py index 7966036..a071bf5 100644 --- a/examples/codepage_tables.py +++ b/examples/codepage_tables.py @@ -37,14 +37,14 @@ def print_codepage(printer, codepage): sep = "" # Table header - printer.set(text_type='B') + printer.set(font='b') printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16))))) printer.set() # The table for x in range(0, 16): # First column - printer.set(text_type='B') + printer.set(font='b') printer._raw("{} ".format(hex(x)[2:])) printer.set() diff --git a/examples/qr_code.py b/examples/qr_code.py index 6db8b68..325f845 100644 --- a/examples/qr_code.py +++ b/examples/qr_code.py @@ -16,4 +16,4 @@ if __name__ == '__main__': # Adapt to your needs p = Usb(0x0416, 0x5011, profile="POS-5890") - p.qr(content) + p.qr(content, center=True) diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index adebf90..979bfc7 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -1,16 +1,47 @@ import re -import six from os import environ, path +import pickle +import logging +import time + +import six import yaml -# Load external printer database -if 'ESCPOS_CAPABILITIES_FILE' in environ: - file_path = environ['ESCPOS_CAPABILITIES_FILE'] -else: - file_path = path.join(path.dirname(__file__), 'capabilities.json') -with open(file_path) as f: - CAPABILITIES = yaml.load(f) +logging.basicConfig() +logger = logging.getLogger(__name__) + + +pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', '/tmp/') +pickle_path = path.join(pickle_dir, 'capabilities.pickle') +capabilities_path = environ.get( + 'ESCPOS_CAPABILITIES_FILE', + path.join(path.dirname(__file__), 'capabilities.json')) + +# Load external printer database +t0 = time.time() +logger.debug('Using capabilities from file: %s', capabilities_path) +if path.exists(pickle_path): + if path.getmtime(capabilities_path) > path.getmtime(pickle_path): + logger.debug('Found a more recent capabilities file') + full_load = True + else: + full_load = False + logger.debug('Loading capabilities from pickle in %s', pickle_path) + with open(pickle_path, 'rb') as cf: + CAPABILITIES = pickle.load(cf) +else: + logger.debug('Capabilities pickle file not found: %s', pickle_path) + full_load = True + +if full_load: + logger.debug('Loading and pickling capabilities') + with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp: + CAPABILITIES = yaml.load(cp) + pickle.dump(CAPABILITIES, pp) + +logger.debug('Finished loading capabilities took %.2fs', time.time() - t0) + PROFILES = CAPABILITIES['profiles'] diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 8a01c8b..e3b3a53 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -88,7 +88,7 @@ class Escpos(object): raise NotImplementedError() def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", - fragment_height=960): + fragment_height=960, center=False): """ Print an image You can select whether the printer should print in high density or not. The default value is high density. @@ -109,14 +109,19 @@ class Escpos(object): :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:* 960 + :param center: Center image horizontally *default:* False """ 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)) + + if center: + im.center(max_width) except KeyError: # If the printer's pixel width is not known, print anyways... pass @@ -174,7 +179,8 @@ class Escpos(object): header = self._int_low_high(len(data) + 2, 2) self._raw(GS + b'(L' + header + m + fn + data) - def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, native=False): + def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, + native=False, center=False): """ Print QR Code for the provided string :param content: The content of the code. Numeric data will be more efficiently compacted. @@ -186,6 +192,7 @@ class Escpos(object): by all printers). :param native: True to render the code on the printer, False to render the code as an image and send it to the printer (Default) + :param center: Centers the code *default:* False """ # Basic validation if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: @@ -212,12 +219,17 @@ class Escpos(object): qr_code.make(fit=True) 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.image(im, center=center) self.text('\n') self.text('\n') return + + if center: + raise NotImplementedError("Centering not implemented for native QR rendering") + # Native 2D code printing cn = b'1' # Code type for QR code # Select model: 1, 2 or micro. diff --git a/src/escpos/image.py b/src/escpos/image.py index a5b15ab..76b75fd 100644 --- a/src/escpos/image.py +++ b/src/escpos/image.py @@ -115,3 +115,19 @@ class EscposImage(object): box = (left, upper, right, lower) fragments.append(self.img_original.crop(box)) return fragments + + def center(self, max_width): + """In-place image centering + + :param: Maximum width in order to deduce x offset for centering + :return: None + """ + old_width, height = self._im.size + new_size = (max_width, height) + + new_im = Image.new("1", new_size) + paste_x = int((max_width - old_width) / 2) + + new_im.paste(self._im, (paste_x, 0)) + + self._im = new_im diff --git a/test/test_function_image.py b/test/test_function_image.py index 5aac41b..5f66612 100644 --- a/test/test_function_image.py +++ b/test/test_function_image.py @@ -145,10 +145,8 @@ def test_large_graphics(): 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. - """ +@pytest.fixture +def dummy_with_width(): instance = printer.Dummy() instance.profile.profile_data = { 'media': { @@ -157,8 +155,25 @@ def test_width_too_large(): } } } + return instance + + +def test_width_too_large(dummy_with_width): + """ + Test printing an image that is too large in width. + """ + instance = dummy_with_width with pytest.raises(ImageWidthError): instance.image(Image.new("RGB", (385, 200))) - instance.image(Image.new("RGB", (384, 200))) \ No newline at end of file + instance.image(Image.new("RGB", (384, 200))) + + +def test_center_image(dummy_with_width): + instance = dummy_with_width + + with pytest.raises(ImageWidthError): + instance.image(Image.new("RGB", (385, 200)), center=True) + + instance.image(Image.new("RGB", (384, 200)), center=True) diff --git a/test/test_function_qr_native.py b/test/test_function_qr_native.py index 4aa9c1d..e15e45d 100644 --- a/test/test_function_qr_native.py +++ b/test/test_function_qr_native.py @@ -13,6 +13,8 @@ from __future__ import print_function from __future__ import unicode_literals from nose.tools import raises +import pytest + import escpos.printer as printer from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1 @@ -25,7 +27,6 @@ def test_defaults(): b'(k\x07\x001P01234\x1d(k\x03\x001Q0' assert(instance.output == expected) - def test_empty(): """Test QR printing blank code""" instance = printer.Dummy() @@ -99,3 +100,13 @@ def test_image_invalid_model(): """Test unsupported QR model as image""" instance = printer.Dummy() instance.qr("1234", native=False, model=QR_MODEL_1) + + +@pytest.fixture +def instance(): + return printer.Dummy() + + +def test_center_not_implementer(instance): + with pytest.raises(NotImplementedError): + instance.qr("test", center=True, native=True) \ No newline at end of file diff --git a/test/test_function_qr_non-native.py b/test/test_function_qr_non-native.py index bba47fd..a420b2b 100644 --- a/test/test_function_qr_non-native.py +++ b/test/test_function_qr_non-native.py @@ -13,6 +13,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import pytest import mock from escpos.printer import Dummy @@ -30,3 +31,12 @@ def test_type_of_object_passed_to_image_function(img_function): d.qr("LoremIpsum") args, kwargs = img_function.call_args assert isinstance(args[0], Image.Image) + + +@pytest.fixture +def instance(): + return Dummy() + + +def test_center(instance): + instance.qr("LoremIpsum", center=True)