"""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]