1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-06-25 08:38:43 +00:00

Merge 9d77f630c8b7db09ad858fb9866b609286ec43cc into b85d5b907d0de05fc217c98c8810edc97399b3c1

This commit is contained in:
kymok 2025-05-04 14:21:58 +02:00 committed by GitHub
commit 0773f77bde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 490 additions and 3 deletions

51
examples/kanji.py Normal file
View File

@ -0,0 +1,51 @@
"""Example for Kanji features."""
from escpos.printer import Usb
checkerboard_kanji = (
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
)
p = Usb(0x04B8, 0x0E1F, {}, profile="TM-T20II")
p.set_kanji_encoding("iso2022_jp")
p.set(align="center")
p.set_kanji_decoration(double_height=True)
p.set_kanji_underline(2)
p.kanji_text("漢字モード\n")
p.set_kanji_decoration()
p.ln()
p.kanji_text("こんにちは世界!\n")
p.ln()
p.define_user_defined_kanji(b"\x77\x7e", checkerboard_kanji)
p.write_user_defined_kanji(b"\x77\x7e")
p.kanji_text("←外字\n")
p.delete_user_defined_kanji(b"\x77\x7e")
p.cut()

View File

@ -1,4 +1,4 @@
""" Example for software_columns: Print text arranged into columns.""" """Example for software_columns: Print text arranged into columns."""
from escpos import printer from escpos import printer

View File

@ -75,6 +75,6 @@ all =
pywin32; platform_system=='Windows' pywin32; platform_system=='Windows'
[flake8] [flake8]
exclude = .git,.venv,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py exclude = .git,venv,.venv,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
max-line-length = 120 max-line-length = 120
extend-ignore = E203, W503 extend-ignore = E203, W503

View File

@ -303,3 +303,23 @@ RT_MASK_ONLINE: int = 8
RT_MASK_PAPER: int = 18 RT_MASK_PAPER: int = 18
RT_MASK_LOWPAPER: int = 30 RT_MASK_LOWPAPER: int = 30
RT_MASK_NOPAPER: int = 114 RT_MASK_NOPAPER: int = 114
# Kanji mode
KANJI_PRINT_MODE: bytes = FS + b"\x21" # Select Kanji print mode (FS !)
KANJI_ENTER_KANJI_MODE: bytes = FS + b"\x26" # Set Kanji mode (FS &)
KANJI_UNDERLINE: bytes = FS + b"\x2d" # Underline Kanji mode (FS -)
KANJI_EXIT_KANJI_MODE: bytes = FS + b"\x2e" # Cancel Kanji mode (FS .)
KANJI_DEFINE_USER_DEFINED: bytes = (
FS + b"\x32"
) # Define user-defined Kanji characters (FS 2)
KANJI_DELETE_USER_DEFINED: bytes = (
FS + b"\x3f"
) # Cancel user-defined Kanji characters (FS ?)
KANJI_SET_ENCODING: bytes = FS + b"\x43" # Set Kanji code system (FS C)
KANJI_SET_SPACING: bytes = FS + b"\x53" # Select Kanji character spacing (FS S)
KANJI_SET_QUADRUPLE_SIZE: bytes = FS + b"\x57" # Select Kanji quadruple size (FS W)
KANJI_SET_CHAR_STYLE: bytes = FS + b"\x28\x41" # Select Kanji character style (FS ( A)
# ISO-2022-JP Escape Sequences (partially implemented)
ISO2022_JP_ASCII: bytes = b"\x1b\x28\x42"
ISO2022_JP_JIS_X_0208_1983: bytes = b"\x1b\x24\x42" # So-called "New JIS"

View File

