added rounding to 1/8th of a dollar for price

This commit is contained in:
Joe Lothan 2026-06-15 18:38:54 -04:00
parent ff248a9b68
commit 6efed3be72
2 changed files with 274 additions and 2 deletions

267
glyphs.py Normal file
View file

@ -0,0 +1,267 @@
"""Custom fraction glyphs for the TM-T88V receipt printer.
Each glyph is a 12-wide x 24-tall pixel bitmap defined as a list of 24 strings.
'#' = pixel on, '.' = pixel off.
To customize a glyph, edit its pattern below. Each row must be exactly 12
characters wide and there must be exactly 24 rows per glyph.
"""
# Map each fraction to a character code on the printer.
# Must be in 0x20-0x7E range 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",
}
# fmt: off
GLYPHS = {
"1/8": [
"............", # 0
".........#..", # 1 ── numerator "1"
"........##..", # 2
".........#..", # 3
".........#..", # 4
".........#..", # 5
".........#..", # 6
".........#..", # 7
"........#...", # 8 ── slash
".......#....", # 9
"......#.....", # 10
".....#......", # 11
"....#.......", # 12
"...#........", # 13
"..#.........", # 14
".#..........", # 15
"..##........", # 16 ── denominator "8"
".#..#.......", # 17
".#..#.......", # 18
"..##........", # 19
".#..#.......", # 20
".#..#.......", # 21
"..##........", # 22
"............", # 23
],
"1/4": [
"............", # 0
".........#..", # 1 ── "1"
"........##..", # 2
".........#..", # 3
".........#..", # 4
".........#..", # 5
".........#..", # 6
".........#..", # 7
"........#...", # 8 ── slash
".......#....", # 9
"......#.....", # 10
".....#......", # 11
"....#.......", # 12
"...#........", # 13
"..#.........", # 14
".#..........", # 15
"....#.......", # 16 ── "4"
"...##.......", # 17
"..#.#.......", # 18
".#..#.......", # 19
".####.......", # 20
"....#.......", # 21
"....#.......", # 22
"............", # 23
],
"3/8": [
"............", # 0
"........##..", # 1 ── "3"
".......#..#.", # 2
"..........#.", # 3
"........##..", # 4
"..........#.", # 5
".......#..#.", # 6
"........##..", # 7
"........#...", # 8 ── slash
".......#....", # 9
"......#.....", # 10
".....#......", # 11
"....#.......", # 12
"...#........", # 13
"..#.........", # 14
".#..........", # 15
"..##........", # 16 ── "8"
".#..#.......", # 17
".#..#.......", # 18
"..##........", # 19
".#..#.......", # 20
".#..#.......", # 21
"..##........", # 22
"............", # 23
],
"1/2": [
"............", # 0
".........#..", # 1 ── "1"
"........##..", # 2
".........#..", # 3
".........#..", # 4
".........#..", # 5
".........#..", # 6
".........#..", # 7
"........#...", # 8 ── slash
".......#....", # 9
"......#.....", # 10
".....#......", # 11
"....#.......", # 12
"...#........", # 13
"..#.........", # 14
".#..........", # 15
"..##........", # 16 ── "2"
".#..#.......", # 17
"....#.......", # 18
"...#........", # 19
"..#.........", # 20
".#..........", # 21
".####.......", # 22
"............", # 23
],
"5/8": [
"............", # 0
".......####.", # 1 ── "5"
".......#....", # 2
".......###..", # 3
"..........#.", # 4
"..........#.", # 5
".......#..#.", # 6
"........##..", # 7
"........#...", # 8 ── slash
".......#....", # 9
"......#.....", # 10
".....#......", # 11
"....#.......", # 12
"...#........", # 13
"..#.........", # 14
".#..........", # 15
"..##........", # 16 ── "8"
".#..#.......", # 17
".#..#.......", # 18
"..##........", # 19
".#..#.......", # 20
".#..#.......", # 21
"..##........", # 22
"............", # 23
],
"3/4": [
"............", # 0
"........##..", # 1 ── "3"
".......#..#.", # 2
"..........#.", # 3
"........##..", # 4
"..........#.", # 5
".......#..#.", # 6
"........##..", # 7
"........#...", # 8 ── slash
".......#....", # 9
"......#.....", # 10
".....#......", # 11
"....#.......", # 12
"...#........", # 13
"..#.........", # 14
".#..........", # 15
"....#.......", # 16 ── "4"
"...##.......", # 17
"..#.#.......", # 18
".#..#.......", # 19
".####.......", # 20
"....#.......", # 21
"....#.......", # 22
"............", # 23
],
"7/8": [
"............", # 0
".......####.", # 1 ── "7"
"..........#.", # 2
".........#..", # 3
".........#..", # 4
"........#...", # 5
"........#...", # 6
"........#...", # 7
"........#...", # 8 ── slash
".......#....", # 9
"......#.....", # 10
".....#......", # 11
"....#.......", # 12
"...#........", # 13
"..#.........", # 14
".#..........", # 15
"..##........", # 16 ── "8"
".#..#.......", # 17
".#..#.......", # 18
"..##........", # 19
".#..#.......", # 20
".#..#.......", # 21
"..##........", # 22
"............", # 23
],
}
# fmt: on
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 "57a" where 'a' 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]

View file

@ -14,6 +14,8 @@ import sys
from escpos.printer import Dummy
from glyphs import price_to_fraction, upload_fractions
# TM-T88V with 90° rotation: Font A chars are 24 dots wide (instead of 12),
# so 512 printable dots / 24 = 21 chars fit across the 80mm paper width.
LINE_WIDTH = 21
@ -21,10 +23,11 @@ LINE_WIDTH = 21
def print_trade(p, symbol, lots, price):
"""Print a single trade as ticker tape characters, one per row."""
frac = price_to_fraction(price)
if lots > 1:
price_str = f"{lots}s{price:.2f}"
price_str = f"{lots}s{frac}"
else:
price_str = f"{price:.2f}"
price_str = frac
# Symbol characters at the top (right-aligned)
for ch in symbol:
@ -49,6 +52,8 @@ def main():
# ESC 3 n: set line spacing to n/180 inch. Font A chars are 12 dots wide,
# so n=12 makes rotated characters sit flush with no gap.
p._raw(b"\x1b\x33\x0c")
# Upload custom fraction glyphs (⅛ ¼ ⅜ ½ ⅝ ¾ ⅞)
upload_fractions(p)
with open(csv_file) as f:
reader = csv.reader(f)