TDF Send
So on a recommendation from shmoo on Libera, I decided to get myself a Pro duck.ai account for just whatever bullshit I feel like doing. It’s actually so much better than Google’s Gemini subscription shit. Way better. Infinitely better.
Anyways, I vibecoded a zoitechat script that allows you to send “The Draw Font” ascii spam to whatever channel. It works pretty well I guess. I fuckin dig it. I dunno how well it works in edge cases, so if anyone has any updates please do pass along.
Anyway, without further ado:
# tdf_send.py — TheDraw .TDF font sender for ZoiteChat
import os
import re
import shlex
import struct
import zoitechat
__module_name__ = "TDF Send"
__module_version__ = "1.6"
__module_description__ = "Render and send TheDraw .TDF fonts"
TDF_MARKER = b"\x55\xaa\x00\xff"
FIRST_FONT_OFFSET = 20
FONT_HEADER_SIZE = 213
UNDEFINED_OFFSET = 0xffff
DOS_TO_IRC = [1, 2, 3, 10, 4, 6, 7, 15, 14, 12, 9, 11, 4, 13, 8, 0]
OUTLINE_MAP = {
"A": 0xCD, "B": 0xC4, "C": 0xB3, "D": 0xBA,
"E": 0xD5, "F": 0xBB, "G": 0xD6, "H": 0xBF,
"I": 0xC8, "J": 0xBE, "K": 0xC0, "L": 0xBD,
"M": 0xB5, "N": 0xC7,
}
PC_DISPLAY_GLYPHS = {
1: "☺", 2: "☻", 3: "♥", 4: "♦", 5: "♣", 6: "♠",
7: "•", 8: "◘", 9: "○", 10: "◙", 11: "♂", 12: "♀",
14: "♫", 15: "☼", 16: "►", 17: "◄", 18: "↕", 19: "‼",
20: "¶", 21: "§", 22: "▬", 23: "↨", 24: "↑", 25: "↓",
26: "→", 27: "←", 28: "∟", 29: "↔", 30: "▲", 31: "▼",
127: "⌂",
}
IRC_COLOR_RE = re.compile(r"\x03\d{2},\d{2}")
loaded_fonts = {}
loaded_file = None
def cp437_to_utf8(value):
return PC_DISPLAY_GLYPHS.get(
value,
bytes([value]).decode("cp437", errors="replace"),
)
def font_key(name):
return name.replace("\x00", "").replace("\u00A0", " ").strip().casefold()
def display_width(text):
return len(IRC_COLOR_RE.sub("", text).replace("\x0f", ""))
def compact_irc_colors(line):
result = []
last_color = None
pos = 0
while pos < len(line):
match = IRC_COLOR_RE.match(line, pos)
if match:
color = match.group(0)
if color != last_color:
result.append(color)
last_color = color
pos = match.end()
else:
result.append(line[pos])
pos += 1
return "".join(result)
class TDFFont:
def __init__(self, name, font_type, spacing, data, offsets):
self.name = name
self.font_type = font_type
self.spacing = max(0, spacing - 1)
self.data = data
self.offsets = offsets
def character(self, char):
if char == " ":
return [" "]
code = ord(char)
if code < 33 or code > 126:
return [char]
offset = self.offsets[code - 33]
if offset == UNDEFINED_OFFSET or offset + 2 > len(self.data):
return [char]
width = self.data[offset]
height = self.data[offset + 1]
pos = offset + 2
row = ""
row_columns = 0
rows = []
if width == 0:
return [" "]
while pos < len(self.data):
value = self.data[pos]
pos += 1
if value == 0:
if row or not rows:
rows.append(row + (" " * max(0, width - row_columns)))
break
if value == 13:
rows.append(row + (" " * max(0, width - row_columns)))
row = ""
row_columns = 0
continue
color_value = None
if self.font_type == 2:
if pos >= len(self.data):
break
color_value = self.data[pos]
pos += 1
if self.font_type == 0:
reference = chr(value)
if reference == "@":
glyph = " "
elif reference == "O":
glyph = "\u00A0"
elif reference == "&":
continue
elif reference in OUTLINE_MAP:
glyph = cp437_to_utf8(OUTLINE_MAP[reference])
else:
glyph = cp437_to_utf8(value)
else:
glyph = cp437_to_utf8(value)
if color_value is not None:
fg = DOS_TO_IRC[color_value & 0x0F]
bg = DOS_TO_IRC[(color_value >> 4) & 0x07]
row += "\x03{:02d},{:02d}{}".format(fg, bg, glyph)
else:
row += glyph
row_columns += 1
while len(rows) < height:
rows.append(" " * width)
return rows or [" " * width]
def render(self, text):
rendered = []
for source_line in text.splitlines() or [""]:
glyphs = [self.character(char) for char in source_line]
widths = [
max((display_width(row) for row in glyph), default=1)
for glyph in glyphs
]
height = max((len(glyph) for glyph in glyphs), default=1)
output = [""] * height
for index, glyph in enumerate(glyphs):
for row_index in range(height):
if row_index < len(glyph):
output[row_index] += glyph[row_index]
else:
output[row_index] += " " * widths[index]
if index != len(glyphs) - 1:
for row_index in range(height):
output[row_index] += " " * self.spacing
rendered.extend(row.rstrip() + "\x0f" for row in output)
return rendered
def load_tdf(path):
global loaded_fonts, loaded_file
with open(path, "rb") as source:
blob = source.read()
if not blob.startswith(b"\x13TheDraw FONTS file\x1a"):
raise ValueError("Not a valid TheDraw .TDF file.")
fonts = {}
pos = FIRST_FONT_OFFSET
while pos + FONT_HEADER_SIZE <= len(blob):
if blob[pos:pos + 4] != TDF_MARKER:
break
name_length = min(blob[pos + 4], 12)
name = blob[pos + 5:pos + 5 + name_length].decode(
"cp437",
errors="replace",
).split("\x00", 1)[0].strip()
font_type = blob[pos + 21]
spacing = blob[pos + 22]
block_size = struct.unpack_from("<H", blob, pos + 23)[0]
offsets = list(struct.unpack_from("<94H", blob, pos + 25))
data_start = pos + FONT_HEADER_SIZE
data_end = min(data_start + block_size, len(blob))
if name:
fonts[font_key(name)] = TDFFont(
name,
font_type,
spacing,
blob[data_start:data_end],
offsets,
)
pos = data_start + block_size
if not fonts:
raise ValueError("No readable fonts were found in this file.")
loaded_fonts = fonts
loaded_file = os.path.abspath(path)
def get_font(name):
font = loaded_fonts.get(font_key(name))
if font is None:
raise ValueError(
'Font "{}" was not found. Use /TDFFONTS to list fonts.'.format(name)
)
return font
def send_lines(context, lines):
for line in lines:
line = compact_irc_colors(line.replace("\r", "").replace("\n", ""))
if len(line.encode("utf-8")) > 350:
raise ValueError(
"A rendered line is too wide for IRC. "
"Use shorter text or a narrower font."
)
context.command("SAY " + line)
def tdfload_cb(word, word_eol, userdata):
try:
if len(word) < 2:
raise ValueError("Usage: /TDFLOAD /full/path/to/fonts.tdf")
load_tdf(os.path.expanduser(word_eol[1]))
zoitechat.prnt(
"Loaded {} font(s) from {}".format(len(loaded_fonts), loaded_file)
)
except Exception as error:
zoitechat.prnt("TDF error: {}".format(error))
return zoitechat.EAT_ALL
def tdffonts_cb(word, word_eol, userdata):
if not loaded_fonts:
zoitechat.prnt("No TDF file loaded. Use /TDFLOAD <file>.")
else:
zoitechat.prnt(
"Loaded fonts: " +
", ".join(sorted(font.name for font in loaded_fonts.values()))
)
return zoitechat.EAT_ALL
def tdf_cb(word, word_eol, userdata):
try:
args = shlex.split(word_eol[1])
if len(args) < 2:
raise ValueError('Usage: /TDF "Font Name" text')
send_lines(
zoitechat.get_context(),
get_font(args[0]).render(" ".join(args[1:])),
)
except Exception as error:
zoitechat.prnt("TDF error: {}".format(error))
return zoitechat.EAT_ALL
def tdfto_cb(word, word_eol, userdata):
try:
args = shlex.split(word_eol[1])
if len(args) < 3:
raise ValueError(
'Usage: /TDFTO <#channel-or-nick> "Font Name" text'
)
context = zoitechat.find_context(channel=args[0])
if context is None:
raise ValueError(
'No open channel or query named "{}".'.format(args[0])
)
send_lines(context, get_font(args[1]).render(" ".join(args[2:])))
except Exception as error:
zoitechat.prnt("TDF error: {}".format(error))
return zoitechat.EAT_ALL
zoitechat.hook_command(
"TDFLOAD",
tdfload_cb,
help="/TDFLOAD <file> Load a TheDraw .TDF collection",
)
zoitechat.hook_command(
"TDFFONTS",
tdffonts_cb,
help="/TDFFONTS List loaded fonts",
)
zoitechat.hook_command(
"TDF",
tdf_cb,
help='/TDF "Font Name" <text> Send to the active channel or query',
)
zoitechat.hook_command(
"TDFTO",
tdfto_cb,
help='/TDFTO <#channel-or-nick> "Font Name" <text>',
)
zoitechat.prnt("TDF Send loaded. Use /TDFLOAD <file>.")