@ -52,6 +52,18 @@ from .constants import (
HW_INIT, HW_INIT,
HW_RESET, HW_RESET,
HW_SELECT, HW_SELECT,
ISO2022_JP_ASCII,
ISO2022_JP_JIS_X_0208_1983,
KANJI_DEFINE_USER_DEFINED,
KANJI_DELETE_USER_DEFINED,
KANJI_ENTER_KANJI_MODE,
KANJI_EXIT_KANJI_MODE,
KANJI_PRINT_MODE,
KANJI_SET_CHAR_STYLE,
KANJI_SET_ENCODING,
KANJI_SET_QUADRUPLE_SIZE,
KANJI_SET_SPACING,
KANJI_UNDERLINE,
LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLEAR,
LINE_DISPLAY_CLOSE, LINE_DISPLAY_CLOSE,
LINE_DISPLAY_OPEN, LINE_DISPLAY_OPEN,
@ -134,6 +146,7 @@ class Escpos(object, metaclass=ABCMeta):
""" """
self.profile = get_profile(profile) self.profile = get_profile(profile)
self.magic = MagicEncode(self, **(magic_encode_args or {})) self.magic = MagicEncode(self, **(magic_encode_args or {}))
self.kanji_encoding: Optional[str] = None
def __del__(self): def __del__(self):
"""Call self.close upon deletion.""" """Call self.close upon deletion."""
@ -183,7 +196,7 @@ class Escpos(object, metaclass=ABCMeta):
raise NotImplementedError() raise NotImplementedError()
def set_sleep_in_fragment(self, sleep_time_ms: int) -> None: def set_sleep_in_fragment(self, sleep_time_ms: int) -> None:
"""Configures the currently active sleep time after sending a fragment. """Configure the currently active sleep time after sending a fragment.
If during printing an image an issue like "USBTimeoutError: [Errno 110] If during printing an image an issue like "USBTimeoutError: [Errno 110]
Operation timed out" occurs, setting this value to roughly 300 Operation timed out" occurs, setting this value to roughly 300
@ -1494,6 +1507,218 @@ class Escpos(object, metaclass=ABCMeta):
self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration)) self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration))
def _enter_kanji_mode(self) -> None:
"""Enter Kanji mode."""
self._raw(KANJI_ENTER_KANJI_MODE)
def _exit_kanji_mode(self) -> None:
"""Exit Kanji mode."""
self._raw(KANJI_EXIT_KANJI_MODE)
def kanji_text(self, text: str) -> None:
"""Print Kanji text.
:param text: The Kanji text.
:param encoding: The encoding of the text.
:raises ValueError: If the Kanji encoding is not set.
"""
if self.kanji_encoding is None:
raise ValueError("Kanji encoding not set")
elif self.kanji_encoding == "iso2022_jp":
# ISO-2022-JP encoding is a stateful encoding.
# We need to enter and exit Kanji mode.
self._iso2022jp_text(text)
else:
encoded_text = text.encode(self.kanji_encoding, "ignore")
self._enter_kanji_mode()
self._raw(encoded_text)
self._exit_kanji_mode()
def _iso2022jp_text(self, text: str) -> None:
"""Print ISO-2022-JP text."""
encoded = text.encode("iso2022_jp", "ignore")
while len(encoded) > 0:
# find the next escape sequence
escape_pos = encoded.find(b"\x1b", 1)
if escape_pos == -1:
current_chunk = encoded
encoded = b""
else:
current_chunk = encoded[:escape_pos]
encoded = encoded[escape_pos:]
# find encoding
if not current_chunk.startswith(ESC):
# ASCII
self._raw(current_chunk)
elif current_chunk.startswith(ISO2022_JP_ASCII):
# ASCII
stripped = current_chunk[len(ISO2022_JP_ASCII) :]
self._raw(stripped)
elif current_chunk.startswith(ISO2022_JP_JIS_X_0208_1983):
# JIS X 0208-1983
stripped = current_chunk[len(ISO2022_JP_JIS_X_0208_1983) :]
self._enter_kanji_mode()
self._raw(stripped)
self._exit_kanji_mode()
else:
# unknown encoding
pretty_sequence = " ".join([hex(b) for b in current_chunk])
raise ValueError(
"Unimplemented ISO-2022-JP escape sequence: " + pretty_sequence
)
def set_kanji_decoration(
self,
*,
double_width: bool = False,
double_height: bool = False,
underline: Literal[0, 1, 2] = 0,
) -> None:
"""Set the Kanji print mode.
:param double_width: Doubles the width of the text.
:param double_height: Doubles the height of the text.
:param underline: Underlines the text.
"""
n: int = 0x00
if double_width:
n |= 0x04
if double_height:
n |= 0x08
self._raw(KANJI_PRINT_MODE + six.int2byte(n))
self.set_kanji_underline(underline)
def set_kanji_underline(
self,
underline: Literal[0, 1, 2] = 0,
) -> None:
"""Set the Kanji underline mode.
Some printers may only support 1 dot width underline.
:param underline:
The underline mode.
0 = Unset underline.
1 = Set underline with 1 dot width.
2 = Set underline with 2 dot width.
"""
self._raw(KANJI_UNDERLINE + six.int2byte(underline))
def define_user_defined_kanji(
self,
code: bytes,
data: bytes,
) -> None:
"""Set a user defined Kanji character.
:param code: The Kanji code.
:param data: The Kanji data.
"""
self._raw(KANJI_DEFINE_USER_DEFINED + code + data)
def delete_user_defined_kanji(
self,
code: bytes,
) -> None:
"""Delete a user defined Kanji character.
:param code: The Kanji code.
"""
self._raw(KANJI_DELETE_USER_DEFINED + code)
def write_user_defined_kanji(
self,
code: bytes,
) -> None:
"""Write a user defined Kanji character.
:param code: The Kanji code.
"""
self._enter_kanji_mode()
self._raw(code)
self._exit_kanji_mode()
def set_kanji_encoding(
self,
encoding: Literal[
"iso2022_jp",
"shift_jis",
"shift_jis_2004",
"euc_kr", # FIXME test with real device,
"big5", # FIXME test with real device,
"gb2312", # FIXME test with real device,
"gb18030", # FIXME test with real device,
],
) -> None:
"""Select the Kanji encoding.
This command is available only for Japanese model printers.
:param code: Encoding.
:raises ValueError: If the encoding is invalid.
.. todo:: Test the encodings marked above with `FIXME` with a real device.
"""
# Japanese model printer have several Kanji encoding modes.
if (
encoding == "iso2022_jp"
or encoding == "euc_kr"
or encoding == "big5"
or encoding == "gb2312"
or encoding == "gb18030"
):
self._raw(KANJI_SET_ENCODING + b"\x00")
self.kanji_encoding = encoding
elif encoding == "shift_jis":
self._raw(KANJI_SET_ENCODING + b"\x01")
self.kanji_encoding = encoding
elif encoding == "shift_jis_2004":
self._raw(KANJI_SET_ENCODING + b"\x02")
self.kanji_encoding = encoding
else:
raise ValueError("Invalid encoding")
def set_kanji_spacing(
self,
left_spacing: int,
right_spacing: int,
) -> None:
"""Set the Kanji spacing.
Spacing is either 0-255 or 0-32 according to the printer model.
:param left_spacing: The left spacing.
:param right_spacing: The right spacing.
"""
self._raw(
KANJI_SET_SPACING + six.int2byte(left_spacing) + six.int2byte(right_spacing)
)
def set_kanji_quadruple_size(
self,
enable: bool,
) -> None:
"""Set the Kanji quadruple size.
:param enable: Enable quadruple size.
"""
self._raw(KANJI_SET_QUADRUPLE_SIZE + six.int2byte(int(enable)))
def set_kanji_font(
self,
font: Literal[0, 1, 2],
) -> None:
"""Set the Kanji font.
:param font: The Kanji font.
0 font A
1 font B
2 font C
Some fonts may not be available on all printers.
"""
self._raw(KANJI_SET_CHAR_STYLE + b"\x02\x00\x30" + six.int2byte(font))
class EscposIO: class EscposIO:
r"""ESC/POS Printer IO object. r"""ESC/POS Printer IO object.

