2016-08-25 15:30:05 +00:00
|
|
|
import re
|
2016-08-30 10:26:09 +00:00
|
|
|
import six
|
2016-08-25 15:30:05 +00:00
|
|
|
from os import path
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
2016-08-30 10:26:09 +00:00
|
|
|
# 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']
|
2016-08-25 15:30:05 +00:00
|
|
|
|
|
|
|
|
2016-08-30 10:26:09 +00:00
|
|
|
class NotSupported(Exception):
|
|
|
|
pass
|
2016-08-25 15:30:05 +00:00
|
|
|
|
|
|
|
|
2016-08-30 10:53:31 +00:00
|
|
|
BARCODE_B = 'barcodeB'
|
|
|
|
|
|
|
|
|
2016-08-30 10:26:09 +00:00
|
|
|
class BaseProfile(object):
|
|
|
|
"""This respresents a printer profile.
|
|
|
|
|
|
|
|
A printer profile knows about the number of columns, supported
|
|
|
|
features, colors and more.
|
|
|
|
"""
|
|
|
|
|
|
|
|
profile_data = {}
|
2016-08-25 15:30:05 +00:00
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return self.profile_data[name]
|
|
|
|
|
2016-08-30 10:26:09 +00:00
|
|
|
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
|
|
|
|
|
2016-08-25 15:30:05 +00:00
|
|
|
def get_columns(self, font):
|
|
|
|
""" Return the number of columns for the given font.
|
|
|
|
"""
|
2016-08-30 10:26:09 +00:00
|
|
|
font = self.get_font(font)
|
|
|
|
return self.fonts[six.text_type(font)]['columns']
|
2016-08-25 15:30:05 +00:00
|
|
|
|
2016-08-30 10:53:31 +00:00
|
|
|
def supports(self, feature):
|
|
|
|
"""Return true/false for the given feature.
|
|
|
|
"""
|
|
|
|
return self.features.get(feature)
|
|
|
|
|
2016-08-25 15:30:05 +00:00
|
|
|
|
|
|
|
def get_profile(name=None, **kwargs):
|
2016-08-30 10:26:09 +00:00
|
|
|
"""Get the profile by name; if no name is given, return the
|
|
|
|
default profile.
|
|
|
|
"""
|
2016-08-25 15:30:05 +00:00
|
|
|
if isinstance(name, Profile):
|
|
|
|
return name
|
|
|
|
|
|
|
|
clazz = get_profile_class(name or 'default')
|
|
|
|
return clazz(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
CLASS_CACHE = {}
|
|
|
|
|
|
|
|
|
|
|
|
def get_profile_class(name):
|
2016-08-30 10:26:09 +00:00
|
|
|
"""For the given profile name, load the data from the external
|
|
|
|
database, then generate dynamically a class.
|
|
|
|
"""
|
2016-08-25 15:30:05 +00:00
|
|
|
if not name in CLASS_CACHE:
|
2016-08-30 10:26:09 +00:00
|
|
|
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})
|
2016-08-25 15:30:05 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2016-08-30 10:26:09 +00:00
|
|
|
# For users, who want to provide their profile
|
|
|
|
class Profile(get_profile_class('default')):
|
|
|
|
|
2016-08-30 10:53:31 +00:00
|
|
|
def __init__(self, columns=None, features={}):
|
|
|
|
super(Profile, self).__init__()
|
2016-08-30 10:26:09 +00:00
|
|
|
|
|
|
|
self.columns = columns
|
2016-08-30 10:53:31 +00:00
|
|
|
self.features = features
|
2016-08-30 10:26:09 +00:00
|
|
|
|
|
|
|
def get_columns(self, font):
|
|
|
|
if self.columns is not None:
|
|
|
|
return columns
|
|
|
|
|
|
|
|
return super(Profile, self).get_columns(font)
|
2016-08-25 15:30:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|