#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # # Script to generate menu and intermission screen 'text' graphics for # Freedoom, by compositing individual font character graphics together. # from PIL import Image from glob import glob import re import sys from config import * from image_dimensions import * from tint import image_tint class TextGenerator(object): def __init__(self, fontdir, kerning_table={}): self.fontdir = fontdir self.kerning_table = self.compile_kerning_table(kerning_table) self.get_font_widths() # Tinting parameters for colorizing text: COLOR_BLUE = "#000001" COLOR_RED = "#010000" COLOR_WHITE = None # Height of font in pixels. FONT_HEIGHT = 15 FONT_LC_HEIGHT = 15 # 12 # If true, the font only has uppercase characters. UPPERCASE_FONT = False # Width of a space character in pixels. SPACE_WIDTH = 7 LOWERCASE_RE = re.compile(r"^[a-z\!\. ]*$") def compile_kerning_table(self, kerning_table): """Given a dictionary of kerning patterns, compile Regexps.""" result = {} for pattern, adjust in kerning_table.items(): result[re.compile(pattern)] = adjust return result def get_font_widths(self): charfiles = glob("%s/font*.png" % self.fontdir) self.char_widths = {} for c in range(128): filename = self.char_filename(chr(c)) if filename not in charfiles: continue w, _ = get_image_dimensions(filename) self.char_widths[chr(c)] = w def __contains__(self, c): return c in self.char_widths def char_width(self, c): return self.char_widths[c] def char_filename(self, c): return "%s/font%03d.png" % (self.fontdir, ord(c)) def kerning_adjust(self, char_1, char_2): """Get kerning adjustment for pair of characters. Zero means no adjustment. A negative value adjusts to the left and a positive value adjusts to the right. """ for pattern, adjust in self.kerning_table.items(): if pattern.match(char_1 + char_2): return adjust else: return 0 def iterate_char_positions(self, text): """Iterate over characters in string, yielding character with position it should be placed at in the output file. """ x = 0 last_c = " " for c in text: if c == " ": x += self.SPACE_WIDTH if c in self: x += self.kerning_adjust(last_c, c) yield c, x # Characters overlap by one pixel. x += self.char_width(c) - 1 last_c = c # We need to add back the missing pixel from the right side # of the last char. x += 1 yield None, x def text_width(self, text): """Given a string of text, get text width in pixels.""" for c, x in self.iterate_char_positions(text): if c is None: return x def generate_graphic(self, text, color=None): """Get command to render text to a file with the given background color. """ if self.UPPERCASE_FONT: text = text.upper() """Command line construction helper, used in render functions""" width = self.text_width(text) if self.LOWERCASE_RE.match(text): height = self.FONT_LC_HEIGHT else: height = self.FONT_HEIGHT txt_image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) for c, x in self.iterate_char_positions(text): if c is None: break filename = self.char_filename(c) char_image = Image.open(filename) char_image.load() int_image = Image.new("RGBA", txt_image.size, (0, 0, 0, 0)) int_image.paste(char_image, (x, height - self.FONT_HEIGHT)) txt_image = Image.alpha_composite(txt_image, int_image) txt_image = image_tint(txt_image, color) return txt_image def generate_graphics(font, graphics, color=None): for name, text in sorted(graphics.items()): # write a makefile fragment target = "%s.png" % name image = font.generate_graphic(text, color=color) image.save(target) def generate_kerning_test(font): pairs = [] for c1 in sorted(font.char_widths): char1 = "%c" % c1 for c2 in sorted(font.char_widths): char2 = "%c" % c2 if font.kerning_adjust(char1, char2) != 0: pairs.append(char1 + char2) image = font.generate_graphic(" ".join(pairs)) image.save("kerning.png") if __name__ == "__main__": font = TextGenerator("fontchars", kerning_table=FONT_KERNING_RULES) generate_graphics(font, red_graphics, color=font.COLOR_RED) generate_graphics(font, blue_graphics, color=font.COLOR_BLUE) generate_graphics(font, white_graphics, color=font.COLOR_WHITE)