Merge branch 'capabilities' into text-encoding
This commit is contained in:
commit
f6ce7e45da
|
@ -34,3 +34,7 @@ before_install:
|
||||||
script:
|
script:
|
||||||
- tox
|
- tox
|
||||||
- codecov
|
- codecov
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
on_success: never
|
||||||
|
on_failure: change
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
*********
|
*********
|
||||||
Changelog
|
Changelog
|
||||||
*********
|
*********
|
||||||
2016-08-?? - Version 2.?.? - "Death and Gravity"
|
|
||||||
|
2016-08-?? - Version 2.?.? - "?"
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
changes
|
changes
|
||||||
|
@ -14,6 +15,32 @@ contributors
|
||||||
- Patrick Kanzler (with code by Frédéric Van der Essen)
|
- Patrick Kanzler (with code by Frédéric Van der Essen)
|
||||||
|
|
||||||
|
|
||||||
|
2016-08-10 - Version 2.1.3 - "Ethics Gradient"
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
changes
|
||||||
|
^^^^^^^
|
||||||
|
- configure readthedocs and travis
|
||||||
|
- update doc with hint on image preprocessing
|
||||||
|
- add fix for printing large images (by splitting them into multiple images)
|
||||||
|
|
||||||
|
contributors
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
- Patrick Kanzler
|
||||||
|
|
||||||
|
2016-08-02 - Version 2.1.2 - "Death and Gravity"
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
changes
|
||||||
|
^^^^^^^
|
||||||
|
- fix File-printer: flush after every call of _raw()
|
||||||
|
- fix lists in documentation
|
||||||
|
- fix CODE128: by adding the control character to the barcode-selection-sequence the barcode became unusable
|
||||||
|
|
||||||
|
contributors
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
- Patrick Kanzler
|
||||||
|
|
||||||
2016-08-02 - Version 2.1.1 - "Contents May Differ"
|
2016-08-02 - Version 2.1.1 - "Contents May Differ"
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ Content
|
||||||
user/raspi
|
user/raspi
|
||||||
user/todo
|
user/todo
|
||||||
user/usage
|
user/usage
|
||||||
|
user/barcode
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
Printing Barcodes
|
||||||
|
-----------------
|
||||||
|
:Last Reviewed: 2016-07-31
|
||||||
|
|
||||||
|
Most ESC/POS-printers implement barcode-printing.
|
||||||
|
The barcode-commandset is implemented in the barcode-method.
|
||||||
|
For a list of compatible barcodes you should check the manual of your printer.
|
||||||
|
As a rule of thumb: even older Epson-models support most 1D-barcodes.
|
||||||
|
To be sure just try some implementations and have a look at the notices below.
|
||||||
|
|
||||||
|
barcode-method
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
The barcode-method is rather low-level and orients itself on the implementation of ESC/POS.
|
||||||
|
In the future this class could be supplemented by a high-level class that helps the user generating the payload.
|
||||||
|
|
||||||
|
.. py:currentmodule:: escpos.escpos
|
||||||
|
|
||||||
|
.. automethod:: Escpos.barcode
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
CODE128
|
||||||
|
~~~~~~~
|
||||||
|
Code128 barcodes need a certain format.
|
||||||
|
For now the user has to make sure that the payload is correct.
|
||||||
|
For alphanumeric CODE128 you have to preface your payload with `{B`.
|
||||||
|
|
||||||
|
.. code-block:: Python
|
||||||
|
|
||||||
|
from escpos.printer import Dummy, Serial
|
||||||
|
p = Serial()
|
||||||
|
# print CODE128 012ABCDabcd
|
||||||
|
p.barcode("{B012ABCDabcd", "CODE128", function_type="B")
|
||||||
|
|
||||||
|
A very good description on CODE128 is also on `Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.
|
|
@ -218,3 +218,34 @@ Here you can download an example, that will print a set of common barcodes:
|
||||||
|
|
||||||
* :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_
|
* :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_
|
||||||
|
|
||||||
|
Hint: preprocess printing
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Printing images directly to the printer is rather slow.
|
||||||
|
One factor that slows down the process is the transmission over e.g. serial port.
|
||||||
|
|
||||||
|
Apart from configuring your printer to use the maximum baudrate (in the case of serial-printers), there is not much
|
||||||
|
that you can do.
|
||||||
|
However you could use the :py:class:`escpos.printer.Dummy`-printer to preprocess your image.
|
||||||
|
This is probably best explained by an example:
|
||||||
|
|
||||||
|
.. code-block:: Python
|
||||||
|
|
||||||
|
from escpos.printer import Serial, Dummy
|
||||||
|
|
||||||
|
p = Serial()
|
||||||
|
d = Dummy()
|
||||||
|
|
||||||
|
# create ESC/POS for the print job, this should go really fast
|
||||||
|
d.text("This is my image:\n")
|
||||||
|
d.image("funny_cat.png")
|
||||||
|
d.cut()
|
||||||
|
|
||||||
|
# send code to printer
|
||||||
|
p._raw(d.output)
|
||||||
|
|
||||||
|
This way you could also store the code in a file and print later.
|
||||||
|
You could then for example print the code from another process than your main-program and thus reduce the waiting time.
|
||||||
|
(Of course this will not make the printer print faster.)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
formats:
|
||||||
|
- pdf
|
||||||
|
- epub
|
||||||
|
requirements_file: doc/requirements.txt
|
||||||
|
python:
|
||||||
|
version: 2
|
||||||
|
setup_py_install: true
|
|
@ -0,0 +1,83 @@
|
||||||
|
import re
|
||||||
|
from os import path
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
with open(path.join(path.dirname(__file__), 'capabilities.yml')) as f:
|
||||||
|
PROFILES = yaml.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
class Profile(object):
|
||||||
|
|
||||||
|
profile_data = {}
|
||||||
|
|
||||||
|
def __init__(self, columns=None):
|
||||||
|
self.default_columns = columns
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return self.profile_data[name]
|
||||||
|
|
||||||
|
def get_columns(self, font):
|
||||||
|
""" Return the number of columns for the given font.
|
||||||
|
"""
|
||||||
|
if self.default_columns:
|
||||||
|
return self.default_columns
|
||||||
|
|
||||||
|
if 'columnConfigs' in self.profile_data:
|
||||||
|
columns_def = self.columnConfigs[self.defaultColumnConfig]
|
||||||
|
|
||||||
|
elif 'columns' in self.profile_data:
|
||||||
|
columns_def = self.columns
|
||||||
|
|
||||||
|
if isinstance(columns_def, int):
|
||||||
|
return columns_def
|
||||||
|
return columns_def[font]
|
||||||
|
|
||||||
|
|
||||||
|
def get_profile(name=None, **kwargs):
|
||||||
|
if isinstance(name, Profile):
|
||||||
|
return name
|
||||||
|
|
||||||
|
clazz = get_profile_class(name or 'default')
|
||||||
|
return clazz(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CLASS_CACHE = {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_profile_class(name):
|
||||||
|
if not name in CLASS_CACHE:
|
||||||
|
profile_data = resolve_profile_data(name)
|
||||||
|
class_name = '%sProfile' % clean(name)
|
||||||
|
new_class = type(class_name, (Profile,), {'profile_data': profile_data})
|
||||||
|
CLASS_CACHE[name] = new_class
|
||||||
|
|
||||||
|
return CLASS_CACHE[name]
|
||||||
|
|
||||||
|
|
||||||
|
def clean(s):
|
||||||
|
# Remove invalid characters
|
||||||
|
s = re.sub('[^0-9a-zA-Z_]', '', s)
|
||||||
|
# Remove leading characters until we find a letter or underscore
|
||||||
|
s = re.sub('^[^a-zA-Z_]+', '', s)
|
||||||
|
return str(s)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_profile_data(name):
|
||||||
|
data = PROFILES[name]
|
||||||
|
inherits = data.get('inherits')
|
||||||
|
if not inherits:
|
||||||
|
return data
|
||||||
|
|
||||||
|
if not isinstance(inherits, (tuple, list)):
|
||||||
|
inherits = [inherits]
|
||||||
|
|
||||||
|
merged = {}
|
||||||
|
for base in reversed(inherits):
|
||||||
|
base_data = resolve_profile_data(base)
|
||||||
|
merged.update(base_data)
|
||||||
|
merged.update(data)
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
# Description of the format
|
||||||
|
abstract:
|
||||||
|
# Defines non-standard code pages that the printer supports, but
|
||||||
|
# that we won't find in Python's encoding system. If you define one
|
||||||
|
# here, don't forget to add it to codePageMap to assign it to a slot.
|
||||||
|
customCodePages:
|
||||||
|
sample:
|
||||||
|
|
||||||
|
# This maps the indexed code page slots to code page names.
|
||||||
|
# Often, the slot assignment is the same, but the device only
|
||||||
|
# supports a subset.
|
||||||
|
codePageMap:
|
||||||
|
0: "CP437"
|
||||||
|
1: "CP932"
|
||||||
|
3: "sample"
|
||||||
|
|
||||||
|
# Maybe not all of the codepages in the map are supported. This
|
||||||
|
# is for subprofiles to select which ones the device knows.
|
||||||
|
codePages: [sample, cp932]
|
||||||
|
|
||||||
|
|
||||||
|
# Many recent Epson-branded thermal receipt printers.
|
||||||
|
default:
|
||||||
|
columns: 42
|
||||||
|
|
||||||
|
barcodeB: true
|
||||||
|
bitImage: true
|
||||||
|
graphics: true
|
||||||
|
starCommands: false
|
||||||
|
qrCode: true
|
||||||
|
|
||||||
|
customCodePages:
|
||||||
|
TCVN-3-1: [
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ăâêôơưđ ",
|
||||||
|
" àảãáạ ằẳẵắ ",
|
||||||
|
" ặầẩẫấậè ẻẽ",
|
||||||
|
"éẹềểễếệìỉ ĩíịò",
|
||||||
|
" ỏõóọồổỗốộờởỡớợù",
|
||||||
|
" ủũúụừửữứựỳỷỹýỵ ",
|
||||||
|
]
|
||||||
|
TCVN-3-2: [
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ĂÂ Ð ÊÔƠƯ ",
|
||||||
|
" ÀẢÃÁẠ ẰẲẴẮ ",
|
||||||
|
" ẶẦẨẪẤẬÈ ẺẼ",
|
||||||
|
"ÉẸỀỂỄẾỆÌỈ ĨÍỊÒ",
|
||||||
|
" ỎÕÓỌỒỔỖỐỘỜỞỠỚỢÙ",
|
||||||
|
" ỦŨÚỤỪỬỮỨỰỲỶỸÝỴ "
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Commented-out slots are TODO (might just need uncomment, might
|
||||||
|
# need verification/research)
|
||||||
|
codePageMap:
|
||||||
|
0: "CP437"
|
||||||
|
1: "CP932"
|
||||||
|
2: "CP850"
|
||||||
|
3: "CP860"
|
||||||
|
4: "CP863"
|
||||||
|
5: "CP865"
|
||||||
|
#6: // Hiragana
|
||||||
|
#7: // One-pass printing Kanji characters
|
||||||
|
#8: // Page 8 [One-pass printing Kanji characters]
|
||||||
|
11: "CP851"
|
||||||
|
12: "CP853"
|
||||||
|
13: "CP857"
|
||||||
|
14: "CP737"
|
||||||
|
15: "ISO8859_7"
|
||||||
|
16: "CP1252"
|
||||||
|
17: "CP866"
|
||||||
|
18: "CP852"
|
||||||
|
19: "CP858"
|
||||||
|
#20: // Thai Character Code 42
|
||||||
|
#21: // Thai Character Code 1"
|
||||||
|
#22: // Thai Character Code 13
|
||||||
|
#23: // Thai Character Code 14
|
||||||
|
#24: // Thai Character Code 16
|
||||||
|
#25: // Thai Character Code 17
|
||||||
|
#26: // Thai Character Code 18
|
||||||
|
30: 'TCVN-3-1', # TCVN-3: Vietnamese
|
||||||
|
31: 'TCVN-3-2', # TCVN-3: Vietnamese
|
||||||
|
32: "CP720"
|
||||||
|
33: "CP775"
|
||||||
|
34: "CP855"
|
||||||
|
35: "CP861"
|
||||||
|
36: "CP862"
|
||||||
|
37: "CP864"
|
||||||
|
38: "CP869"
|
||||||
|
39: "ISO8859_2"
|
||||||
|
40: "ISO8859_15"
|
||||||
|
41: "CP1098"
|
||||||
|
42: "CP774"
|
||||||
|
43: "CP772"
|
||||||
|
44: "CP1125"
|
||||||
|
45: "CP1250"
|
||||||
|
46: "CP1251"
|
||||||
|
47: "CP1253"
|
||||||
|
48: "CP1254"
|
||||||
|
49: "CP1255"
|
||||||
|
50: "CP1256"
|
||||||
|
51: "CP1257"
|
||||||
|
52: "CP1258"
|
||||||
|
53: "RK1048"
|
||||||
|
#66: // Devanagari
|
||||||
|
#67: // Bengali
|
||||||
|
#68: // Tamil
|
||||||
|
#69: // Telugu
|
||||||
|
#70: // Assamese
|
||||||
|
#71: // Oriya
|
||||||
|
#72: // Kannada
|
||||||
|
#73: // Malayalam
|
||||||
|
#74: // Gujarati
|
||||||
|
#75: // Punjabi
|
||||||
|
#82: // Marathi
|
||||||
|
#254:
|
||||||
|
#255:
|
||||||
|
|
||||||
|
|
||||||
|
# Designed for non-Epson printers sold online. Without knowing
|
||||||
|
# their character encoding table, only CP437 output is assumed,
|
||||||
|
# and graphics() calls will be disabled, as it usually prints junk
|
||||||
|
# on these models.
|
||||||
|
simple:
|
||||||
|
codePages:
|
||||||
|
- cp437
|
||||||
|
graphics: false
|
||||||
|
|
||||||
|
|
||||||
|
# Profile for Star-branded printers.
|
||||||
|
star:
|
||||||
|
inherits: default
|
||||||
|
starCommands: true
|
||||||
|
|
||||||
|
|
||||||
|
epson:
|
||||||
|
inherits: default
|
||||||
|
manufacturer: "Epson"
|
||||||
|
|
||||||
|
|
||||||
|
"P-822D":
|
||||||
|
inherits: default
|
||||||
|
graphics: false
|
||||||
|
|
||||||
|
|
||||||
|
# http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf
|
||||||
|
"TM-T88II":
|
||||||
|
inherits: epson
|
||||||
|
columns:
|
||||||
|
a: 42
|
||||||
|
b: 56
|
||||||
|
codePages:
|
||||||
|
- PC437 # 0
|
||||||
|
- Katakana # 1
|
||||||
|
- PC850 # 2
|
||||||
|
- PC860 # 3
|
||||||
|
- PC863 # 4
|
||||||
|
- PC865 # 5
|
||||||
|
- PC858 # 19
|
||||||
|
- blank
|
||||||
|
|
||||||
|
# http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf
|
||||||
|
"TM-T88III":
|
||||||
|
inherits: epson
|
||||||
|
columns:
|
||||||
|
a: 42
|
||||||
|
b: 56
|
||||||
|
codePages:
|
||||||
|
- PC437 # 0
|
||||||
|
- Katakana # 1
|
||||||
|
- PC850 # 2
|
||||||
|
- PC860 # 3
|
||||||
|
- PC863 # 4
|
||||||
|
- PC865 # 5
|
||||||
|
- WPC1252 # 16
|
||||||
|
- PC866 # 17
|
||||||
|
- PC852 # 18
|
||||||
|
- PC858 # 19
|
||||||
|
- blank
|
||||||
|
|
||||||
|
|
||||||
|
"TM-P80":
|
||||||
|
inherits: epson
|
||||||
|
defaultColumnConfig: default
|
||||||
|
columnConfigs:
|
||||||
|
default: {'a': 48, 'b': 64, 'kanji': 24}
|
||||||
|
'42_emulation': {'a': 42, 'b': 60, 'kanji': 21}
|
||||||
|
|
||||||
|
|
||||||
|
"TM-P60II 2":
|
||||||
|
inherits: epson
|
||||||
|
columnConfigs:
|
||||||
|
'58mm_paper': {'a': 35, 'b': 42, 'c': 52}
|
||||||
|
'60mm_paper': {'a': 36, 'b': 43, 'c': 54}
|
||||||
|
|
||||||
|
|
||||||
|
"TM-P20 2":
|
||||||
|
inherits: epson
|
||||||
|
# Has 5 fonts!
|
||||||
|
|
||||||
|
"TM-T90":
|
||||||
|
inherits: epson
|
||||||
|
colors:
|
||||||
|
- black
|
||||||
|
- red
|
|
@ -240,10 +240,7 @@ BARCODE_TYPE_B = {
|
||||||
'NW7': _SET_BARCODE_TYPE(71),
|
'NW7': _SET_BARCODE_TYPE(71),
|
||||||
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
|
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
|
||||||
'CODE93': _SET_BARCODE_TYPE(72),
|
'CODE93': _SET_BARCODE_TYPE(72),
|
||||||
# These are all the same barcode, but using different charcter sets
|
'CODE128': _SET_BARCODE_TYPE(73),
|
||||||
'CODE128A': _SET_BARCODE_TYPE(73) + b'{A', # CODE128 character set A
|
|
||||||
'CODE128B': _SET_BARCODE_TYPE(73) + b'{B', # CODE128 character set B
|
|
||||||
'CODE128C': _SET_BARCODE_TYPE(73) + b'{C', # CODE128 character set C
|
|
||||||
'GS1-128': _SET_BARCODE_TYPE(74),
|
'GS1-128': _SET_BARCODE_TYPE(74),
|
||||||
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
|
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
|
||||||
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),
|
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),
|
||||||
|
|
|
@ -24,6 +24,7 @@ from .magicencode import MagicEncode
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod # abstract base class support
|
from abc import ABCMeta, abstractmethod # abstract base class support
|
||||||
from escpos.image import EscposImage
|
from escpos.image import EscposImage
|
||||||
|
from escpos.capabilities import get_profile
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(ABCMeta)
|
@six.add_metaclass(ABCMeta)
|
||||||
|
@ -35,11 +36,11 @@ class Escpos(object):
|
||||||
"""
|
"""
|
||||||
device = None
|
device = None
|
||||||
|
|
||||||
def __init__(self, columns=32, **kwargs):
|
def __init__(self, profile=None, **kwargs):
|
||||||
""" Initialize ESCPOS Printer
|
""" Initialize ESCPOS Printer
|
||||||
|
|
||||||
:param columns: Text columns used by the printer. Defaults to 32."""
|
:param profile: Printer profile"""
|
||||||
self.columns = columns
|
self.profile = get_profile(profile)
|
||||||
self.magic = MagicEncode(**kwargs)
|
self.magic = MagicEncode(**kwargs)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
@ -57,7 +58,8 @@ class Escpos(object):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
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=1024):
|
||||||
""" 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.
|
||||||
|
@ -77,9 +79,20 @@ class Escpos(object):
|
||||||
:param high_density_vertical: print in high density in vertical direction *default:* True
|
:param high_density_vertical: print in high density in vertical direction *default:* True
|
||||||
: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:* 1024
|
||||||
|
|
||||||
"""
|
"""
|
||||||
im = EscposImage(img_source)
|
im = EscposImage(img_source)
|
||||||
|
|
||||||
|
if im.height > fragment_height:
|
||||||
|
fragments = im.split(fragment_height)
|
||||||
|
for fragment in fragments:
|
||||||
|
self.image(fragment,
|
||||||
|
high_density_vertical=high_density_vertical,
|
||||||
|
high_density_horizontal=high_density_horizontal,
|
||||||
|
impl=impl,
|
||||||
|
fragment_height=fragment_height)
|
||||||
|
return
|
||||||
|
|
||||||
if impl == "bitImageRaster":
|
if impl == "bitImageRaster":
|
||||||
# GS v 0, raster format bit image
|
# GS v 0, raster format bit image
|
||||||
|
@ -362,7 +375,7 @@ class Escpos(object):
|
||||||
txt = six.text_type(txt)
|
txt = six.text_type(txt)
|
||||||
self._raw(self.magic.encode_text(txt=txt))
|
self._raw(self.magic.encode_text(txt=txt))
|
||||||
|
|
||||||
def block_text(self, txt, columns=None):
|
def block_text(self, txt, font=None, columns=None):
|
||||||
""" Text is printed wrapped to specified columns
|
""" Text is printed wrapped to specified columns
|
||||||
|
|
||||||
Text has to be encoded in unicode.
|
Text has to be encoded in unicode.
|
||||||
|
@ -371,7 +384,7 @@ class Escpos(object):
|
||||||
:param columns: amount of columns
|
:param columns: amount of columns
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
col_count = self.columns if columns is None else columns
|
col_count = self.profile.get_columns(font) if columns is None else columns
|
||||||
self.text(textwrap.fill(txt, col_count))
|
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,
|
def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9, invert=False, smooth=False,
|
||||||
|
|
|
@ -8,6 +8,12 @@ This module contains the image format handler :py:class:`EscposImage`.
|
||||||
:license: GNU GPL v3
|
:license: GNU GPL v3
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import math
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +36,9 @@ class EscposImage(object):
|
||||||
else:
|
else:
|
||||||
img_original = Image.open(img_source)
|
img_original = Image.open(img_source)
|
||||||
|
|
||||||
|
# store image for eventual further processing (splitting)
|
||||||
|
self.img_original = img_original
|
||||||
|
|
||||||
# Convert to white RGB background, paste over white background
|
# Convert to white RGB background, paste over white background
|
||||||
# to strip alpha.
|
# to strip alpha.
|
||||||
img_original = img_original.convert('RGBA')
|
img_original = img_original.convert('RGBA')
|
||||||
|
@ -88,3 +97,21 @@ class EscposImage(object):
|
||||||
Convert image to raster-format binary
|
Convert image to raster-format binary
|
||||||
"""
|
"""
|
||||||
return self._im.tobytes()
|
return self._im.tobytes()
|
||||||
|
|
||||||
|
def split(self, fragment_height):
|
||||||
|
"""
|
||||||
|
Split an image into multiple fragments after fragment_height pixels
|
||||||
|
|
||||||
|
:param fragment_height: height of fragment
|
||||||
|
:return: list of PIL objects
|
||||||
|
"""
|
||||||
|
passes = int(math.ceil(self.height/fragment_height))
|
||||||
|
fragments = []
|
||||||
|
for n in range(0, passes):
|
||||||
|
left = 0
|
||||||
|
right = self.width
|
||||||
|
upper = n * fragment_height
|
||||||
|
lower = min((n + 1) * fragment_height, self.height)
|
||||||
|
box = (left, upper, right, lower)
|
||||||
|
fragments.append(self.img_original.crop(box))
|
||||||
|
return fragments
|
||||||
|
|
|
@ -130,3 +130,12 @@ def test_graphics_transparent():
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.image('test/resources/black_transparent.png', impl="graphics")
|
instance.image('test/resources/black_transparent.png', impl="graphics")
|
||||||
assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002')
|
assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002')
|
||||||
|
|
||||||
|
|
||||||
|
def test_large_graphics():
|
||||||
|
"""
|
||||||
|
Test whether 'large' graphics that induce a fragmentation are handled correctly.
|
||||||
|
"""
|
||||||
|
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')
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""tests for the non-native part of qr()
|
||||||
|
|
||||||
|
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from escpos.printer import Dummy
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('escpos.printer.Dummy.image', spec=Dummy)
|
||||||
|
def test_type_of_object_passed_to_image_function(img_function):
|
||||||
|
"""
|
||||||
|
Test the type of object that is passed to the image function during non-native qr-printing.
|
||||||
|
|
||||||
|
The type should be PIL.Image
|
||||||
|
"""
|
||||||
|
d = Dummy()
|
||||||
|
d.qr("LoremIpsum")
|
||||||
|
args, kwargs = img_function.call_args
|
||||||
|
assert isinstance(args[0], Image.Image)
|
|
@ -15,9 +15,10 @@ from __future__ import unicode_literals
|
||||||
import mock
|
import mock
|
||||||
from hypothesis import given
|
from hypothesis import given
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
|
from escpos.printer import Dummy
|
||||||
import escpos.printer as printer
|
import escpos.printer as printer
|
||||||
|
|
||||||
|
|
||||||
@given(text=st.text())
|
@given(text=st.text())
|
||||||
def test_function_text_dies_ist_ein_test_lf(text):
|
def test_function_text_dies_ist_ein_test_lf(text):
|
||||||
"""test the text printing function with simple string and compare output"""
|
"""test the text printing function with simple string and compare output"""
|
||||||
|
@ -25,3 +26,11 @@ def test_function_text_dies_ist_ein_test_lf(text):
|
||||||
instance.magic.encode_text = mock.Mock()
|
instance.magic.encode_text = mock.Mock()
|
||||||
instance.text(text)
|
instance.text(text)
|
||||||
instance.magic.encode_text.assert_called_with(txt=text)
|
instance.magic.encode_text.assert_called_with(txt=text)
|
||||||
|
|
||||||
|
|
||||||
|
def test_block_text():
|
||||||
|
printer = Dummy()
|
||||||
|
printer.block_text(
|
||||||
|
"All the presidents men were eating falafel for breakfast.", font='a')
|
||||||
|
assert printer.output == \
|
||||||
|
'All the presidents men were eating falafel\nfor breakfast.'
|
||||||
|
|
|
@ -43,6 +43,20 @@ def test_image_white():
|
||||||
_load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00'])
|
_load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_split():
|
||||||
|
"""
|
||||||
|
test whether the split-function works as expected
|
||||||
|
"""
|
||||||
|
im = EscposImage('test/resources/black_white.png')
|
||||||
|
(upper_part, lower_part) = im.split(1)
|
||||||
|
upper_part = EscposImage(upper_part)
|
||||||
|
lower_part = EscposImage(lower_part)
|
||||||
|
assert(upper_part.width == lower_part.width == 2)
|
||||||
|
assert(upper_part.height == lower_part.height == 1)
|
||||||
|
assert(upper_part.to_raster_format() == b'\xc0')
|
||||||
|
assert(lower_part.to_raster_format() == b'\x00')
|
||||||
|
|
||||||
|
|
||||||
def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected):
|
def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected):
|
||||||
"""
|
"""
|
||||||
Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
|
Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
|
||||||
|
|
Loading…
Reference in New Issue