mirror of
				https://github.com/python-escpos/python-escpos
				synced 2025-10-23 09:30:00 +00:00 
			
		
		
		
	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
This commit is contained in:
		 Romain Porte
					Romain Porte
				
			
				
					committed by
					
						 Patrick Kanzler
						Patrick Kanzler
					
				
			
			
				
	
			
			
			 Patrick Kanzler
						Patrick Kanzler
					
				
			
						parent
						
							50c627fbb0
						
					
				
				
					commit
					b648cfd67f
				
			| @@ -16,4 +16,4 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Adapt to your needs |     # Adapt to your needs | ||||||
|     p = Usb(0x0416, 0x5011, profile="POS-5890") |     p = Usb(0x0416, 0x5011, profile="POS-5890") | ||||||
|     p.qr(content) |     p.qr(content, center=True) | ||||||
|   | |||||||
| @@ -87,7 +87,7 @@ class Escpos(object): | |||||||
|         raise NotImplementedError() |         raise NotImplementedError() | ||||||
|  |  | ||||||
|     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=960): |               fragment_height=960, center=False): | ||||||
|         """ Print an image |         """ Print an image | ||||||
|  |  | ||||||
|         You can select whether the printer should print in high density or not. The default value is high density. |         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 high_density_horizontal: print in high density in horizontal direction *default:* True | ||||||
|         :param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn` |         :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 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) |         im = EscposImage(img_source) | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             max_width = int(self.profile.profile_data['media']['width']['pixels']) |             max_width = int(self.profile.profile_data['media']['width']['pixels']) | ||||||
|  |  | ||||||
|             if im.width > max_width: |             if im.width > max_width: | ||||||
|                 raise ImageWidthError('{} > {}'.format(im.width, max_width)) |                 raise ImageWidthError('{} > {}'.format(im.width, max_width)) | ||||||
|  |  | ||||||
|  |             if center: | ||||||
|  |                 im.center(max_width) | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             # If the printer's pixel width is not known, print anyways... |             # If the printer's pixel width is not known, print anyways... | ||||||
|             pass |             pass | ||||||
| @@ -173,7 +178,8 @@ class Escpos(object): | |||||||
|         header = self._int_low_high(len(data) + 2, 2) |         header = self._int_low_high(len(data) + 2, 2) | ||||||
|         self._raw(GS + b'(L' + header + m + fn + data) |         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 |         """ Print QR Code for the provided string | ||||||
|  |  | ||||||
|         :param content: The content of the code. Numeric data will be more efficiently compacted. |         :param content: The content of the code. Numeric data will be more efficiently compacted. | ||||||
| @@ -185,6 +191,7 @@ class Escpos(object): | |||||||
|             by all printers). |             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 |         :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) |             printer (Default) | ||||||
|  |         :param center: Centers the code *default:* False | ||||||
|         """ |         """ | ||||||
|         # Basic validation |         # Basic validation | ||||||
|         if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: |         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_code.make(fit=True) | ||||||
|             qr_img = qr_code.make_image() |             qr_img = qr_code.make_image() | ||||||
|             im = qr_img._img.convert("RGB") |             im = qr_img._img.convert("RGB") | ||||||
|  |  | ||||||
|             # Convert the RGB image in printable image |             # Convert the RGB image in printable image | ||||||
|             self.text('\n') |             self.text('\n') | ||||||
|             self.image(im) |             self.image(im, center=center) | ||||||
|             self.text('\n') |             self.text('\n') | ||||||
|             self.text('\n') |             self.text('\n') | ||||||
|             return |             return | ||||||
|  |  | ||||||
|  |         if center: | ||||||
|  |             raise NotImplementedError("Centering not implemented for native QR rendering") | ||||||
|  |  | ||||||
|         # Native 2D code printing |         # Native 2D code printing | ||||||
|         cn = b'1'  # Code type for QR code |         cn = b'1'  # Code type for QR code | ||||||
|         # Select model: 1, 2 or micro. |         # Select model: 1, 2 or micro. | ||||||
|   | |||||||
| @@ -115,3 +115,19 @@ class EscposImage(object): | |||||||
|             box = (left, upper, right, lower) |             box = (left, upper, right, lower) | ||||||
|             fragments.append(self.img_original.crop(box)) |             fragments.append(self.img_original.crop(box)) | ||||||
|         return fragments |         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 | ||||||
|   | |||||||
| @@ -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') |     assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00') | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_width_too_large(): | @pytest.fixture | ||||||
|     """ | def dummy_with_width(): | ||||||
|     Test printing an image that is too large in width. |  | ||||||
|     """ |  | ||||||
|     instance = printer.Dummy() |     instance = printer.Dummy() | ||||||
|     instance.profile.profile_data = { |     instance.profile.profile_data = { | ||||||
|         'media': { |         '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): |     with pytest.raises(ImageWidthError): | ||||||
|         instance.image(Image.new("RGB", (385, 200))) |         instance.image(Image.new("RGB", (385, 200))) | ||||||
|  |  | ||||||
|     instance.image(Image.new("RGB", (384, 200))) |     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) | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ from __future__ import print_function | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  |  | ||||||
| from nose.tools import raises | from nose.tools import raises | ||||||
|  | import pytest | ||||||
|  |  | ||||||
| import escpos.printer as printer | import escpos.printer as printer | ||||||
| from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1 | 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' |         b'(k\x07\x001P01234\x1d(k\x03\x001Q0' | ||||||
|     assert(instance.output == expected) |     assert(instance.output == expected) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_empty(): | def test_empty(): | ||||||
|     """Test QR printing blank code""" |     """Test QR printing blank code""" | ||||||
|     instance = printer.Dummy() |     instance = printer.Dummy() | ||||||
| @@ -99,3 +100,13 @@ def test_image_invalid_model(): | |||||||
|     """Test unsupported QR model as image""" |     """Test unsupported QR model as image""" | ||||||
|     instance = printer.Dummy() |     instance = printer.Dummy() | ||||||
|     instance.qr("1234", native=False, model=QR_MODEL_1) |     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) | ||||||
| @@ -13,6 +13,7 @@ from __future__ import division | |||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import pytest | ||||||
| import mock | import mock | ||||||
|  |  | ||||||
| from escpos.printer import Dummy | from escpos.printer import Dummy | ||||||
| @@ -30,3 +31,12 @@ def test_type_of_object_passed_to_image_function(img_function): | |||||||
|     d.qr("LoremIpsum") |     d.qr("LoremIpsum") | ||||||
|     args, kwargs = img_function.call_args |     args, kwargs = img_function.call_args | ||||||
|     assert isinstance(args[0], Image.Image) |     assert isinstance(args[0], Image.Image) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def instance(): | ||||||
|  |     return Dummy() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_center(instance): | ||||||
|  |     instance.qr("LoremIpsum", center=True) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user