View File

@ -0,0 +1,190 @@
import pytest
import escpos.printer as printer
from escpos.constants import (
KANJI_DEFINE_USER_DEFINED,
KANJI_DELETE_USER_DEFINED,
KANJI_ENTER_KANJI_MODE,
KANJI_EXIT_KANJI_MODE,
KANJI_PRINT_MODE,
KANJI_SET_CHAR_STYLE,
KANJI_SET_ENCODING,
KANJI_SET_QUADRUPLE_SIZE,
KANJI_SET_SPACING,
KANJI_UNDERLINE,
)
checkerboard_kanji = (
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\xf0\xf0\xf0"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
b"\x0f\x0f\x0f"
)
def test_enter_kanji_mode() -> None:
"""should enter kanji mode."""
instance = printer.Dummy()
instance._enter_kanji_mode()
assert instance.output == KANJI_ENTER_KANJI_MODE
def test_exit_kanji_mode() -> None:
"""should exit kanji mode."""
instance = printer.Dummy()
instance._exit_kanji_mode()
assert instance.output == KANJI_EXIT_KANJI_MODE
def test_kanji_text_ISO_2022_JP() -> None:
"""should print kanji text."""
instance = printer.Dummy()
instance.set_kanji_encoding("iso2022_jp")
instance.kanji_text("Hello世界Hello")
assert instance.output == (
KANJI_SET_ENCODING
+ b"\x00"
+ b"\x48\x65\x6c\x6c\x6f"
+ KANJI_ENTER_KANJI_MODE
+ b"\x40\x24\x33\x26"
+ KANJI_EXIT_KANJI_MODE
+ b"\x48\x65\x6c\x6c\x6f"
)
def test_kanji_text_shift_jis() -> None:
"""should print kanji text."""
instance = printer.Dummy()
instance.set_kanji_encoding("shift_jis")
instance.kanji_text("Hello世界Hello")
assert instance.output == (
KANJI_SET_ENCODING
+ b"\x01"
+ KANJI_ENTER_KANJI_MODE
+ b"\x48\x65\x6c\x6c\x6f"
+ b"\x90\xa2\x8a\x45"
+ b"\x48\x65\x6c\x6c\x6f"
+ KANJI_EXIT_KANJI_MODE
)
def test_kanji_text_without_encoding() -> None:
"""Test behavior when no encoding is set."""
instance = printer.Dummy()
with pytest.raises(ValueError):
instance.kanji_text("Hello世界Hello")
def test_set_kanji_decoration() -> None:
"""should set kanji decoration."""
instance = printer.Dummy()
instance.set_kanji_decoration()
assert instance.output == KANJI_PRINT_MODE + b"\x00" + KANJI_UNDERLINE + b"\x00"
instance = printer.Dummy()
instance.set_kanji_decoration(double_width=True, double_height=True, underline=1)
assert instance.output == KANJI_PRINT_MODE + b"\x0c" + KANJI_UNDERLINE + b"\x01"
def test_define_user_defined_kanji() -> None:
"""should define user defined kanji."""
instance = printer.Dummy()
instance.define_user_defined_kanji(b"\x77\x7e", checkerboard_kanji)
assert (
instance.output == KANJI_DEFINE_USER_DEFINED + b"\x77\x7e" + checkerboard_kanji
)
def test_delete_user_defined_kanji() -> None:
"""should delete user defined kanji."""
instance = printer.Dummy()
instance.delete_user_defined_kanji(b"\x77\x7e")
assert instance.output == KANJI_DELETE_USER_DEFINED + b"\x77\x7e"
def test_kanji_set_encoding() -> None:
"""should set kanji encoding."""
instance = printer.Dummy()
instance.set_kanji_encoding("iso2022_jp")
assert instance.output == KANJI_SET_ENCODING + b"\x00"
instance = printer.Dummy()
instance.set_kanji_encoding("shift_jis")
assert instance.output == KANJI_SET_ENCODING + b"\x01"
instance = printer.Dummy()
instance.set_kanji_encoding("shift_jis_2004")
assert instance.output == KANJI_SET_ENCODING + b"\x02"
instance = printer.Dummy()
instance.set_kanji_encoding("big5")
assert instance.output == KANJI_SET_ENCODING + b"\x00"
instance = printer.Dummy()
instance.set_kanji_encoding("euc_kr")
assert instance.output == KANJI_SET_ENCODING + b"\x00"
instance = printer.Dummy()
instance.set_kanji_encoding("gb2312")
assert instance.output == KANJI_SET_ENCODING + b"\x00"
instance = printer.Dummy()
instance.set_kanji_encoding("gb18030")
assert instance.output == KANJI_SET_ENCODING + b"\x00"
def test_kanji_spacing() -> None:
"""should set kanji spacing."""
instance = printer.Dummy()
instance.set_kanji_spacing(16, 0)
assert instance.output == KANJI_SET_SPACING + b"\x10\x00"
instance = printer.Dummy()
instance.set_kanji_spacing(0, 16)
assert instance.output == KANJI_SET_SPACING + b"\x00\x10"
def test_kanji_quadruple_size() -> None:
"""should set kanji quadruple size."""
instance = printer.Dummy()
instance.set_kanji_quadruple_size(True)
assert instance.output == KANJI_SET_QUADRUPLE_SIZE + b"\x01"
instance = printer.Dummy()
instance.set_kanji_quadruple_size(False)
assert instance.output == KANJI_SET_QUADRUPLE_SIZE + b"\x00"
def test_kanji_set_font() -> None:
"""should set kanji font."""
instance = printer.Dummy()
instance.set_kanji_font(0)
assert instance.output == KANJI_SET_CHAR_STYLE + b"\x02\x00\x30\x00"
instance = printer.Dummy()
instance.set_kanji_font(1)
assert instance.output == KANJI_SET_CHAR_STYLE + b"\x02\x00\x30\x01"
instance = printer.Dummy()
instance.set_kanji_font(2)
assert instance.output == KANJI_SET_CHAR_STYLE + b"\x02\x00\x30\x02"

View File

@ -30,6 +30,7 @@ setenv = PY_IGNORE_IMPORTMISMATCH=1
[testenv:docs] [testenv:docs]
basepython = python basepython = python
changedir = doc changedir = doc
passenv = PYENCHANT_LIBRARY_PATH
deps = sphinx>=7.2.3 deps = sphinx>=7.2.3
setuptools_scm setuptools_scm
python-barcode>=0.15.0,<1 python-barcode>=0.15.0,<1