From ed3077f00f68588c04fdef4c7081166566aea9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Thu, 25 Aug 2016 16:49:40 +0200 Subject: [PATCH 01/16] Define a capability format in YAML. --- src/escpos/capabilities.yml | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/escpos/capabilities.yml diff --git a/src/escpos/capabilities.yml b/src/escpos/capabilities.yml new file mode 100644 index 0000000..55f9212 --- /dev/null +++ b/src/escpos/capabilities.yml @@ -0,0 +1,68 @@ +# Many recent Epson-branded thermal receipt printers. +default: + columns: 42 + + barcodeB: true + bitImage: true + graphics: true + starCommands: false + qrCode: true + codePages: + - cp437 + # ... + # + +# 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 + + +"TM-T88IIIP": + inherits: epson + columns: + a: 42 + b: 56 + +"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 \ No newline at end of file From a8574ad9d7b7b933bb70fa47f84b3e396d058033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Thu, 25 Aug 2016 17:30:05 +0200 Subject: [PATCH 02/16] Support loading capabilites YAML into Python classes. --- src/escpos/capabilities.py | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/escpos/capabilities.py diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py new file mode 100644 index 0000000..2b08083 --- /dev/null +++ b/src/escpos/capabilities.py @@ -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 + + From 8b5bc9cf8a595b4232afb96dc845ea9dbc0d7951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Thu, 25 Aug 2016 17:30:20 +0200 Subject: [PATCH 03/16] Make the Escpos class accept a profile. This is now used for the block_text function. --- src/escpos/escpos.py | 11 ++++++----- test/test_function_text.py | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index c0df537..683688c 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -23,6 +23,7 @@ from .exceptions import * 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 codepage = None - def __init__(self, columns=32): + def __init__(self, profile=None): """ 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) def __del__(self): """ call self.close upon deletion """ @@ -439,7 +440,7 @@ class Escpos(object): # TODO: why is it problematic to print an empty string? raise TextError() - 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. @@ -448,7 +449,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, diff --git a/test/test_function_text.py b/test/test_function_text.py index b0b1ca1..973ddfc 100644 --- a/test/test_function_text.py +++ b/test/test_function_text.py @@ -15,6 +15,7 @@ from __future__ import unicode_literals from nose.tools import with_setup import escpos.printer as printer +from escpos.printer import Dummy import os import filecmp @@ -43,3 +44,11 @@ def test_function_text_dies_ist_ein_test_lf(): instance.text('Dies ist ein Test.\n') instance.flush() assert(filecmp.cmp('test/Dies ist ein Test.LF.txt', devfile)) + + +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.' From 3fd1a3de5d2824d8c60544ce4b967c5d8391179b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Fri, 26 Aug 2016 15:14:28 +0200 Subject: [PATCH 04/16] A suggested format for defining the code pages. --- src/escpos/capabilities.yml | 149 ++++++++++++++++++++++++++++++++++-- 1 file changed, 144 insertions(+), 5 deletions(-) diff --git a/src/escpos/capabilities.yml b/src/escpos/capabilities.yml index 55f9212..e105687 100644 --- a/src/escpos/capabilities.yml +++ b/src/escpos/capabilities.yml @@ -1,3 +1,24 @@ +# 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 @@ -7,10 +28,96 @@ default: graphics: true starCommands: false qrCode: true - codePages: - - cp437 - # ... - # + + 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, @@ -38,11 +145,41 @@ epson: graphics: false -"TM-T88IIIP": +# 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 @@ -51,12 +188,14 @@ epson: 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! From 216184f43f2080520df7c6ab541f80d382471842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Sat, 27 Aug 2016 12:17:35 +0200 Subject: [PATCH 05/16] Rework capabilities format based on Mike's ideas. --- src/escpos/capabilities.yml | 423 ++++++++++++++++++++---------------- 1 file changed, 234 insertions(+), 189 deletions(-) diff --git a/src/escpos/capabilities.yml b/src/escpos/capabilities.yml index e105687..561479d 100644 --- a/src/escpos/capabilities.yml +++ b/src/escpos/capabilities.yml @@ -1,207 +1,252 @@ -# 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] +# Define code pages that implementors are likely not to find in iconv etc. +codepages: + blank: [ + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + ] + TCVN-3-1: [ + " ", + " ", + " ăâêôơưđ ", + " àảãáạ ằẳẵắ ", + " ặầẩẫấậè ẻẽ", + "éẹềểễếệìỉ ĩíịò", + " ỏõóọồổỗốộờởỡớợù", + " ủũúụừửữứựỳỷỹýỵ ", + ] + TCVN-3-2: [ + " ", + " ", + " ĂÂ Ð ÊÔƠƯ ", + " ÀẢÃÁẠ ẰẲẴẮ ", + " ẶẦẨẪẤẬÈ ẺẼ", + "ÉẸỀỂỄẾỆÌỈ ĨÍỊÒ", + " ỎÕÓỌỒỔỖỐỘỜỞỠỚỢÙ", + " ỦŨÚỤỪỬỮỨỰỲỶỸÝỴ " + ] -# 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: [ - " ", - " ", - " ĂÂ Ð ÊÔƠƯ ", - " ÀẢÃÁẠ ẰẲẴẮ ", - " ẶẦẨẪẤẬÈ ẺẼ", - "ÉẸỀỂỄẾỆÌỈ ĨÍỊÒ", - " ỎÕÓỌỒỔỖỐỘỜỞỠỚỢÙ", - " ỦŨÚỤỪỬỮỨỰỲỶỸÝỴ " - ] +commands: + LineFeed: + name: Line feed + FeedAndCut: + name: Feed and cut + SetAbsolutePrintPos: + name: Set absolute print position + GraphicsData: + name: Graphics data - # 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: +profiles: + default: + name: Default profile + description: Many recent Epson-branded thermal receipt printers + commands: + BarcodeB: true + BitImage: true + GraphicsData: true + QrCode: true + features: + starCommands: false + font: + 0: + columns: 40 + 1: + columns: 50 + colors: + - black + # Commented-out slots are TODO (might just need uncomment, might + # need verification/research) + codepages: + 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 + # 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: + inherits: default + name: Simple profile + codePages: + 0: CP437 + commands: + graphicsData: false -# Profile for Star-branded printers. -star: - inherits: default - starCommands: true + star: + name: Star-branded printers + inherits: default + features: + starCommands: false -epson: - inherits: default - manufacturer: "Epson" +printers: + P-822D: + inherits: default + manufacturer: "Epson" + commands: + graphicsData: false + # http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf + TM-T88II: + inherits: default + manufacturer: "Epson" + fonts: + a: + columns: 42 + b: + columns: 56 + codePages: + 0: PC437 + 1: Katakana + 2: PC850 + 3: PC860 + 4: PC863 + 5: PC865 + 19: PC858 + 255: blank -"P-822D": - inherits: default - graphics: false + # http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf + TM-T88III: + inherits: default + manufacturer: "Epson" + fonts: + a: + columns: 42 + b: + columns: 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: default + manufacturer: "Epson" + fonts: + a: + columns: 48 + b: + columns: 64 + kanji: + columns: 24 -# 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 + TM-P80 (42 column emulation mode): + inherits: TM-P80 + fonts: + a: + columns: 42 + b: + columns: 60 + kanji: + columns: 21 -# 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-P60II 2 (58mm): + inherits: default + manufacturer: "Epson" + media: + width: + mm: 58 + fonts: + a: {columns: 35} + b: {columns: 42} + c: {columns: 52} + TM-P60II 2 (60mm): + inherits: default + manufacturer: "Epson" + media: + width: + mm: 60 + fonts: + a: {columns: 36} + b: {columns: 43} + c: {columns: 54} -"TM-P80": - inherits: epson - defaultColumnConfig: default - columnConfigs: - default: {'a': 48, 'b': 64, 'kanji': 24} - '42_emulation': {'a': 42, 'b': 60, 'kanji': 21} + TM-P20 2: + inherits: default + manufacturer: "Epson" + fonts: [a, b c, d, e, f] - -"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 \ No newline at end of file + TM-T90: + inherits: default + manufacturer: "Epson" + colors: + - black + - red \ No newline at end of file From a07f84a5bc6a36e05e8a02cdd2204cc14f6cc722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 12:26:09 +0200 Subject: [PATCH 06/16] Match the current printer-db format. --- setup.py | 3 +- src/escpos/capabilities.json | 1 + src/escpos/capabilities.py | 87 +++++++----- src/escpos/capabilities.yml | 252 ----------------------------------- src/escpos/constants.py | 7 +- src/escpos/escpos.py | 14 +- 6 files changed, 67 insertions(+), 297 deletions(-) create mode 100644 src/escpos/capabilities.json delete mode 100644 src/escpos/capabilities.yml diff --git a/setup.py b/setup.py index 0e3eea5..6a95e63 100755 --- a/setup.py +++ b/setup.py @@ -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', diff --git a/src/escpos/capabilities.json b/src/escpos/capabilities.json new file mode 100644 index 0000000..02ada55 --- /dev/null +++ b/src/escpos/capabilities.json @@ -0,0 +1 @@ +{"profiles": {"TM-T88III": {"vendor": "Epson", "features": {"starCommands": false, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"17": "CP866", "16": "CP1252", "19": "CP858", "18": "CP862", "1": "Unknown", "0": "CP437", "3": "CP860", "2": "CP850", "5": "CP865", "4": "CP863", "255": "Unknown"}, "name": "TM-T88III"}, "TM-P80": {"vendor": "Epson", "features": {"starCommands": false, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": 72, "pixels": 576}}, "notes": "Portable printer (48-column mode)", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}, "2": {"name": "Kanji", "columns": 24}}, "colors": {"0": "black"}, "codePages": {"51": "CP1257", "48": "CP1254", "50": "CP1256", "82": "Unknown", "49": "CP1255", "66": "Unknown", "67": "Unknown", "68": "Unknown", "69": "Unknown", "52": "CP1258", "53": "RK1048", "254": "Unknown", "255": "Unknown", "24": "Unknown", "25": "Unknown", "26": "Unknown", "20": "Unknown", "21": "CP874", "22": "Unknown", "23": "Unknown", "46": "CP1251", "47": "CP1253", "44": "CP1125", "45": "CP1250", "42": "CP774", "43": "CP772", "40": "ISO_8859-15", "41": "CP1098", "1": "CP932", "0": "CP437", "3": "CP860", "2": "CP850", "5": "CP865", "4": "CP863", "7": "Unknown", "6": "Unknown", "8": "Unknown", "39": "ISO_8859-2", "75": "Unknown", "38": "CP869", "73": "Unknown", "72": "Unknown", "71": "Unknown", "70": "Unknown", "11": "CP851", "13": "CP857", "12": "CP853", "15": "ISO_8859-7", "14": "CP737", "17": "CP866", "16": "CP1252", "19": "CP858", "18": "CP852", "31": "TCVN-3-2", "30": "TCVN-3-1", "37": "CP864", "36": "CP862", "35": "CP861", "34": "CP855", "33": "CP775", "74": "Unknown", "32": "CP720"}, "name": "TM-P80"}, "P822D": {"vendor": "PBM", "features": {"starCommands": false, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": false, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"56": "CP861", "81": "CP3848", "54": "CP852", "60": "CP855", "61": "CP857", "62": "CP862", "63": "CP864", "64": "CP737", "53": "CP858", "66": "CP869", "67": "CP928", "68": "CP772", "69": "CP774", "80": "CP3847", "52": "CP437", "86": "CP3011", "87": "CP3012", "84": "CP3001", "85": "CP3002", "24": "CP747", "25": "CP1257", "27": "Unknown", "20": "Unknown", "21": "Unknown", "22": "Unknown", "23": "Unknown", "82": "CP1001", "28": "CP864", "29": "CP1001", "1": "Unknown", "0": "CP437", "3": "CP860", "2": "CP850", "5": "CP865", "4": "CP863", "7": "Unknown", "6": "Unknown", "9": "Unknown", "8": "Unknown", "255": "Unknown", "83": "CP2001", "77": "CP3844", "76": "CP3843", "75": "CP3841", "74": "CP3840", "73": "CP1251", "72": "CP1250", "71": "CP1252", "70": "CP874", "79": "CP3846", "78": "CP3845", "10": "Unknown", "59": "CP866", "58": "CP865", "17": "CP866", "16": "CP1252", "19": "CP858", "18": "CP852", "31": "Unknown", "30": "Unknown", "51": "Unknown", "50": "CP437", "35": "CP1257", "34": "CP1256", "33": "CP720", "55": "CP860", "89": "CP3041", "88": "CP3021", "32": "CP1255", "57": "CP863", "65": "CP851"}, "name": "P822D"}, "TEP-200M": {"vendor": "EPOS", "features": {"starCommands": false, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"51": "CP1257", "48": "CP1254", "50": "CP1256", "82": "Unknown", "49": "CP1255", "66": "Unknown", "67": "Unknown", "68": "Unknown", "69": "Unknown", "52": "CP1258", "53": "RK1048", "254": "Unknown", "255": "Unknown", "24": "Unknown", "25": "Unknown", "26": "Unknown", "20": "Unknown", "21": "CP874", "22": "Unknown", "23": "Unknown", "46": "CP1251", "47": "CP1253", "44": "CP1125", "45": "CP1250", "42": "CP774", "43": "CP772", "40": "ISO_8859-15", "41": "CP1098", "1": "CP932", "0": "CP437", "3": "CP860", "2": "CP850", "5": "CP865", "4": "CP863", "7": "Unknown", "6": "Unknown", "8": "Unknown", "39": "ISO_8859-2", "75": "Unknown", "38": "CP869", "73": "Unknown", "72": "Unknown", "71": "Unknown", "70": "Unknown", "11": "CP851", "13": "CP857", "12": "CP853", "15": "ISO_8859-7", "14": "CP737", "17": "CP866", "16": "CP1252", "19": "CP858", "18": "CP852", "31": "TCVN-3-2", "30": "TCVN-3-1", "37": "CP864", "36": "CP862", "35": "CP861", "34": "CP855", "33": "CP775", "74": "Unknown", "32": "CP720"}, "name": "TEP200M Series"}, "TM-T88II": {"vendor": "Epson", "features": {"starCommands": false, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"16": "CP1252", "1": "Unknown", "0": "CP437", "3": "CP860", "2": "CP850", "5": "CP865", "4": "CP863", "255": "Unknown"}, "name": "TM-T88II"}, "TM-P80-42col": {"vendor": "Epson", "features": {"starCommands": false, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": 63.6, "pixels": 546}}, "notes": "Portable printer (42-column mode)", "fonts": {"1": {"name": "Font B", "columns": 60}, "0": {"name": "Font A", "columns": 42}, "2": {"name": "Kanji", "columns": 21}}, "colors": {"0": "black"}, "codePages": {"51": "CP1257", "48": "CP1254", "50": "CP1256", "82": "Unknown", "49": "CP1255", "66": "Unknown", "67": "Unknown", "68": "Unknown", "69": "Unknown", "52": "CP1258", "53": "RK1048", "254": "Unknown", "255": "Unknown", "24": "Unknown", "25": "Unknown", "26": "Unknown", "20": "Unknown", "21": "CP874", "22": "Unknown", "23": "Unknown", "46": "CP1251", "47": "CP1253", "44": "CP1125", "45": "CP1250", "42": "CP774", "43": "CP772", "40": "ISO_8859-15", "41": "CP1098", "1": "CP932", "0": "CP437", "3": "CP860", "2": "CP850", "5": "CP865", "4": "CP863", "7": "Unknown", "6": "Unknown", "8": "Unknown", "39": "ISO_8859-2", "75": "Unknown", "38": "CP869", "73": "Unknown", "72": "Unknown", "71": "Unknown", "70": "Unknown", "11": "CP851", "13": "CP857", "12": "CP853", "15": "ISO_8859-7", "14": "CP737", "17": "CP866", "16": "CP1252", "19": "CP858", "18": "CP852", "31": "TCVN-3-2", "30": "TCVN-3-1", "37": "CP864", "36": "CP862", "35": "CP861", "34": "CP855", "33": "CP775", "74": "Unknown", "32": "CP720"}, "name": "TM-P80 (42 column mode)"}, "TSP600": {"vendor": "Star Micronics", "features": {"starCommands": true, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "Star TSP600 thermal printer series with ESC/POS emulation enabled", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"98": "Unknown", "64": "CP3840", "65": "CP3841", "66": "CP3843", "67": "CP3844", "68": "CP3845", "69": "CP3846", "255": "Unknown", "20": "CP774", "21": "CP874", "1": "CP437", "0": "CP437", "3": "CP437", "2": "CP932", "5": "CP852", "4": "CP858", "7": "CP861", "6": "CP860", "9": "CP865", "8": "CP863", "96": "Unknown", "97": "Unknown", "77": "CP3012", "76": "CP3011", "75": "CP3002", "74": "CP3001", "73": "CP2001", "72": "CP1001", "71": "CP3848", "70": "CP3847", "102": "Unknown", "100": "Unknown", "101": "Unknown", "79": "CP3041", "78": "CP3021", "11": "CP855", "10": "CP866", "13": "CP862", "12": "CP857", "15": "CP737", "14": "CP864", "17": "CP869", "16": "CP851", "19": "CP772", "18": "CP928", "34": "CP1251", "99": "Unknown", "33": "CP1250", "32": "CP1252"}, "name": "TSP600 Series"}, "default": {"vendor": "Generic", "features": {"starCommands": false, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "Default ESC/POS profile, suitable for standards-compliant or Epson-branded printers. This profile allows the use of standard ESC/POS features, and can encode a variety of code pages.\n", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"51": "CP1257", "48": "CP1254", "50": "CP1256", "82": "Unknown", "49": "CP1255", "66": "Unknown", "67": "Unknown", "68": "Unknown", "69": "Unknown", "52": "CP1258", "53": "RK1048", "254": "Unknown", "255": "Unknown", "24": "Unknown", "25": "Unknown", "26": "Unknown", "20": "Unknown", "21": "CP874", "22": "Unknown", "23": "Unknown", "46": "CP1251", "47": "CP1253", "44": "CP1125", "45": "CP1250", "42": "CP774", "43": "CP772", "40": "ISO_8859-15", "41": "CP1098", "1": "CP932", "0": "CP437", "3": "CP860", "2": "CP850", "5": "CP865", "4": "CP863", "7": "Unknown", "6": "Unknown", "8": "Unknown", "39": "ISO_8859-2", "75": "Unknown", "38": "CP869", "73": "Unknown", "72": "Unknown", "71": "Unknown", "70": "Unknown", "11": "CP851", "13": "CP857", "12": "CP853", "15": "ISO_8859-7", "14": "CP737", "17": "CP866", "16": "CP1252", "19": "CP858", "18": "CP852", "31": "TCVN-3-2", "30": "TCVN-3-1", "37": "CP864", "36": "CP862", "35": "CP861", "34": "CP855", "33": "CP775", "74": "Unknown", "32": "CP720"}, "name": "Default"}, "TUP500": {"vendor": "Star Micronics", "features": {"starCommands": true, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "Star TUP500 thermal printer series with ESC/POS emulation enabled", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"98": "Unknown", "64": "CP3840", "65": "CP3841", "66": "CP3843", "67": "CP3844", "68": "CP3845", "69": "CP3846", "255": "Unknown", "20": "CP774", "21": "CP874", "1": "CP437", "0": "CP437", "3": "CP437", "2": "CP932", "5": "CP852", "4": "CP858", "7": "CP861", "6": "CP860", "9": "CP865", "8": "CP863", "96": "Unknown", "97": "Unknown", "77": "CP3012", "76": "CP3011", "75": "CP3002", "74": "CP3001", "73": "CP2001", "72": "CP1001", "71": "CP3848", "70": "CP3847", "102": "Unknown", "100": "Unknown", "101": "Unknown", "79": "CP3041", "78": "CP3021", "11": "CP855", "10": "CP866", "13": "CP862", "12": "CP857", "15": "CP737", "14": "CP864", "17": "CP869", "16": "CP851", "19": "CP772", "18": "CP928", "34": "CP1251", "99": "Unknown", "33": "CP1250", "32": "CP1252"}, "name": "TUP500 Series"}, "TM-U220": {"vendor": "Epson", "features": {"starCommands": false, "highDensity": false, "barcodeB": false, "bitImageColumn": true, "graphics": false, "qrCode": false, "bitImageRaster": false}, "media": {"width": {"mm": 80, "pixels": "Unknown"}}, "notes": "Two-color impact printer with 80mm output", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"1": "alternate", "0": "black"}, "codePages": {"0": "CP437"}, "name": "TM-U220"}, "simple": {"vendor": "Generic", "features": {"starCommands": false, "highDensity": true, "barcodeB": false, "bitImageColumn": false, "graphics": false, "qrCode": false, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "A profile for use in printers with unknown or poor compatibility. This profile indicates that a small number of features are supported, so that commands are not sent a printer that is unlikely to understand them.\n", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"0": "CP437"}, "name": "Simple"}, "SP2000": {"vendor": "Star Micronics", "features": {"starCommands": true, "highDensity": true, "barcodeB": true, "bitImageColumn": true, "graphics": true, "qrCode": true, "bitImageRaster": true}, "media": {"width": {"mm": "Unknown", "pixels": "Unknown"}}, "notes": "Star SP2000 impact printer series with ESC/POS emulation enabled", "fonts": {"1": {"name": "Font B", "columns": 56}, "0": {"name": "Font A", "columns": 42}}, "colors": {"0": "black"}, "codePages": {"98": "Unknown", "64": "CP3840", "65": "CP3841", "66": "CP3843", "67": "CP3844", "68": "CP3845", "69": "CP3846", "255": "Unknown", "20": "CP774", "21": "CP874", "1": "CP437", "0": "CP437", "3": "CP437", "2": "CP932", "5": "CP852", "4": "CP858", "7": "CP861", "6": "CP860", "9": "CP865", "8": "CP863", "96": "Unknown", "97": "Unknown", "77": "CP3012", "76": "CP3011", "75": "CP3002", "74": "CP3001", "73": "CP2001", "72": "CP1001", "71": "CP3848", "70": "CP3847", "102": "Unknown", "100": "Unknown", "101": "Unknown", "79": "CP3041", "78": "CP3021", "11": "CP855", "10": "CP866", "13": "CP862", "12": "CP857", "15": "CP737", "14": "CP864", "17": "CP869", "16": "CP851", "19": "CP772", "18": "CP928", "34": "CP1251", "99": "Unknown", "33": "CP1250", "32": "CP1252"}, "name": "SP2000 Series"}}, "encodings": {"CP3041": {"name": "Unimplemented Star-specific CP3041"}, "RK1048": {"iconv": "RK1048", "name": "RK1048"}, "Unknown": {"notes": "Code page that has not yet been identified.", "name": "Unknown"}, "CP1252": {"iconv": "CP1252", "python_encode": "cp1252", "name": "CP1252"}, "CP1255": {"iconv": "CP1255", "python_encode": "cp1255", "name": "CP1255"}, "CP1254": {"iconv": "CP1254", "python_encode": "cp1254", "name": "CP1254"}, "CP1257": {"iconv": "CP1257", "python_encode": "cp1257", "name": "CP1257"}, "CP1256": {"iconv": "CP1256", "python_encode": "cp1256", "name": "CP1256"}, "CP1251": {"iconv": "CP1251", "python_encode": "cp1251", "name": "CP1251"}, "CP1250": {"iconv": "CP1250", "python_encode": "cp1250", "name": "CP1250"}, "CP1253": {"iconv": "CP1253", "python_encode": "cp1253", "name": "CP1253"}, "CP1098": {"name": "CP1098"}, "CP437": {"iconv": "CP437", "python_encode": "cp437", "name": "CP437"}, "CP3021": {"name": "Unimplemented Star-specific CP3021"}, "CP3002": {"name": "Unimplemented Star-specific CP3002"}, "CP1258": {"iconv": "CP1258", "python_encode": "cp1258", "name": "CP1258"}, "CP3001": {"name": "Unimplemented Star-specific CP3001"}, "CP928": {"name": "CP928"}, "TCVN-3-1": {"data": [" ", " ", " \u0103\u00e2\u00ea\u00f4\u01a1\u01b0\u0111 ", " \u00e0\u1ea3\u00e3\u00e1\u1ea1 \u1eb1\u1eb3\u1eb5\u1eaf ", " \u1eb7\u1ea7\u1ea9\u1eab\u1ea5\u1ead\u00e8 \u1ebb\u1ebd", "\u00e9\u1eb9\u1ec1\u1ec3\u1ec5\u1ebf\u1ec7\u00ec\u1ec9 \u0129\u00ed\u1ecb\u00f2", " \u1ecf\u00f5\u00f3\u1ecd\u1ed3\u1ed5\u1ed7\u1ed1\u1ed9\u1edd\u1edf\u1ee1\u1edb\u1ee3\u00f9", " \u1ee7\u0169\u00fa\u1ee5\u1eeb\u1eed\u1eef\u1ee9\u1ef1\u1ef3\u1ef7\u1ef9\u00fd\u1ef5 "], "name": "Vietnamese TCVN-3 1"}, "TCVN-3-2": {"data": [" ", " ", " \u0102\u00c2 \u00d0 \u00ca\u00d4\u01a0\u01af ", " \u00c0\u1ea2\u00c3\u00c1\u1ea0 \u1eb0\u1eb2\u1eb4\u1eae ", " \u1eb6\u1ea6\u1ea8\u1eaa\u1ea4\u1eac\u00c8 \u1eba\u1ebc", "\u00c9\u1eb8\u1ec0\u1ec2\u1ec4\u1ebe\u1ec6\u00cc\u1ec8 \u0128\u00cd\u1eca\u00d2", " \u1ece\u00d5\u00d3\u1ecc\u1ed2\u1ed4\u1ed6\u1ed0\u1ed8\u1edc\u1ede\u1ee0\u1eda\u1ee2\u00d9", " \u1ee6\u0168\u00da\u1ee4\u1eea\u1eec\u1eee\u1ee8\u1ef0\u1ef2\u1ef6\u1ef8\u00dd\u1ef4 "], "name": "Vietnamese TCVN-3 1"}, "CP737": {"iconv": "CP737", "python_encode": "cp737", "name": "CP737"}, "CP747": {"name": "CP747"}, "CP851": {"notes": "Not used, due to inconsistencies between implementations.", "name": "Greek CP851"}, "CP850": {"iconv": "CP850", "python_encode": "cp850", "name": "CP850"}, "CP853": {"name": "CP853"}, "CP852": {"iconv": "CP852", "python_encode": "cp852", "name": "CP852"}, "CP855": {"iconv": "CP855", "python_encode": "cp855", "name": "CP855"}, "CP857": {"iconv": "CP857", "python_encode": "cp857", "name": "CP857"}, "CP861": {"iconv": "CP861", "python_encode": "cp861", "name": "CP861"}, "CP774": {"iconv": "CP774", "name": "CP774"}, "CP775": {"iconv": "CP775", "python_encode": "cp775", "name": "CP775"}, "CP874": {"iconv": "CP874", "python_encode": "cp874", "name": "CP874"}, "CP866": {"iconv": "CP866", "python_encode": "cp866", "name": "CP866"}, "CP1125": {"iconv": "CP1125", "python_encode": "cp1125", "name": "CP1125"}, "CP1001": {"name": "Unimplemented Star-specific CP1001"}, "CP858": {"python_encode": "cp858", "name": "CP858"}, "CP932": {"iconv": "CP932", "python_encode": "cp932", "name": "CP932"}, "CP3011": {"data": ["\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5", "\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00a2\u00a3\u00a5\u20a7\u0192", "\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u2310\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb", "\u2591\u2592\u2593\u2502\u2524\u0100\u2562\u0146\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510", "\u2514\u2534\u252c\u251c\u2500\u253c\u0101\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567", "\u0160\u2564\u010d\u010c\u2558\u2552\u0123\u012a\u012b\u2518\u250c\u2588\u2584\u016b\u016a\u2580", "\u03b1\u00df\u0393\u03c0\u03a3\u03c3\u00b5\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u03c6\u03b5\u2229", "\u0112\u0113\u0122\u0137\u0136\u013c\u013b\u017e\u017d\u2219\u00b7\u221a\u0145\u0161\u25a0 "], "name": "CP3011 Latvian"}, "CP862": {"iconv": "CP862", "python_encode": "cp862", "name": "CP862"}, "CP3012": {"data": ["\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f", "\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f", "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f", "\u2591\u2592\u2593\u2502\u2524\u0100\u2562\u0146\u2555\u2563\u2551\u2557\u255d\u014c\u255b\u2510", "\u2514\u2534\u252c\u251c\u2500\u253c\u0101\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567", "\u0160\u2564\u010d\u010c\u2558\u2552\u0123\u012a\u012b\u2518\u250c\u2588\u2584\u016b\u016a\u2580", "\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f", "\u0112\u0113\u0122\u0137\u0136\u013c\u013b\u017e\u017d\u2219\u00b7\u221a\u0145\u0161\u25a0 "], "name": "CP3012 Cyrillic"}, "ISO_8859-7": {"iconv": "ISO_8859-7", "python_encode": "iso8859_7", "name": "ISO_8859-7"}, "CP863": {"iconv": "CP863", "python_encode": "cp863", "name": "CP863"}, "CP720": {"python_encode": "cp720", "name": "CP720"}, "CP2001": {"name": "Unimplemented Star-specific CP2001"}, "CP864": {"iconv": "CP864", "python_encode": "cp864", "name": "CP864"}, "ISO_8859-2": {"iconv": "ISO_8859-2", "python_encode": "iso8859_2", "name": "ISO_8859-2"}, "CP865": {"iconv": "CP865", "python_encode": "cp865", "name": "CP865"}, "CP869": {"iconv": "CP869", "python_encode": "cp869", "name": "CP869"}, "CP3848": {"name": "Unimplemented Star-specific CP3848"}, "ISO_8859-15": {"iconv": "ISO_8859-15", "python_encode": "iso8859-15", "name": "ISO_8859-15"}, "CP772": {"iconv": "CP772", "name": "CP772"}, "CP860": {"iconv": "CP860", "python_encode": "cp860", "name": "CP860"}, "CP3843": {"name": "Unimplemented Star-specific CP3843"}, "CP3840": {"name": "Unimplemented Star-specific CP3840"}, "CP3841": {"name": "Unimplemented Star-specific CP3841"}, "CP3846": {"name": "Unimplemented Star-specific CP3846"}, "CP3847": {"name": "Unimplemented Star-specific CP3847"}, "CP3844": {"name": "Unimplemented Star-specific CP3844"}, "CP3845": {"name": "Unimplemented Star-specific CP3845"}}} diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index 2b08083..816c6e9 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -1,40 +1,53 @@ 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 + + +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 - - 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] + font = self.get_font(font) + return self.fonts[six.text_type(font)]['columns'] 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 +55,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 +81,20 @@ 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): + super(Profile, self).__init() + + self.columns = columns + + def get_columns(self, font): + if self.columns is not None: + return 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 diff --git a/src/escpos/capabilities.yml b/src/escpos/capabilities.yml deleted file mode 100644 index 561479d..0000000 --- a/src/escpos/capabilities.yml +++ /dev/null @@ -1,252 +0,0 @@ -# Define code pages that implementors are likely not to find in iconv etc. -codepages: - blank: [ - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - ] - TCVN-3-1: [ - " ", - " ", - " ăâêôơưđ ", - " àảãáạ ằẳẵắ ", - " ặầẩẫấậè ẻẽ", - "éẹềểễếệìỉ ĩíịò", - " ỏõóọồổỗốộờởỡớợù", - " ủũúụừửữứựỳỷỹýỵ ", - ] - TCVN-3-2: [ - " ", - " ", - " ĂÂ Ð ÊÔƠƯ ", - " ÀẢÃÁẠ ẰẲẴẮ ", - " ẶẦẨẪẤẬÈ ẺẼ", - "ÉẸỀỂỄẾỆÌỈ ĨÍỊÒ", - " ỎÕÓỌỒỔỖỐỘỜỞỠỚỢÙ", - " ỦŨÚỤỪỬỮỨỰỲỶỸÝỴ " - ] - - -commands: - LineFeed: - name: Line feed - FeedAndCut: - name: Feed and cut - SetAbsolutePrintPos: - name: Set absolute print position - GraphicsData: - name: Graphics data - - -profiles: - default: - name: Default profile - description: Many recent Epson-branded thermal receipt printers - commands: - BarcodeB: true - BitImage: true - GraphicsData: true - QrCode: true - features: - starCommands: false - font: - 0: - columns: 40 - 1: - columns: 50 - colors: - - black - # Commented-out slots are TODO (might just need uncomment, might - # need verification/research) - codepages: - 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: - inherits: default - name: Simple profile - codePages: - 0: CP437 - commands: - graphicsData: false - - - star: - name: Star-branded printers - inherits: default - features: - starCommands: false - - -printers: - P-822D: - inherits: default - manufacturer: "Epson" - commands: - graphicsData: false - - # http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf - TM-T88II: - inherits: default - manufacturer: "Epson" - fonts: - a: - columns: 42 - b: - columns: 56 - codePages: - 0: PC437 - 1: Katakana - 2: PC850 - 3: PC860 - 4: PC863 - 5: PC865 - 19: PC858 - 255: blank - - # http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf - TM-T88III: - inherits: default - manufacturer: "Epson" - fonts: - a: - columns: 42 - b: - columns: 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: default - manufacturer: "Epson" - fonts: - a: - columns: 48 - b: - columns: 64 - kanji: - columns: 24 - - TM-P80 (42 column emulation mode): - inherits: TM-P80 - fonts: - a: - columns: 42 - b: - columns: 60 - kanji: - columns: 21 - - TM-P60II 2 (58mm): - inherits: default - manufacturer: "Epson" - media: - width: - mm: 58 - fonts: - a: {columns: 35} - b: {columns: 42} - c: {columns: 52} - - TM-P60II 2 (60mm): - inherits: default - manufacturer: "Epson" - media: - width: - mm: 60 - fonts: - a: {columns: 36} - b: {columns: 43} - c: {columns: 54} - - TM-P20 2: - inherits: default - manufacturer: "Epson" - fonts: [a, b c, d, e, f] - - TM-T90: - inherits: default - manufacturer: "Epson" - colors: - - black - - red \ No newline at end of file diff --git a/src/escpos/constants.py b/src/escpos/constants.py index 93721cb..6087c80 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -93,14 +93,17 @@ 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 + # Char code table CHARCODE_PC437 = ESC + b'\x74\x00' # USA: Standard Europe CHARCODE_JIS = ESC + b'\x74\x01' # Japanese Katakana diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 683688c..70e5ca1 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -452,8 +452,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: @@ -463,7 +463,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 @@ -528,10 +530,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) From 5fa89ff685aef81bd12fb0370d6ff1bda814a05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 12:53:31 +0200 Subject: [PATCH 07/16] Automatically choose correct barcode function. Tests for barcode function. --- src/escpos/capabilities.py | 13 +++++++++-- src/escpos/escpos.py | 43 +++++++++++++++++++++++++---------- test/test_function_barcode.py | 34 +++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 test/test_function_barcode.py diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index 816c6e9..6e375bc 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -15,6 +15,9 @@ class NotSupported(Exception): pass +BARCODE_B = 'barcodeB' + + class BaseProfile(object): """This respresents a printer profile. @@ -43,6 +46,11 @@ class BaseProfile(object): font = self.get_font(font) return self.fonts[six.text_type(font)]['columns'] + 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 @@ -84,10 +92,11 @@ def clean(s): # For users, who want to provide their profile class Profile(get_profile_class('default')): - def __init__(self, columns=None): - super(Profile, self).__init() + 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: diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 70e5ca1..50e59f8 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -23,7 +23,7 @@ from .exceptions import * 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) @@ -293,7 +293,8 @@ class Escpos(object): else: raise CharCodeError() - 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 @@ -364,14 +365,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) @@ -400,14 +427,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": diff --git a/test/test_function_barcode.py b/test/test_function_barcode.py new file mode 100644 index 0000000..4205eba --- /dev/null +++ b/test/test_function_barcode.py @@ -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 == '' \ No newline at end of file From 3d8626d17e8192943691b3b8de525c9e3a239239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 13:08:23 +0200 Subject: [PATCH 08/16] Update text() test to use dummy printer. --- test/Dies ist ein Test.LF.txt | 1 - test/test_function_text.py | 28 ++-------------------------- 2 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 test/Dies ist ein Test.LF.txt diff --git a/test/Dies ist ein Test.LF.txt b/test/Dies ist ein Test.LF.txt deleted file mode 100644 index d7e5cff..0000000 --- a/test/Dies ist ein Test.LF.txt +++ /dev/null @@ -1 +0,0 @@ -Dies ist ein Test. diff --git a/test/test_function_text.py b/test/test_function_text.py index 973ddfc..dc25842 100644 --- a/test/test_function_text.py +++ b/test/test_function_text.py @@ -12,38 +12,14 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -from nose.tools import with_setup - -import escpos.printer as printer from escpos.printer import Dummy -import os - -import filecmp - -devfile = 'testfile' -def setup_testfile(): - """create a testfile as devfile""" - fhandle = open(devfile, 'a') - try: - os.utime(devfile, None) - finally: - fhandle.close() - - -def teardown_testfile(): - """destroy testfile again""" - os.remove(devfile) - - -@with_setup(setup_testfile, teardown_testfile) def test_function_text_dies_ist_ein_test_lf(): """test the text printing function with simple string and compare output""" - instance = printer.File(devfile=devfile) + instance = Dummy() instance.text('Dies ist ein Test.\n') - instance.flush() - assert(filecmp.cmp('test/Dies ist ein Test.LF.txt', devfile)) + assert instance.output == 'Dies ist ein Test.\n' def test_block_text(): From 4496ea91bd9e8969b0e0b9d45d96448f24a170af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 13:09:29 +0200 Subject: [PATCH 09/16] Make pytest the test runner. --- setup.py | 2 +- tox.ini | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 219ee2c..ebc785d 100755 --- a/setup.py +++ b/setup.py @@ -117,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': [ diff --git a/tox.ini b/tox.ini index 0968270..5412ac3 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,10 @@ deps = nose coverage scripttest mock + pytest + pytest-cov hypothesis -commands = nosetests --with-coverage --cover-erase --cover-branches +commands = py.test [testenv:docs] basepython = python From 3681c5c7bfcaff98adf388e84cc1cdab1923c054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 13:20:42 +0200 Subject: [PATCH 10/16] Fix tests for Python 3. --- test/test_function_barcode.py | 2 +- test/test_function_text.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_function_barcode.py b/test/test_function_barcode.py index 4205eba..c6ec958 100644 --- a/test/test_function_barcode.py +++ b/test/test_function_barcode.py @@ -31,4 +31,4 @@ def test_lacks_support(bctype, supports_b): with pytest.raises(BarcodeTypeError): instance.barcode('test', bctype) - assert instance.output == '' \ No newline at end of file + assert instance.output == b'' \ No newline at end of file diff --git a/test/test_function_text.py b/test/test_function_text.py index dc25842..7f91c95 100644 --- a/test/test_function_text.py +++ b/test/test_function_text.py @@ -19,7 +19,7 @@ def test_function_text_dies_ist_ein_test_lf(): """test the text printing function with simple string and compare output""" instance = Dummy() instance.text('Dies ist ein Test.\n') - assert instance.output == 'Dies ist ein Test.\n' + assert instance.output == b'Dies ist ein Test.\n' def test_block_text(): @@ -27,4 +27,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.' From b92eeed50b1947fdf3a5dbac496814d962a4601b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 13:27:48 +0200 Subject: [PATCH 11/16] Add tests for the profile. --- src/escpos/capabilities.py | 2 +- test/test_profile.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/test_profile.py diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index 6e375bc..9ae0e79 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -100,7 +100,7 @@ class Profile(get_profile_class('default')): def get_columns(self, font): if self.columns is not None: - return columns + return self.columns return super(Profile, self).get_columns(font) diff --git a/test/test_profile.py b/test/test_profile.py new file mode 100644 index 0000000..c9418d5 --- /dev/null +++ b/test/test_profile.py @@ -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') \ No newline at end of file From 68c17f118199e2ca7be4549ddd890a2c60901233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 13:33:35 +0200 Subject: [PATCH 12/16] Generate coverage reports. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5412ac3..362f2a6 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ deps = nose pytest pytest-cov hypothesis -commands = py.test +commands = py.test --cov escpos [testenv:docs] basepython = python From 58ea206c36e42a30435ea63fe860d1a357a6948c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 16:13:38 +0200 Subject: [PATCH 13/16] Avoid mutable default argumet. --- src/escpos/capabilities.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index 9ae0e79..3dd5c32 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -92,11 +92,11 @@ def clean(s): # For users, who want to provide their profile class Profile(get_profile_class('default')): - def __init__(self, columns=None, features={}): + def __init__(self, columns=None, features=None): super(Profile, self).__init__() self.columns = columns - self.features = features + self.features = features or {} def get_columns(self, font): if self.columns is not None: From 1bd53697b99b728515adb36e06b2358d013e7593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 17:47:09 +0200 Subject: [PATCH 14/16] Fix the CLI test inside pytest runner. --- test/test_cli.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/test/test_cli.py b/test/test_cli.py index b9aebc3..14df358 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -34,27 +34,22 @@ class TestCLI(): """ Contains setups, teardowns, and tests for CLI """ - def __init__(self): - """ Initalize the tests. - Just define some vars here since most of them get set during - setup_class and teardown_class - """ - self.env = None - self.default_args = None - - @staticmethod - def setup_class(): + @classmethod + def setup_class(cls): """ Create a config file to read from """ with open(CONFIGFILE, 'w') as config: config.write(CONFIG_YAML) - @staticmethod - def teardown_class(): + @classmethod + def teardown_class(cls): """ Remove config file """ os.remove(CONFIGFILE) def setup(self): """ Create a file to print to and set up env""" + self.env = None + self.default_args = None + self.env = TestFileEnvironment( base_path=TEST_DIR, cwd=os.getcwd(), From c850a726cbcf8b4c6d6a09931e3927bf04cb04ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 17:55:58 +0200 Subject: [PATCH 15/16] Fix docstring warnings from QuantifedCode. --- src/escpos/capabilities.py | 5 ++++- test/test_function_barcode.py | 4 ++++ test/test_profile.py | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index 3dd5c32..15416ed 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -12,6 +12,9 @@ ENCODINGS = CAPABILITIES['encodings'] class NotSupported(Exception): + """Raised if a requested feature is not suppored by the + printer profile. + """ pass @@ -37,7 +40,7 @@ class BaseProfile(object): 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) + '"{}" is not a valid font in the current profile'.format(font)) return font def get_columns(self, font): diff --git a/test/test_function_barcode.py b/test/test_function_barcode.py index c6ec958..2edd712 100644 --- a/test/test_function_barcode.py +++ b/test/test_function_barcode.py @@ -16,6 +16,8 @@ import pytest b'\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00') ]) def test_barcode(bctype, data, expected): + """should generate different barcode types correctly. + """ instance = printer.Dummy() instance.barcode(data, bctype) assert instance.output == expected @@ -26,6 +28,8 @@ def test_barcode(bctype, data, expected): ('CODE128', False), ]) def test_lacks_support(bctype, supports_b): + """should raise an error if the barcode type is not supported. + """ profile = Profile(features={BARCODE_B: supports_b}) instance = printer.Dummy(profile=profile) with pytest.raises(BarcodeTypeError): diff --git a/test/test_profile.py b/test/test_profile.py index c9418d5..d75c88c 100644 --- a/test/test_profile.py +++ b/test/test_profile.py @@ -8,6 +8,8 @@ def profile(): class TestBaseProfile: + """Test the `BaseProfile` class. + """ def test_get_font(self, profile): with pytest.raises(NotSupported): @@ -26,6 +28,8 @@ class TestBaseProfile: class TestCustomProfile: + """Test custom profile options with the `Profile` class. + """ def test_columns(self): assert Profile(columns=10).get_columns('sdfasdf') == 10 From b543ecea5886eb4cbc4b82046071b930df80e8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Elsd=C3=B6rfer?= Date: Tue, 30 Aug 2016 18:00:06 +0200 Subject: [PATCH 16/16] Enable branch coverage. --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..398ff08 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +branch = True