Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions examples/kanji.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion examples/software_columns.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 20 additions & 0 deletions src/escpos/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,23 @@
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"
227 changes: 226 additions & 1 deletion src/escpos/escpos.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@
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,
Expand Down Expand Up @@ -134,6 +146,7 @@ def __init__(self, profile=None, magic_encode_args=None, **kwargs) -> None:
"""
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."""
Expand Down Expand Up @@ -183,7 +196,7 @@ def _read(self) -> bytes:
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
Expand Down Expand Up @@ -1494,6 +1507,218 @@ def buzzer(self, times: int = 2, duration: int = 4) -> None:

self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration))

def _enter_kanji_mode(self) -> None:
"""Enter Kanji mode."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Enter Kanji mode."""
def _enter_kanji_mode(self) -> None:

Please annotate a return type None for completeness.

self._raw(KANJI_ENTER_KANJI_MODE)

def _exit_kanji_mode(self) -> None:
"""Exit Kanji mode."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Exit Kanji mode."""
def _exit_kanji_mode(self) -> None:

Please annotate a return type None for completeness.

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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now this could be kept in escpos.py, but if this functionality grows more, we should think about a separate module (like magicencode)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _iso2022jp_text(self, text: str) -> None:
def _iso2022jp_text(self, text: str) -> None:

Please annotate a return type None for completeness.

"""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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Set the Kanji print mode.
) -> None:

Please annotate a return type None for completeness.


: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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Set the Kanji underline mode.
) -> None:

Please annotate a return type None for completeness.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Set a user defined Kanji character.
) -> None:

Please annotate a return type None for completeness.


: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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Delete a user defined Kanji character.
) -> None:

Please annotate a return type None for completeness.


: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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please annotate a return type None for completeness.


This command is available only for Japanese model printers.

:param code: Encoding.
Comment on lines +1657 to +1658
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:param code: Encoding.
.. todo:: Test the encodings marked above with `FIXME` with a real device.

Please add a short todo for the FIXME above so that it shows up in this list: https://python-escpos--680.org.readthedocs.build/en/680/dev/todo.html

: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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Set the Kanji spacing.
) -> None:

Please annotate a return type None for completeness.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Set the Kanji quadruple size.
) -> None:

Please annotate a return type None for completeness.


: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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Set the Kanji font.
) -> None:

Please annotate a return type None for completeness.


: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.
Expand Down
Loading