diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 090a8e8..41ceb3b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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" ---------------------------------------------- diff --git a/README.rst b/README.rst index 72e21bd..e79b279 100644 --- a/README.rst +++ b/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 `_. diff --git a/setup.py b/setup.py index 0e3eea5..ebc785d 100755 --- a/setup.py +++ b/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': [ 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..9ae0e79 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -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 diff --git a/src/escpos/capabilities.yml b/src/escpos/capabilities.yml deleted file mode 100644 index 5849218..0000000 --- a/src/escpos/capabilities.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/escpos/constants.py b/src/escpos/constants.py index aa7857b..2bec85c 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -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 @@ -260,7 +282,7 @@ QR_ECLEVEL_L = 0 QR_ECLEVEL_M = 1 QR_ECLEVEL_Q = 2 QR_ECLEVEL_H = 3 - + # QRcode models QR_MODEL_1 = 1 QR_MODEL_2 = 2 diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 2dc17e0..76b6809 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -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. diff --git a/test/test_function_barcode.py b/test/test_function_barcode.py new file mode 100644 index 0000000..c6ec958 --- /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 == b'' \ No newline at end of file diff --git a/test/test_function_text.py b/test/test_function_text.py index e03c305..5aac224 100644 --- a/test/test_function_text.py +++ b/test/test_function_text.py @@ -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.' diff --git a/test/test_functions.py b/test/test_functions.py new file mode 100644 index 0000000..22ee737 --- /dev/null +++ b/test/test_functions.py @@ -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) \ No newline at end of file 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 diff --git a/tox.ini b/tox.ini index 0968270..b5555cc 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 --cov reports [testenv:docs] basepython = python