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:
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)
|
||||||
|
|
Loading…
Reference in New Issue