fix: reset MagicEncode after font switch (ESC M) and hw init (ESC @)#729
Open
larsblumberg wants to merge 3 commits intopython-escpos:masterfrom
Open
fix: reset MagicEncode after font switch (ESC M) and hw init (ESC @)#729larsblumberg wants to merge 3 commits intopython-escpos:masterfrom
larsblumberg wants to merge 3 commits intopython-escpos:masterfrom
Conversation
Some printers (confirmed: NT-5890K) silently reset their active code page
back to the factory default after a font switch (`ESC M n`). `MagicEncode`
was unaware of this hardware-side reset: its cached `self.encoding` remained
stale, causing subsequent `text()` calls to skip `CODEPAGE_CHANGE`. Non-ASCII
characters were then sent in the previously-active encoding (e.g. CP1257)
but interpreted by the printer under its default code page, producing
garbled output.
`ESC @` (`hw("INIT")`) is defined in the ESC/POS spec as a full printer reset
that restores all settings to factory defaults, including the active code
page — so the same fix applies there by spec.
Fix: add `MagicEncode.reset_encoding()` which clears both `self.encoding`
and `self.encoder.used_encodings`, and call it from `set()` after every
font change and from `hw()` after `INIT`. This forces a fresh code page
selection and a `CODEPAGE_CHANGE` re-emission before the next text output.
Why `used_encodings` must also be cleared:
`self.encoding = None` is enough to ensure a `CODEPAGE_CHANGE` is emitted.
However, `used_encodings` biases `find_suitable_encoding()` toward previously-
used code pages. After a reset, that preference is stale: on NT-5890K the
previously-used CP1257 does not function correctly after `ESC M`, so
`MagicEncode` would re-select it, emit `CODEPAGE_CHANGE` → CP1257, and send
e.g. `ü` as `0xFC` — wrong in the printer's default code page. Clearing
`used_encodings` removes the stale bias and lets slot-number ordering take
over, landing on CP850 where `ü` = `0x81`, a byte that is correct in
virtually every Western code page regardless of whether the printer
honours the code page switch.
Follow-up: `used_encodings` could be removed from `MagicEncode` entirely.
`self.encoding` is the mechanism that actually avoids redundant switches: it
keeps the current code page as long as it can encode the next character,
without consulting `used_encodings` at all. `used_encodings` is only consulted
when a switch is already unavoidable — at which point it cannot prevent
any switch, it can only gamble on which encoding might be needed again
later. That saves at most one future switch in the rare case where the
same non-default code page is needed again after having been forced away.
Removing `used_encodings` would also make `reset_encoding()` unnecessary:
with only `self.encoding` to clear, callers would just write
`self.magic.encoding = None` directly, with no need for a helper method.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the four key behaviours introduced by the fix: - reset_encoding() sets self.encoding to None - reset_encoding() clears encoder.used_encodings - the next write after reset always re-emits CODEPAGE_CHANGE (even for the same encoding that was active before the reset) - after clearing used_encodings, find_suitable_encoding picks the lowest-slot encoding rather than the previously-used high-slot one (the exact scenario that caused the NT-5890K umlaut bug)
larsblumberg
added a commit
to larsblumberg/python-escpos
that referenced
this pull request
Mar 21, 2026
larsblumberg
added a commit
to larsblumberg/python-escpos
that referenced
this pull request
Mar 21, 2026
c9a87b6 to
c9370ec
Compare
c9370ec to
052a8f4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug description
Some printers (confirmed: Netum
NT-5890K) silently reset their active code page back to the factory default after a font switch (ESC M n).MagicEncodewas unaware of this hardware-side reset: its cachedself.encodingremained stale, causing subsequenttext()calls to skipCODEPAGE_CHANGE. Non-ASCII characters were then sent in the previously-active encoding (e.g. CP1257) but interpreted by the printer under its default code page, producing garbled output.ESC @(hw("INIT")) is defined in the ESC/POS spec as a full printer reset that restores all settings to factory defaults, including the active code page — so the same fix applies there by spec.Proposed fix
Fix: add
MagicEncode.reset_encoding()which clears bothself.encodingandself.encoder.used_encodings, and call it fromset()after every font change and fromhw()afterINIT. This forces a fresh code page selection and aCODEPAGE_CHANGEre-emission before the next text output.Why
used_encodingsmust also be cleared:self.encoding = Noneis enough to ensure aCODEPAGE_CHANGEis emitted. However,used_encodingsbiasesfind_suitable_encoding()toward previously- used code pages. After a reset, that preference is stale: on NT-5890K the previously-used CP1257 does not function correctly afterESC M, soMagicEncodewould re-select it, emitCODEPAGE_CHANGE→ CP1257, and send e.g.üas0xFC— wrong in the printer's default code page. Clearingused_encodingsremoves the stale bias and lets slot-number ordering take over, landing on CP850 whereü=0x81, a byte that is correct in virtually every Western code page regardless of whether the printer honours the code page switch.Demo of the the bug and the applied fix:
Future work
Follow-up:
used_encodingscould be removed fromMagicEncodeentirely.self.encodingis the mechanism that actually avoids redundant switches: it keeps the current code page as long as it can encode the next character, without consultingused_encodingsat all.used_encodingsis only consulted when a switch is already unavoidable — at which point it cannot prevent any switch, it can only gamble on which encoding might be needed again later. That saves at most one future switch in the rare case where the same non-default code page is needed again after having been forced away. Removingused_encodingswould also makereset_encoding()unnecessary: with onlyself.encodingto clear, callers would just writeself.magic.encoding = Nonedirectly, with no need for a helper method.