From 10977b06e730262b7b64ca21fb0a480aa5d6fae6 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 1 Aug 2016 14:02:49 +0200 Subject: [PATCH 01/15] doc add hint on image preprocessing --- doc/user/usage.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/user/usage.rst b/doc/user/usage.rst index 1d848a1..91629cd 100644 --- a/doc/user/usage.rst +++ b/doc/user/usage.rst @@ -204,6 +204,37 @@ Here you can download an example, that will print a set of common barcodes: * :download:`barcode.bin ` by `@mike42 `_ +Hint: preprocess printing +------------------------- + +Printing images directly to the printer is rather slow. +One factor that slows down the process is the transmission over e.g. serial port. + +Apart from configuring your printer to use the maximum baudrate (in the case of serial-printers), there is not much +that you can do. +However you could use the :py:class:`escpos.printer.Dummy`-printer to preprocess your image. +This is probably best explained by an example: + +.. code-block:: Python + + from escpos.printer import Serial, Dummy + + p = Serial() + d = Dummy() + + # create ESC/POS for the print job, this should go really fast + d.text("This is my image:\n") + d.image("funny_cat.png") + d.cut() + + # send code to printer + p._raw(d.output) + +This way you could also store the code in a file and print later. +You could then for example print the code from another process than your main-program and thus reduce the waiting time. +(Of course this will not make the printer print faster.) + + How to update your code for USB printers ---------------------------------------- From 2ecf73074c166cea1da3249f521041f55b91b40c Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 1 Aug 2016 14:39:44 +0200 Subject: [PATCH 02/15] improve large image printing images longer than 1024 pixels will be split into multiple fragments. --- src/escpos/escpos.py | 14 +++++++++++++- src/escpos/image.py | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 081130a..c0df537 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -56,7 +56,8 @@ class Escpos(object): """ pass - def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster"): + def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", + fragment_height=1024): """ Print an image You can select whether the printer should print in high density or not. The default value is high density. @@ -76,9 +77,20 @@ class Escpos(object): :param high_density_vertical: print in high density in vertical direction *default:* True :param high_density_horizontal: print in high density in horizontal direction *default:* True :param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn` + :param fragment_height: Images larger than this will be split into multiple fragments *default:* 1024 """ im = EscposImage(img_source) + + if im.height > fragment_height: + fragments = im.split(fragment_height) + for fragment in fragments: + self.image(fragment, + high_density_vertical=high_density_vertical, + high_density_horizontal=high_density_horizontal, + impl=impl, + fragment_height=fragment_height) + return if impl == "bitImageRaster": # GS v 0, raster format bit image diff --git a/src/escpos/image.py b/src/escpos/image.py index 1180614..abf2e22 100644 --- a/src/escpos/image.py +++ b/src/escpos/image.py @@ -8,6 +8,12 @@ This module contains the image format handler :py:class:`EscposImage`. :license: GNU GPL v3 """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import math from PIL import Image, ImageOps @@ -30,6 +36,9 @@ class EscposImage(object): else: img_original = Image.open(img_source) + # store image for eventual further processing (splitting) + self.img_original = img_original + # Convert to white RGB background, paste over white background # to strip alpha. img_original = img_original.convert('RGBA') @@ -88,3 +97,21 @@ class EscposImage(object): Convert image to raster-format binary """ return self._im.tobytes() + + def split(self, fragment_height): + """ + Split an image into multiple fragments after fragment_height pixels + + :param fragment_height: height of fragment + :return: list of PIL objects + """ + passes = int(math.ceil(self.height/fragment_height)) + fragments = [] + for n in range(0, passes): + left = 0 + right = self.width + upper = n * fragment_height + lower = min((n + 1) * fragment_height, self.height) + box = (left, upper, right, lower) + fragments.append(self.img_original.crop(box)) + return fragments From 38f983593142ca4357adecf31d186180140627a1 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 1 Aug 2016 12:24:07 +0200 Subject: [PATCH 03/15] fix printing of CODE128 The control sequence {A or {B or {C can't be part of the qr code. For this the user has to supply this sequence. --- doc/index.rst | 1 + doc/user/barcode.rst | 34 ++++++++++++++++++++++++++++++++++ src/escpos/constants.py | 5 +---- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 doc/user/barcode.rst diff --git a/doc/index.rst b/doc/index.rst index 8e876a0..060fc7f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,6 +18,7 @@ Content user/raspi user/todo user/usage + user/barcode .. toctree:: :maxdepth: 1 diff --git a/doc/user/barcode.rst b/doc/user/barcode.rst new file mode 100644 index 0000000..5d45419 --- /dev/null +++ b/doc/user/barcode.rst @@ -0,0 +1,34 @@ +Printing Barcodes +----------------- +:Last Reviewed: 2016-07-31 + +Most ESC/POS-printers implement barcode-printing. +The barcode-commandset is implemented in the barcode-method. +For a list of compatible barcodes you should check the manual of your printer. +As a rule of thumb: even older Epson-models support most 1D-barcodes. +To be sure just try some implementations and have a look at the notices below. + +barcode-method +~~~~~~~~~~~~~~ +The barcode-method is rather low-level and orients itself on the implementation of ESC/POS. +In the future this class could be supplemented by a high-level class that helps the user generating the payload. + +.. py:currentmodule:: escpos.escpos + +.. automethod:: Escpos.barcode + :noindex: + +CODE128 +~~~~~~~ +Code128 barcodes need a certain format. +For now the user has to make sure that the payload is correct. +For alphanumeric CODE128 you have to preface your payload with `{B`. + +.. code-block:: Python + + from escpos.printer import Dummy, Serial + p = Serial() + # print CODE128 012ABCDabcd + p.barcode("{B012ABCDabcd", "CODE128", function_type="B") + +A very good description on CODE128 is also on `Wikipedia `_. diff --git a/src/escpos/constants.py b/src/escpos/constants.py index 74b26eb..93721cb 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -168,10 +168,7 @@ BARCODE_TYPE_B = { 'NW7': _SET_BARCODE_TYPE(71), 'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7 'CODE93': _SET_BARCODE_TYPE(72), - # These are all the same barcode, but using different charcter sets - 'CODE128A': _SET_BARCODE_TYPE(73) + b'{A', # CODE128 character set A - 'CODE128B': _SET_BARCODE_TYPE(73) + b'{B', # CODE128 character set B - 'CODE128C': _SET_BARCODE_TYPE(73) + b'{C', # CODE128 character set C + 'CODE128': _SET_BARCODE_TYPE(73), 'GS1-128': _SET_BARCODE_TYPE(74), 'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75), 'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76), From d2e2ea88a697c71fba106033026212e4f687b70b Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 2 Aug 2016 16:05:22 +0200 Subject: [PATCH 04/15] doc write changelog for version v2.1.2 --- CHANGELOG.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4a04f09..65c2c9a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,15 +1,18 @@ ********* Changelog ********* -2016-08-?? - Version 2.?.? - "Death and Gravity" +2016-08-02 - Version 2.1.2 - "Death and Gravity" ------------------------------------------------ changes ^^^^^^^ +- fix File-printer: flush after every call of _raw() +- fix lists in documentation +- fix CODE128: by adding the control character to the barcode-selection-sequence the barcode became unusable contributors ^^^^^^^^^^^^ - +- Patrick Kanzler 2016-08-02 - Version 2.1.1 - "Contents May Differ" -------------------------------------------------- From eea2a6f9c01fb7f03f607e2fb9d10bc1e4ba8b47 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 2 Aug 2016 16:41:47 +0200 Subject: [PATCH 05/15] travis: configure email-notifications --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 76794ac..cce6180 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,3 +34,7 @@ before_install: script: - tox - codecov +notifications: + email: + on_success: never + on_failure: change From dfe2cdbff8446affb53c42a557b1149422597de6 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 2 Aug 2016 18:39:56 +0200 Subject: [PATCH 06/15] configure readthedocs with yml --- readthedocs.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 readthedocs.yml diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000..ecf365d --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,7 @@ +formats: + - pdf + - epub +requirements_file: doc/requirements.txt +python: + version: 2 + setup_py_install: true \ No newline at end of file From 603b34cadb5e0f4b1e7383db099331b84e75cba0 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Sun, 7 Aug 2016 13:04:36 +0200 Subject: [PATCH 07/15] test add test for the fragment-splitting --- test/test_function_image.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_function_image.py b/test/test_function_image.py index f60aa76..b1762f6 100644 --- a/test/test_function_image.py +++ b/test/test_function_image.py @@ -130,3 +130,12 @@ def test_graphics_transparent(): instance = printer.Dummy() instance.image('test/resources/black_transparent.png', impl="graphics") assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002') + + +def test_large_graphics(): + """ + Test whether 'large' graphics that induce a fragmentation are handled correctly. + """ + instance = printer.Dummy() + instance.image('test/resources/black_white.png', impl="bitImageRaster", fragment_height=1) + assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00') From 59dccd79dac80e9e985a1ed90887e6deeb21761f Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Sun, 7 Aug 2016 14:39:58 +0200 Subject: [PATCH 08/15] test add test for image-splitting-method --- test/test_image.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test_image.py b/test/test_image.py index dbfe5b0..6fda6dd 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -43,6 +43,20 @@ def test_image_white(): _load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00']) +def test_split(): + """ + test whether the split-function works as expected + """ + im = EscposImage('test/resources/black_white.png') + (upper_part, lower_part) = im.split(1) + upper_part = EscposImage(upper_part) + lower_part = EscposImage(lower_part) + assert(upper_part.width == lower_part.width == 2) + assert(upper_part.height == lower_part.height == 1) + assert(upper_part.to_raster_format() == b'\xc0') + assert(lower_part.to_raster_format() == b'\x00') + + def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected): """ Load an image, and test whether raster & column formatted output, sizes, etc match expectations. From e44d89bd339ee9b9fde5cb344673fce8f73bcdbb Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 10 Aug 2016 01:28:49 +0200 Subject: [PATCH 09/15] DOC update changelog for v2.1.3 --- CHANGELOG.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 65c2c9a..c005071 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,19 @@ ********* Changelog ********* +2016-08-10 - Version 2.1.3 - "Ethics Gradient" +---------------------------------------------- + +changes +^^^^^^^ +- configure readthedocs and travis +- update doc with hint on image preprocessing +- add fix for printing large images (by splitting them into multiple images) + +contributors +^^^^^^^^^^^^ +- Patrick Kanzler + 2016-08-02 - Version 2.1.2 - "Death and Gravity" ------------------------------------------------ From e8d91a6735143f6dd57068a7dad70aeeed3d5750 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 15 Aug 2016 22:33:23 +0200 Subject: [PATCH 10/15] test add type-check for the qr-printing --- src/escpos/escpos.py | 5 ++--- test/test_function_qr_non-native.py | 32 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 test/test_function_qr_non-native.py diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index c0df537..67a9053 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -157,7 +157,7 @@ class Escpos(object): if not native: # Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image if model != QR_MODEL_2: - raise ValueError("Invalid QR model for qrlib rendering (must be QR_MODEL_2)") + raise ValueError("Invalid QR model for python-qrcode rendering (must be QR_MODEL_2)") python_qr_ec = { QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H, QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L, @@ -168,8 +168,7 @@ class Escpos(object): qr_code.add_data(content) qr_code.make(fit=True) qr_img = qr_code.make_image() - im = qr_img._img.convert("RGB") - # Convert the RGB image in printable image + im = qr_img.convert("RGB") # Convert the RGB image in printable image self.image(im) return # Native 2D code printing diff --git a/test/test_function_qr_non-native.py b/test/test_function_qr_non-native.py new file mode 100644 index 0000000..5c0566a --- /dev/null +++ b/test/test_function_qr_non-native.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the non-native part of qr() + +:author: `Patrick Kanzler `_ +:organization: `python-escpos `_ +:copyright: Copyright (c) 2016 `python-escpos `_ +:license: GNU GPL v3 +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import mock + +from escpos.printer import Dummy +from PIL import Image + + +@mock.patch('escpos.printer.Dummy.image', spec=Dummy) +def test_type_of_object_passed_to_image_function(img_function): + """ + Test the type of object that is passed to the image function during non-native qr-printing. + + The type should be PIL.Image + """ + d = Dummy() + d.qr("LoremIpsum") + args, kwargs = img_function.call_args + assert isinstance(args[0], Image.Image) From 798893caeea7ebd439dd8217e6549f9cd696cfb7 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 15 Aug 2016 23:24:06 +0200 Subject: [PATCH 11/15] refactor access of private member _img of qrcode Since version 2.5 python-qrcode allows the direct access of the PIL-functions. (We require version 4 and above). Thus, we can simply call qr_img.convert() without accessing the private member. This refactoring is identical in functionality. --- src/escpos/escpos.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 67a9053..c0df537 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -157,7 +157,7 @@ class Escpos(object): if not native: # Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image if model != QR_MODEL_2: - raise ValueError("Invalid QR model for python-qrcode rendering (must be QR_MODEL_2)") + raise ValueError("Invalid QR model for qrlib rendering (must be QR_MODEL_2)") python_qr_ec = { QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H, QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L, @@ -168,7 +168,8 @@ class Escpos(object): qr_code.add_data(content) qr_code.make(fit=True) qr_img = qr_code.make_image() - im = qr_img.convert("RGB") # Convert the RGB image in printable image + im = qr_img._img.convert("RGB") + # Convert the RGB image in printable image self.image(im) return # Native 2D code printing 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 12/15] 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 13/15] 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 14/15] 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 15/15] 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!