Merge branch 'capabilities' into text-encoding
This commit is contained in:
commit
b37f4fc8cc
|
@ -15,6 +15,21 @@ contributors
|
|||
- Patrick Kanzler (with code by Frédéric Van der Essen)
|
||||
|
||||
|
||||
2016-08-26 - Version 2.2.0 - "Fate Amenable To Change"
|
||||
------------------------------------------------------
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- fix improper API-use in qrcode()
|
||||
- change setup.py shebang to make it compatible with virtualenvs.
|
||||
- add constants for sheet mode and colors
|
||||
- support changing the linespacing
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Michael Elsdörfer
|
||||
- Patrick Kanzler
|
||||
|
||||
2016-08-10 - Version 2.1.3 - "Ethics Gradient"
|
||||
----------------------------------------------
|
||||
|
||||
|
|
12
README.rst
12
README.rst
|
@ -54,14 +54,14 @@ The basic usage is:
|
|||
|
||||
.. code:: python
|
||||
|
||||
from escpos import *
|
||||
from escpos.printer import Usb
|
||||
|
||||
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
|
||||
Epson = escpos.Escpos(0x04b8,0x0202,0)
|
||||
Epson.text("Hello World\n")
|
||||
Epson.image("logo.gif")
|
||||
Epson.barcode('1324354657687','EAN13',64,2,'','')
|
||||
Epson.cut()
|
||||
p = Usb(0x04b8,0x0202,0)
|
||||
p.text("Hello World\n")
|
||||
p.image("logo.gif")
|
||||
p.barcode('1324354657687','EAN13',64,2,'','')
|
||||
p.cut()
|
||||
|
||||
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.
|
||||
|
||||
|
|
7
setup.py
7
setup.py
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -83,7 +83,8 @@ setup(
|
|||
platforms='any',
|
||||
package_dir={"": "src"},
|
||||
packages=find_packages(where="src", exclude=["tests", "tests.*"]),
|
||||
package_data={'': ['COPYING']},
|
||||
package_data={'': ['COPYING', 'src/escpos/capabilities.json']},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: Console',
|
||||
|
@ -116,7 +117,7 @@ setup(
|
|||
setup_requires=[
|
||||
'setuptools_scm',
|
||||
],
|
||||
tests_require=['tox', 'nose', 'scripttest', 'mock', 'hypothesis'],
|
||||
tests_require=['tox', 'pytest', 'pytest-cov', 'nose', 'scripttest', 'mock', 'hypothesis'],
|
||||
cmdclass={'test': Tox},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,40 +1,61 @@
|
|||
import re
|
||||
import six
|
||||
from os import path
|
||||
import yaml
|
||||
|
||||
|
||||
with open(path.join(path.dirname(__file__), 'capabilities.yml')) as f:
|
||||
PROFILES = yaml.load(f)
|
||||
# Load external printer database
|
||||
with open(path.join(path.dirname(__file__), 'capabilities.json')) as f:
|
||||
CAPABILITIES = yaml.load(f)
|
||||
PROFILES = CAPABILITIES['profiles']
|
||||
ENCODINGS = CAPABILITIES['encodings']
|
||||
|
||||
|
||||
class Profile(object):
|
||||
class NotSupported(Exception):
|
||||
pass
|
||||
|
||||
|
||||
BARCODE_B = 'barcodeB'
|
||||
|
||||
|
||||
class BaseProfile(object):
|
||||
"""This respresents a printer profile.
|
||||
|
||||
A printer profile knows about the number of columns, supported
|
||||
features, colors and more.
|
||||
"""
|
||||
|
||||
profile_data = {}
|
||||
|
||||
def __init__(self, columns=None):
|
||||
self.default_columns = columns
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.profile_data[name]
|
||||
|
||||
def get_font(self, font):
|
||||
"""Return the escpos index for `font`. Makes sure that
|
||||
the requested `font` is valid.
|
||||
"""
|
||||
font = {'a': 0, 'b': 1}.get(font, font)
|
||||
if not six.text_type(font) in self.fonts:
|
||||
raise NotSupported(
|
||||
'"%s" is not a valid font in the current profile' % font)
|
||||
return font
|
||||
|
||||
def get_columns(self, font):
|
||||
""" Return the number of columns for the given font.
|
||||
"""
|
||||
if self.default_columns:
|
||||
return self.default_columns
|
||||
font = self.get_font(font)
|
||||
return self.fonts[six.text_type(font)]['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 supports(self, feature):
|
||||
"""Return true/false for the given feature.
|
||||
"""
|
||||
return self.features.get(feature)
|
||||
|
||||
|
||||
def get_profile(name=None, **kwargs):
|
||||
"""Get the profile by name; if no name is given, return the
|
||||
default profile.
|
||||
"""
|
||||
if isinstance(name, Profile):
|
||||
return name
|
||||
|
||||
|
@ -42,15 +63,19 @@ def get_profile(name=None, **kwargs):
|
|||
return clazz(**kwargs)
|
||||
|
||||
|
||||
|
||||
CLASS_CACHE = {}
|
||||
|
||||
|
||||
def get_profile_class(name):
|
||||
"""For the given profile name, load the data from the external
|
||||
database, then generate dynamically a class.
|
||||
"""
|
||||
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})
|
||||
profile_data = PROFILES[name]
|
||||
profile_name = clean(name)
|
||||
class_name = '{}{}Profile'.format(
|
||||
profile_name[0].upper(), profile_name[1:])
|
||||
new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data})
|
||||
CLASS_CACHE[name] = new_class
|
||||
|
||||
return CLASS_CACHE[name]
|
||||
|
@ -64,20 +89,21 @@ def clean(s):
|
|||
return str(s)
|
||||
|
||||
|
||||
def resolve_profile_data(name):
|
||||
data = PROFILES[name]
|
||||
inherits = data.get('inherits')
|
||||
if not inherits:
|
||||
return data
|
||||
# For users, who want to provide their profile
|
||||
class Profile(get_profile_class('default')):
|
||||
|
||||
def __init__(self, columns=None, features={}):
|
||||
super(Profile, self).__init__()
|
||||
|
||||
self.columns = columns
|
||||
self.features = features
|
||||
|
||||
def get_columns(self, font):
|
||||
if self.columns is not None:
|
||||
return self.columns
|
||||
|
||||
return super(Profile, self).get_columns(font)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -1,207 +0,0 @@
|
|||
# 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:
|
||||
- cp437 # 0
|
||||
- Katakana # 1
|
||||
- cp850 # 2
|
||||
- cp860 # 3
|
||||
- cp863 # 4
|
||||
- cp865 # 5
|
||||
- cp858 # 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:
|
||||
- CP437 # 0
|
||||
- Katakana # 1
|
||||
- CP850 # 2
|
||||
- CP860 # 3
|
||||
- CP863 # 4
|
||||
- CP865 # 5
|
||||
- PC1252 # 16
|
||||
- CP866 # 17
|
||||
- CP852 # 18
|
||||
- CP858 # 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
|
|
@ -55,11 +55,18 @@ _CUT_PAPER = lambda m: GS + b'V' + m
|
|||
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
|
||||
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
|
||||
|
||||
# Beep
|
||||
BEEP = b'\x07'
|
||||
|
||||
# Panel buttons (e.g. the FEED button)
|
||||
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
|
||||
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
|
||||
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
|
||||
|
||||
# Sheet modes
|
||||
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
|
||||
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
|
||||
|
||||
# Text format
|
||||
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
|
||||
# Printers" and tidy up this stuff too.
|
||||
|
@ -93,14 +100,29 @@ TXT_UNDERL_ON = ESC + b'\x2d\x01' # Underline font 1-dot ON
|
|||
TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON
|
||||
TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF
|
||||
TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON
|
||||
TXT_FONT_A = ESC + b'\x4d\x00' # Font type A
|
||||
TXT_FONT_B = ESC + b'\x4d\x01' # Font type B
|
||||
TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification
|
||||
TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering
|
||||
TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification
|
||||
TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON
|
||||
TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF
|
||||
|
||||
# Fonts
|
||||
SET_FONT = lambda n: ESC + b'\x4d' + n
|
||||
TXT_FONT_A = SET_FONT(b'\x00') # Font type A
|
||||
TXT_FONT_B = SET_FONT(b'\x01') # Font type B
|
||||
|
||||
# Text colors
|
||||
TXT_COLOR_BLACK = ESC + b'\x72\x00' # Default Color
|
||||
TXT_COLOR_RED = ESC + b'\x72\x01' # Alternative Color (Usually Red)
|
||||
|
||||
# Spacing
|
||||
LINESPACING_RESET = ESC + b'2'
|
||||
LINESPACING_FUNCS = {
|
||||
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85
|
||||
360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255
|
||||
180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255
|
||||
}
|
||||
|
||||
|
||||
CODEPAGE_CHANGE = ESC + b'\x74'
|
||||
# Char code table
|
||||
|
|
|
@ -24,7 +24,7 @@ from .magicencode import MagicEncode
|
|||
|
||||
from abc import ABCMeta, abstractmethod # abstract base class support
|
||||
from escpos.image import EscposImage
|
||||
from escpos.capabilities import get_profile
|
||||
from escpos.capabilities import get_profile, BARCODE_B
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
|
@ -232,7 +232,8 @@ class Escpos(object):
|
|||
else:
|
||||
self.magic.force_encoding(code)
|
||||
|
||||
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", align_ct=True, function_type="A"):
|
||||
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A",
|
||||
align_ct=True, function_type=None):
|
||||
""" Print Barcode
|
||||
|
||||
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to
|
||||
|
@ -303,14 +304,40 @@ class Escpos(object):
|
|||
issued.
|
||||
:type align_ct: bool
|
||||
|
||||
:param function_type: Choose between ESCPOS function type A or B, depending on printer support and desired
|
||||
barcode.
|
||||
:param function_type: Choose between ESCPOS function type A or B,
|
||||
depending on printer support and desired barcode. If not given,
|
||||
the printer will attempt to automatically choose the correct
|
||||
function based on the current profile.
|
||||
*default*: A
|
||||
|
||||
:raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeTypeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeCodeError`
|
||||
"""
|
||||
if function_type is None:
|
||||
# Choose the function type automatically.
|
||||
if bc in BARCODE_TYPES['A']:
|
||||
function_type = 'A'
|
||||
else:
|
||||
if bc in BARCODE_TYPES['B']:
|
||||
if not self.profile.supports(BARCODE_B):
|
||||
raise BarcodeTypeError((
|
||||
"Barcode type '{bc} not supported for "
|
||||
"the current printer profile").format(bc=bc))
|
||||
function_type = 'B'
|
||||
else:
|
||||
raise BarcodeTypeError((
|
||||
"Barcode type '{bc} is not valid").format(bc=bc))
|
||||
|
||||
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||
if bc.upper() not in bc_types.keys():
|
||||
raise BarcodeTypeError((
|
||||
"Barcode type '{bc}' not valid for barcode function type "
|
||||
"{function_type}").format(
|
||||
bc=bc,
|
||||
function_type=function_type,
|
||||
))
|
||||
|
||||
# Align Bar Code()
|
||||
if align_ct:
|
||||
self._raw(TXT_ALIGN_CT)
|
||||
|
@ -339,14 +366,6 @@ class Escpos(object):
|
|||
else: # DEFAULT POSITION: BELOW
|
||||
self._raw(BARCODE_TXT_BLW)
|
||||
|
||||
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||
if bc.upper() not in bc_types.keys():
|
||||
# TODO: Raise a better error, or fix the message of this error type
|
||||
raise BarcodeTypeError("Barcode type {bc} not valid for barcode function type {function_type}".format(
|
||||
bc=bc,
|
||||
function_type=function_type,
|
||||
))
|
||||
|
||||
self._raw(bc_types[bc.upper()])
|
||||
|
||||
if function_type.upper() == "B":
|
||||
|
@ -385,8 +404,8 @@ class Escpos(object):
|
|||
col_count = self.profile.get_columns(font) if columns is None else columns
|
||||
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,
|
||||
flip=False):
|
||||
def set(self, align='left', font='a', text_type='normal', width=1,
|
||||
height=1, density=9, invert=False, smooth=False, flip=False):
|
||||
""" Set text properties by sending them to the printer
|
||||
|
||||
:param align: horizontal position for text, possible values are:
|
||||
|
@ -396,7 +415,9 @@ class Escpos(object):
|
|||
* RIGHT
|
||||
|
||||
*default*: LEFT
|
||||
:param font: font type, possible values are A or B, *default*: A
|
||||
|
||||
:param font: font given as an index, a name, or one of the
|
||||
special values 'a' or 'b', refering to fonts 0 and 1.
|
||||
:param text_type: text type, possible values are:
|
||||
|
||||
* B for bold
|
||||
|
@ -461,10 +482,8 @@ class Escpos(object):
|
|||
self._raw(TXT_BOLD_OFF)
|
||||
self._raw(TXT_UNDERL_OFF)
|
||||
# Font
|
||||
if font.upper() == "B":
|
||||
self._raw(TXT_FONT_B)
|
||||
else: # DEFAULT FONT: A
|
||||
self._raw(TXT_FONT_A)
|
||||
self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
|
||||
|
||||
# Align
|
||||
if align.upper() == "CENTER":
|
||||
self._raw(TXT_ALIGN_CT)
|
||||
|
@ -499,6 +518,35 @@ class Escpos(object):
|
|||
else:
|
||||
self._raw(TXT_INVERT_OFF)
|
||||
|
||||
def line_spacing(self, spacing=None, divisor=180):
|
||||
""" Set line character spacing.
|
||||
|
||||
If no spacing is given, we reset it to the default.
|
||||
|
||||
There are different commands for setting the line spacing, using
|
||||
a different denominator:
|
||||
|
||||
'+'' line_spacing/360 of an inch, 0 <= line_spacing <= 255
|
||||
'3' line_spacing/180 of an inch, 0 <= line_spacing <= 255
|
||||
'A' line_spacing/60 of an inch, 0 <= line_spacing <= 85
|
||||
|
||||
Some printers may not support all of them. The most commonly
|
||||
available command (using a divisor of 180) is chosen.
|
||||
"""
|
||||
if spacing is None:
|
||||
self._raw(LINESPACING_RESET)
|
||||
return
|
||||
|
||||
if divisor not in LINESPACING_FUNCS:
|
||||
raise ValueError("divisor must be either 360, 180 or 60")
|
||||
if (divisor in [360, 180] \
|
||||
and (not(0 <= spacing <= 255))):
|
||||
raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180")
|
||||
if divisor == 60 and (not(0 <= spacing <= 85)):
|
||||
raise ValueError("spacing must be a int between 0 and 85 when divisor is 60")
|
||||
|
||||
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
|
||||
|
||||
def cut(self, mode=''):
|
||||
""" Cut paper.
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/python
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import escpos.printer as printer
|
||||
from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B
|
||||
from escpos.capabilities import Profile, BARCODE_B
|
||||
from escpos.exceptions import BarcodeTypeError
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bctype,data,expected", [
|
||||
('EAN13', '4006381333931',
|
||||
b'\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00')
|
||||
])
|
||||
def test_barcode(bctype, data, expected):
|
||||
instance = printer.Dummy()
|
||||
instance.barcode(data, bctype)
|
||||
assert instance.output == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bctype,supports_b", [
|
||||
('invalid', True),
|
||||
('CODE128', False),
|
||||
])
|
||||
def test_lacks_support(bctype, supports_b):
|
||||
profile = Profile(features={BARCODE_B: supports_b})
|
||||
instance = printer.Dummy(profile=profile)
|
||||
with pytest.raises(BarcodeTypeError):
|
||||
instance.barcode('test', bctype)
|
||||
|
||||
assert instance.output == b''
|
|
@ -16,13 +16,12 @@ import mock
|
|||
from hypothesis import given
|
||||
import hypothesis.strategies as st
|
||||
from escpos.printer import Dummy
|
||||
import escpos.printer as printer
|
||||
|
||||
|
||||
@given(text=st.text())
|
||||
def test_function_text_dies_ist_ein_test_lf(text):
|
||||
"""test the text printing function with simple string and compare output"""
|
||||
instance = printer.Dummy()
|
||||
instance = Dummy()
|
||||
instance.magic.encode_text = mock.Mock()
|
||||
instance.text(text)
|
||||
instance.magic.encode_text.assert_called_with(txt=text)
|
||||
|
@ -33,4 +32,4 @@ def test_block_text():
|
|||
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.'
|
||||
b'All the presidents men were eating falafel\nfor breakfast.'
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
from nose.tools import assert_raises
|
||||
from escpos.printer import Dummy
|
||||
|
||||
|
||||
def test_line_spacing_code_gen():
|
||||
printer = Dummy()
|
||||
printer.line_spacing(10)
|
||||
assert printer.output == b'\x1b3\n'
|
||||
|
||||
|
||||
def test_line_spacing_rest():
|
||||
printer = Dummy()
|
||||
printer.line_spacing()
|
||||
assert printer.output == b'\x1b2'
|
||||
|
||||
|
||||
def test_line_spacing_error_handling():
|
||||
printer = Dummy()
|
||||
with assert_raises(ValueError):
|
||||
printer.line_spacing(99, divisor=44)
|
||||
with assert_raises(ValueError):
|
||||
printer.line_spacing(divisor=80, spacing=86)
|
||||
with assert_raises(ValueError):
|
||||
printer.line_spacing(divisor=360, spacing=256)
|
||||
with assert_raises(ValueError):
|
||||
printer.line_spacing(divisor=180, spacing=256)
|
|
@ -0,0 +1,34 @@
|
|||
import pytest
|
||||
from escpos.capabilities import get_profile, NotSupported, BARCODE_B, Profile
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def profile():
|
||||
return get_profile('default')
|
||||
|
||||
|
||||
class TestBaseProfile:
|
||||
|
||||
def test_get_font(self, profile):
|
||||
with pytest.raises(NotSupported):
|
||||
assert profile.get_font('3')
|
||||
assert profile.get_font(1) == 1
|
||||
assert profile.get_font('a') == 0
|
||||
|
||||
def test_supports(self, profile):
|
||||
assert not profile.supports('asdf asdf')
|
||||
assert profile.supports(BARCODE_B)
|
||||
|
||||
def test_get_columns(self, profile):
|
||||
assert profile.get_columns('a') > 5
|
||||
with pytest.raises(NotSupported):
|
||||
assert profile.get_columns('asdfasdf')
|
||||
|
||||
|
||||
class TestCustomProfile:
|
||||
|
||||
def test_columns(self):
|
||||
assert Profile(columns=10).get_columns('sdfasdf') == 10
|
||||
|
||||
def test_features(self):
|
||||
assert Profile(features={'foo': True}).supports('foo')
|
Loading…
Reference in New Issue