ticker-tape/glyphs.py

226 lines
6.1 KiB
Python

"""Custom fraction glyphs for the TM-T88V receipt printer.
Fractions are composed from small digit patterns stacked vertically:
numerator on top, denominator on bottom — matching the old ticker tape style.
To customize:
- Edit DIGIT_PATTERNS to change how a digit looks in ALL fractions using it.
- Or override a specific fraction in GLYPH_OVERRIDES with a full 12x24 grid.
- '#' = pixel on, '.' = pixel off
- Each digit pattern is 12 wide x 11 tall.
- Each full glyph is 12 wide x 24 tall (11 + 2 gap + 11).
"""
# Character codes used on the printer for each fraction.
# Must be 0x20-0x7E and not conflict with output chars (A-Z, 0-9, '.', 's', ' ')
CHAR_MAP = {
"1/8": "a",
"1/4": "b",
"3/8": "c",
"1/2": "d",
"5/8": "e",
"3/4": "f",
"7/8": "g",
}
# Map number of eighths (1-7) to fraction name
EIGHTHS_TO_NAME = {
1: "1/8",
2: "1/4",
3: "3/8",
4: "1/2",
5: "5/8",
6: "3/4",
7: "7/8",
}
# ── Digit patterns (12 wide x 11 tall) ───────────────────────────────────────
# These are composed into the fraction glyphs below.
# fmt: off
DIGIT_PATTERNS = {
1: [
".....##.....",
"....###.....",
"...####.....",
".....##.....",
".....##.....",
".....##.....",
".....##.....",
".....##.....",
".....##.....",
".....##.....",
"...######...",
],
2: [
"..########..",
".##......##.",
"..........##",
"..........##",
"........##..",
"......##....",
"....##......",
"..##........",
".##.........",
".##.........",
"############",
],
3: [
"..########..",
".##......##.",
"..........##",
"..........##",
".....####...",
"..........##",
"..........##",
"..........##",
".##......##.",
".##......##.",
"..########..",
],
4: [
".......##...",
"......###...",
".....####...",
"....##.##...",
"...##..##...",
"..##...##...",
".##....##...",
"############",
".......##...",
".......##...",
".......##...",
],
5: [
"############",
"##..........",
"##..........",
"##..........",
"##########..",
"..........##",
"..........##",
"..........##",
"..........##",
".##......##.",
"..########..",
],
7: [
"############",
"##........##",
"..........##",
".........##.",
"........##..",
".......##...",
"......##....",
".....##.....",
"....##......",
"....##......",
"....##......",
],
8: [
"..########..",
".##......##.",
".##......##.",
".##......##.",
"..########..",
".##......##.",
".##......##.",
".##......##.",
".##......##.",
".##......##.",
"..########..",
],
}
# fmt: on
def _compose_fraction(num, den):
"""Build a 12x24 glyph: numerator digit stacked over denominator digit.
Layout (24 rows):
rows 0-10: numerator digit (11 rows)
rows 11-12: gap (2 rows)
rows 13-23: denominator digit (11 rows)
"""
blank = "." * 12
rows = list(DIGIT_PATTERNS[num])
rows.extend([blank] * 2)
rows.extend(DIGIT_PATTERNS[den])
return rows
# ── Composed glyphs (auto-generated from digit patterns) ────────────────────
GLYPHS = {
"1/8": _compose_fraction(1, 8),
"1/4": _compose_fraction(1, 4),
"3/8": _compose_fraction(3, 8),
"1/2": _compose_fraction(1, 2),
"5/8": _compose_fraction(5, 8),
"3/4": _compose_fraction(3, 4),
"7/8": _compose_fraction(7, 8),
}
# ── Per-fraction overrides ──────────────────────────────────────────────────
# To hand-craft a specific fraction, add it here as a 24-row list of 12-char
# strings. It will replace the auto-composed version above.
#
# GLYPH_OVERRIDES = {
# "1/2": [
# "............",
# ... # 24 rows total
# ],
# }
GLYPH_OVERRIDES = {}
# Apply overrides
GLYPHS.update(GLYPH_OVERRIDES)
# ── ESC/POS conversion & upload ─────────────────────────────────────────────
def glyph_to_escpos(rows):
"""Convert a visual glyph (24 rows of 12-char strings) to ESC/POS column data.
ESC/POS user-defined chars are column-major: each column is 3 bytes
(24 vertical pixels), MSB = topmost pixel.
"""
data = bytearray()
for col in range(12):
for byte_idx in range(3):
val = 0
for bit in range(8):
row = byte_idx * 8 + bit
if rows[row][col] == "#":
val |= 0x80 >> bit
data.append(val)
return bytes(data)
def upload_fractions(printer):
"""Upload all custom fraction glyphs to the printer via ESC &."""
# ESC % 1: enable user-defined character set
printer._raw(b"\x1b\x25\x01")
for name, char in CHAR_MAP.items():
rows = GLYPHS[name]
col_data = glyph_to_escpos(rows)
code = ord(char)
# ESC & 3 c1 c2 12 [column data]
# 3 = bytes per column (24 dots), 12 = character width
printer._raw(b"\x1b\x26\x03" + bytes([code, code, 12]) + col_data)
def price_to_fraction(price):
"""Convert a decimal price to dollar amount + fraction character.
Rounds to the nearest eighth of a dollar, like old ticker tapes.
Returns e.g. "246" or "57d" where 'd' is the custom ½ glyph.
"""
dollars = int(price)
eighths = round((price - dollars) * 8)
if eighths == 8:
dollars += 1
eighths = 0
if eighths == 0:
return str(dollars)
name = EIGHTHS_TO_NAME[eighths]
return str(dollars) + CHAR_MAP[name]