shirtgen/gen.py

139 lines
4.5 KiB
Python

from PIL import Image, ImageDraw, ImageFont
import sys
# fonts are defined here
fonts = {
"r": "fonts/F25_Bank_Printer.ttf",
"b": "fonts/F25_Bank_Printer_Bold.ttf",
}
# image size is defined as 5000 x 5000 pixels
size = (5000, 5000)
if len(sys.argv) == 1:
print("please specify a path to a shirtfile")
exit(1)
# parse shirtfile into list of tuples
#
# shirtfile syntax:
# a shirtfile is a plain-text file made up of the following compontents
#
# different text color variants are defined on a line as follows
# variants: [{file_suffix}:{r}:{g}:{b}] ...
# where:
# - file_suffix is the suffix to be added to a file of this variant, can be left empty
# - r, g and b are the red, green and blue color components respecively (0-255)
# if no variants are specified white text will be output to a file with no suffix
#
# lines of text are defined as lines in the following format
# {alignment}:{font}:{size} {text}
# where:
# - alignment can be:
# * 'm' for centered text
# * 'l' for left aligned text
# * 'r' for right aligned text
# - font can be one of the configured fonts above, included per default are:
# * 'r' for F25 Bank Printer (regular)
# * 'b' for F25 Bank Printer Bold
# - size is either 'fill' to automatically calculate a font size to fill the width of the canvas
# or a font size in pixels
# - text is your desired line of text (with no line breaks)
#
# the shirtfile parser treats lines starting with # as comments and ignores empty lines
#
variants = []
texts = []
with open(sys.argv[1], "r") as f:
lines = f.readlines()
for line in lines:
# skip comments and empyt lines
if line.startswith("#") or len(line.strip()) == 0:
continue
# parse variants
if line.startswith("variants:"):
if len(variants) > 0:
print("variants block can only appear once")
exit(1)
parts = line.strip().removeprefix("variants: ").split(" ")
for variant in parts:
subparts = variant.split(":")
variants.append(
(subparts[0], (int(subparts[1]), int(subparts[2]), int(subparts[3]))))
continue
# parse lines
parts = line.strip().split(" ", 1)
texts.append(tuple(parts[0].split(":")) + (parts[1],))
# default to white output with no filename suffix
if len(variants) == 0:
variants.append(("", (255, 255, 255)))
# quick maths
# calculate appropriate text sizes and bounding box sizes for positioning later
p_texts = []
total_height = 0
for text in texts:
font_size = 50
font_path = fonts[text[1]]
bbox_size = (0, 0)
font = None
if text[2] == "fill":
# dynamic font size calc is hell
# reload font at increasing size until we fill out the whole canvas width
while bbox_size[0] < (size[0] - 5):
font = ImageFont.truetype(font_path, font_size)
bbox = font.getbbox(text[3], anchor=f"{text[0]}b")
bbox_size = (bbox[2] - bbox[0], bbox[3] - bbox[1])
font_size += 5
else:
# load font for configured size and do bbox calculation
font_size = int(text[2])
font = ImageFont.truetype(font_path, font_size)
bbox = font.getbbox(text[3], anchor=f"{text[0]}b")
bbox_size = (bbox[2] - bbox[0], bbox[3] - bbox[1])
print(f"selected font size {font_size}")
total_height += bbox[1]
p_texts.append(text + (font, bbox_size))
# account for padding
total_height += (len(texts) - 1) * 20
p_variants = []
for variant in variants:
# create transparent image
im = Image.new("RGBA", size, (0, 0, 0, 0))
# draw the text
draw = ImageDraw.Draw(im)
y_offset = 0
for text in p_texts:
# set x location dependent on alignment
loc_x = 0
if text[0] == "m":
loc_x = (im.size[0]/2)
elif text[0] == "r":
loc_x = im.size[0]
# calculate y location based on total height of all text + current line height to
# center all text together in the center of the canvas
loc_y = (im.size[1]/2 + total_height / 2) + text[5][1] + y_offset
draw.text((loc_x, loc_y), text[3], font=text[4],
anchor=f"{text[0]}b", fill=variant[1])
# add height of this line to offset
y_offset += text[5][1] + 20
# crop to content and save
im = im.crop(im.getbbox())
filename = sys.argv[1].removesuffix(".shirt")
im.save(f"{filename}{variant[0]}.png", 'PNG')