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')