mirror of
https://github.com/python-escpos/python-escpos
synced 2025-08-24 09:03:34 +00:00
Merge branch 'capabilities' into text-encoding
This commit is contained in:
83
src/escpos/capabilities.py
Normal file
83
src/escpos/capabilities.py
Normal file
@@ -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
|
||||
|
||||
|
207
src/escpos/capabilities.yml
Normal file
207
src/escpos/capabilities.yml
Normal file
@@ -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),
|
||||
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
|
||||
'CODE93': _SET_BARCODE_TYPE(72),
|
||||
# These are all the same barcode, but using different charcter sets
|
||||
'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
|
||||
'CODE128': _SET_BARCODE_TYPE(73),
|
||||
'GS1-128': _SET_BARCODE_TYPE(74),
|
||||
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
|
||||
'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 escpos.image import EscposImage
|
||||
from escpos.capabilities import get_profile
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
@@ -35,11 +36,11 @@ class Escpos(object):
|
||||
"""
|
||||
device = None
|
||||
|
||||
def __init__(self, columns=32, **kwargs):
|
||||
def __init__(self, profile=None, **kwargs):
|
||||
""" Initialize ESCPOS Printer
|
||||
|
||||
:param columns: Text columns used by the printer. Defaults to 32."""
|
||||
self.columns = columns
|
||||
:param profile: Printer profile"""
|
||||
self.profile = get_profile(profile)
|
||||
self.magic = MagicEncode(**kwargs)
|
||||
|
||||
def __del__(self):
|
||||
@@ -57,7 +58,8 @@ class Escpos(object):
|
||||
"""
|
||||
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
|
||||
|
||||
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_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:* 1024
|
||||
|
||||
"""
|
||||
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":
|
||||
# GS v 0, raster format bit image
|
||||
@@ -362,7 +375,7 @@ class Escpos(object):
|
||||
txt = six.text_type(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 has to be encoded in unicode.
|
||||
@@ -371,7 +384,7 @@ class Escpos(object):
|
||||
:param columns: amount of columns
|
||||
: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))
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -30,6 +36,9 @@ class EscposImage(object):
|
||||
else:
|
||||
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
|
||||
# to strip alpha.
|
||||
img_original = img_original.convert('RGBA')
|
||||
@@ -88,3 +97,21 @@ class EscposImage(object):
|
||||
Convert image to raster-format binary
|
||||
"""
|
||||
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
|
||||
|
Reference in New Issue
Block a user