From 42db405bf96e62e3a4daa8c72dae1fc47192291d Mon Sep 17 00:00:00 2001 From: kymok Date: Fri, 7 Mar 2025 01:34:27 +0900 Subject: [PATCH 1/6] Add Kanji-related feature code --- src/escpos/constants.py | 20 ++++ src/escpos/escpos.py | 205 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+) diff --git a/src/escpos/constants.py b/src/escpos/constants.py index 3468484..43b389e 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -303,3 +303,23 @@ RT_MASK_ONLINE: int = 8 RT_MASK_PAPER: int = 18 RT_MASK_LOWPAPER: int = 30 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" diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index fb2e7b8..a5f3a64 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -84,6 +84,18 @@ from .constants import ( TXT_NORMAL, TXT_SIZE, TXT_STYLE, + KANJI_PRINT_MODE, + KANJI_ENTER_KANJI_MODE, + KANJI_UNDERLINE, + KANJI_EXIT_KANJI_MODE, + KANJI_DEFINE_USER_DEFINED, + KANJI_DELETE_USER_DEFINED, + KANJI_SET_ENCODING, + KANJI_SET_SPACING, + KANJI_SET_QUADRUPLE_SIZE, + KANJI_SET_CHAR_STYLE, + ISO2022_JP_JIS_X_0208_1983, + ISO2022_JP_ASCII, ) from .exceptions import ( BarcodeCodeError, @@ -134,6 +146,7 @@ class Escpos(object, metaclass=ABCMeta): """ self.profile = get_profile(profile) self.magic = MagicEncode(self, **(magic_encode_args or {})) + self.kanji_encoding: Optional[str] = None def __del__(self): """Call self.close upon deletion.""" @@ -1494,6 +1507,198 @@ class Escpos(object, metaclass=ABCMeta): self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration)) + def _enter_kanji_mode(self): + """Enters Kanji mode.""" + self._raw(KANJI_ENTER_KANJI_MODE) + + def _exit_kanji_mode(self): + """Exits Kanji mode.""" + self._raw(KANJI_EXIT_KANJI_MODE) + + def kanji_text(self, text: str) -> None: + """Prints 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: + """Prints 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 = "" + 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, + ): + """Sets 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, + ): + """Sets 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, + ): + """Sets 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, + ): + """Deletes a user defined Kanji character. + :param code: The Kanji code. + """ + self._raw(KANJI_DELETE_USER_DEFINED + code) + + 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, + ], + ): + """Selects the Kanji encoding. + This command is available only for Japanese model printers. + + :param code: Encoding. + :raises ValueError: If the encoding is invalid. + """ + # 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, + ): + """Sets 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, + ): + """Sets 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], + ): + """Sets 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: r"""ESC/POS Printer IO object. From 515b0a135774af954df4ace5ac1d31e26478ee0a Mon Sep 17 00:00:00 2001 From: kymok Date: Fri, 7 Mar 2025 01:34:35 +0900 Subject: [PATCH 2/6] Add Kanji-related test --- test/test_functions/test_function_kanji.py | 182 +++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 test/test_functions/test_function_kanji.py diff --git a/test/test_functions/test_function_kanji.py b/test/test_functions/test_function_kanji.py new file mode 100644 index 0000000..1af8b42 --- /dev/null +++ b/test/test_functions/test_function_kanji.py @@ -0,0 +1,182 @@ +import pytest +import escpos.printer as printer +from escpos.constants import ( + KANJI_ENTER_KANJI_MODE, + KANJI_EXIT_KANJI_MODE, + KANJI_PRINT_MODE, + KANJI_UNDERLINE, + KANJI_SET_ENCODING, + KANJI_DEFINE_USER_DEFINED, + KANJI_DELETE_USER_DEFINED, + KANJI_SET_SPACING, + KANJI_SET_CHAR_STYLE, + KANJI_SET_QUADRUPLE_SIZE, +) + +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_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" From e561fb1fa2222afc8c9ec1a163401f68645a3744 Mon Sep 17 00:00:00 2001 From: kymok Date: Fri, 7 Mar 2025 01:34:43 +0900 Subject: [PATCH 3/6] Add kanji-related example --- examples/kanji.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/kanji.py diff --git a/examples/kanji.py b/examples/kanji.py new file mode 100644 index 0000000..69359d4 --- /dev/null +++ b/examples/kanji.py @@ -0,0 +1,51 @@ +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, 0, 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._enter_kanji_mode() +p._raw(b"\x77\x7e") +p._exit_kanji_mode() +p.kanji_text("←外字\n") +p.delete_user_defined_kanji(b"\x77\x7e") +p.cut() From f3e48d8845d537d6652a7af5284e690716cb87e4 Mon Sep 17 00:00:00 2001 From: kymok Date: Fri, 7 Mar 2025 10:59:23 +0900 Subject: [PATCH 4/6] Sort imports --- src/escpos/escpos.py | 24 +++++++++++----------- test/test_functions/test_function_kanji.py | 11 +++++----- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index a5f3a64..f808504 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -52,6 +52,18 @@ from .constants import ( HW_INIT, HW_RESET, 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_CLOSE, LINE_DISPLAY_OPEN, @@ -84,18 +96,6 @@ from .constants import ( TXT_NORMAL, TXT_SIZE, TXT_STYLE, - KANJI_PRINT_MODE, - KANJI_ENTER_KANJI_MODE, - KANJI_UNDERLINE, - KANJI_EXIT_KANJI_MODE, - KANJI_DEFINE_USER_DEFINED, - KANJI_DELETE_USER_DEFINED, - KANJI_SET_ENCODING, - KANJI_SET_SPACING, - KANJI_SET_QUADRUPLE_SIZE, - KANJI_SET_CHAR_STYLE, - ISO2022_JP_JIS_X_0208_1983, - ISO2022_JP_ASCII, ) from .exceptions import ( BarcodeCodeError, diff --git a/test/test_functions/test_function_kanji.py b/test/test_functions/test_function_kanji.py index 1af8b42..787fbe0 100644 --- a/test/test_functions/test_function_kanji.py +++ b/test/test_functions/test_function_kanji.py @@ -1,16 +1,17 @@ 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_UNDERLINE, - KANJI_SET_ENCODING, - KANJI_DEFINE_USER_DEFINED, - KANJI_DELETE_USER_DEFINED, - KANJI_SET_SPACING, KANJI_SET_CHAR_STYLE, + KANJI_SET_ENCODING, KANJI_SET_QUADRUPLE_SIZE, + KANJI_SET_SPACING, + KANJI_UNDERLINE, ) checkerboard_kanji = ( From 3ebc29bdb8e40aae5788afd3ea545f26fab1d1af Mon Sep 17 00:00:00 2001 From: kymok Date: Fri, 7 Mar 2025 16:06:20 +0900 Subject: [PATCH 5/6] Fix tox warnings & errors --- examples/kanji.py | 4 ++- examples/software_columns.py | 2 +- src/escpos/escpos.py | 52 ++++++++++++++++++++---------------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/examples/kanji.py b/examples/kanji.py index 69359d4..02f3311 100644 --- a/examples/kanji.py +++ b/examples/kanji.py @@ -1,3 +1,5 @@ +"""Example for Kanji features.""" + from escpos.printer import Usb checkerboard_kanji = ( @@ -27,7 +29,7 @@ checkerboard_kanji = ( b"\x0f\x0f\x0f" ) -p = Usb(0x04B8, 0x0E1F, 0, profile="TM-T20II") +p = Usb(0x04B8, 0x0E1F, {}, profile="TM-T20II") p.set_kanji_encoding("iso2022_jp") diff --git a/examples/software_columns.py b/examples/software_columns.py index a89e1d4..a6361c7 100644 --- a/examples/software_columns.py +++ b/examples/software_columns.py @@ -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 diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index f808504..57460e3 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -196,7 +196,7 @@ class Escpos(object, metaclass=ABCMeta): raise NotImplementedError() 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] Operation timed out" occurs, setting this value to roughly 300 @@ -1508,21 +1508,20 @@ class Escpos(object, metaclass=ABCMeta): self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration)) def _enter_kanji_mode(self): - """Enters Kanji mode.""" + """Enter Kanji mode.""" self._raw(KANJI_ENTER_KANJI_MODE) def _exit_kanji_mode(self): - """Exits Kanji mode.""" + """Exit Kanji mode.""" self._raw(KANJI_EXIT_KANJI_MODE) def kanji_text(self, text: str) -> None: - """Prints Kanji text. + """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": @@ -1536,14 +1535,14 @@ class Escpos(object, metaclass=ABCMeta): self._exit_kanji_mode() def _iso2022jp_text(self, text: str) -> None: - """Prints ISO-2022-JP text.""" + """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 = "" + encoded = b"" else: current_chunk = encoded[:escape_pos] encoded = encoded[escape_pos:] @@ -1576,7 +1575,7 @@ class Escpos(object, metaclass=ABCMeta): double_height: bool = False, underline: Literal[0, 1, 2] = 0, ): - """Sets the Kanji print mode. + """Set the Kanji print mode. :param double_width: Doubles the width of the text. :param double_height: Doubles the height of the text. @@ -1594,14 +1593,15 @@ class Escpos(object, metaclass=ABCMeta): self, underline: Literal[0, 1, 2] = 0, ): - """Sets the Kanji underline mode. + """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. + :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)) @@ -1610,7 +1610,7 @@ class Escpos(object, metaclass=ABCMeta): code: bytes, data: bytes, ): - """Sets a user defined Kanji character. + """Set a user defined Kanji character. :param code: The Kanji code. :param data: The Kanji data. @@ -1621,7 +1621,8 @@ class Escpos(object, metaclass=ABCMeta): self, code: bytes, ): - """Deletes a user defined Kanji character. + """Delete a user defined Kanji character. + :param code: The Kanji code. """ self._raw(KANJI_DELETE_USER_DEFINED + code) @@ -1638,7 +1639,8 @@ class Escpos(object, metaclass=ABCMeta): "gb18030", # FIXME test with real device, ], ): - """Selects the Kanji encoding. + """Select the Kanji encoding. + This command is available only for Japanese model printers. :param code: Encoding. @@ -1668,8 +1670,10 @@ class Escpos(object, metaclass=ABCMeta): left_spacing: int, right_spacing: int, ): - """Sets the Kanji spacing. + """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. """ @@ -1681,7 +1685,8 @@ class Escpos(object, metaclass=ABCMeta): self, enable: bool, ): - """Sets the Kanji quadruple size. + """Set the Kanji quadruple size. + :param enable: Enable quadruple size. """ self._raw(KANJI_SET_QUADRUPLE_SIZE + six.int2byte(int(enable))) @@ -1690,12 +1695,13 @@ class Escpos(object, metaclass=ABCMeta): self, font: Literal[0, 1, 2], ): - """Sets the Kanji font. + """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. + 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)) From bd83c979eda97443038b5450c0a618263f0ea865 Mon Sep 17 00:00:00 2001 From: kymok Date: Sun, 20 Apr 2025 07:46:39 +0900 Subject: [PATCH 6/6] fix return types; add user defined kanji function --- examples/kanji.py | 4 +-- setup.cfg | 2 +- src/escpos/escpos.py | 34 +++++++++++++++------- test/test_functions/test_function_kanji.py | 9 +++++- tox.ini | 1 + 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/examples/kanji.py b/examples/kanji.py index 02f3311..4d7bf6c 100644 --- a/examples/kanji.py +++ b/examples/kanji.py @@ -45,9 +45,7 @@ p.kanji_text("こんにちは世界!\n") p.ln() p.define_user_defined_kanji(b"\x77\x7e", checkerboard_kanji) -p._enter_kanji_mode() -p._raw(b"\x77\x7e") -p._exit_kanji_mode() +p.write_user_defined_kanji(b"\x77\x7e") p.kanji_text("←外字\n") p.delete_user_defined_kanji(b"\x77\x7e") p.cut() diff --git a/setup.cfg b/setup.cfg index e8fe3a3..67dc57d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,6 +75,6 @@ all = pywin32; platform_system=='Windows' [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 extend-ignore = E203, W503 diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 57460e3..7b74f05 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -1507,11 +1507,11 @@ class Escpos(object, metaclass=ABCMeta): self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration)) - def _enter_kanji_mode(self): + def _enter_kanji_mode(self) -> None: """Enter Kanji mode.""" self._raw(KANJI_ENTER_KANJI_MODE) - def _exit_kanji_mode(self): + def _exit_kanji_mode(self) -> None: """Exit Kanji mode.""" self._raw(KANJI_EXIT_KANJI_MODE) @@ -1574,7 +1574,7 @@ class Escpos(object, metaclass=ABCMeta): 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. @@ -1592,7 +1592,7 @@ class Escpos(object, metaclass=ABCMeta): 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. @@ -1609,7 +1609,7 @@ class Escpos(object, metaclass=ABCMeta): self, code: bytes, data: bytes, - ): + ) -> None: """Set a user defined Kanji character. :param code: The Kanji code. @@ -1620,13 +1620,25 @@ class Escpos(object, metaclass=ABCMeta): 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[ @@ -1638,13 +1650,15 @@ class Escpos(object, metaclass=ABCMeta): "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 ( @@ -1669,7 +1683,7 @@ class Escpos(object, metaclass=ABCMeta): 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. @@ -1684,7 +1698,7 @@ class Escpos(object, metaclass=ABCMeta): def set_kanji_quadruple_size( self, enable: bool, - ): + ) -> None: """Set the Kanji quadruple size. :param enable: Enable quadruple size. @@ -1694,7 +1708,7 @@ class Escpos(object, metaclass=ABCMeta): def set_kanji_font( self, font: Literal[0, 1, 2], - ): + ) -> None: """Set the Kanji font. :param font: The Kanji font. diff --git a/test/test_functions/test_function_kanji.py b/test/test_functions/test_function_kanji.py index 787fbe0..3bb1c34 100644 --- a/test/test_functions/test_function_kanji.py +++ b/test/test_functions/test_function_kanji.py @@ -88,6 +88,13 @@ def test_kanji_text_shift_jis() -> None: ) +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() @@ -96,7 +103,7 @@ def test_set_kanji_decoration() -> None: 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" + assert instance.output == KANJI_PRINT_MODE + b"\x0c" + KANJI_UNDERLINE + b"\x01" def test_define_user_defined_kanji() -> None: diff --git a/tox.ini b/tox.ini index b058d51..8f6ab22 100644 --- a/tox.ini +++ b/tox.ini @@ -30,6 +30,7 @@ setenv = PY_IGNORE_IMPORTMISMATCH=1 [testenv:docs] basepython = python changedir = doc +passenv = PYENCHANT_LIBRARY_PATH deps = sphinx>=7.2.3 setuptools_scm python-barcode>=0.15.0,<1