From b648cfd67f930337d878690375f30cdb1aa6e49e Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Thu, 31 Aug 2017 09:25:35 +0200 Subject: [PATCH] First attempt at centering images and QRs (#250) This was tested on ZJ-5890 with success. By default centering is deactivated for backward compatibility. Trying to center a QR code in native mode will raise an exception as we do not know ATM if the native rendering is centered by default or not. * Added basic tests for center feature * Check image size before centering --- examples/qr_code.py | 2 +- src/escpos/escpos.py | 18 +++++++++++++++--- src/escpos/image.py | 16 ++++++++++++++++ test/test_function_image.py | 25 ++++++++++++++++++++----- test/test_function_qr_native.py | 13 ++++++++++++- test/test_function_qr_non-native.py | 10 ++++++++++ 6 files changed, 74 insertions(+), 10 deletions(-) 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/escpos.py b/src/escpos/escpos.py index 04c1d80..c94125c 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -87,7 +87,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. @@ -108,14 +108,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 @@ -173,7 +178,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. @@ -185,6 +191,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]: @@ -211,12 +218,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)