import argparse
import os
from pathlib import Path
import PIL
import numpy as np
from PIL import ImageDraw, ImageFont
class cd:
def __init__(self, target):
self.target = target
self.old_path = None
def __enter__(self):
self.old_path = os.getcwd()
os.chdir(self.target)
def __exit__(self, etype, value, traceback):
os.chdir(self.old_path)
class FontRender:
def __init__(self, font_file: str, char_table_file: str, font_size_pixel: int = 50, output_dir=None, chan=1):
self.font_file = font_file
self.font_size_pixel: int = font_size_pixel
self.char_table_file = char_table_file
self.output_dir = Path(output_dir or Path(__file__).parent / "output")
self.chan = chan
self.font = ImageFont.truetype(
self.font_file,
self.font_size_pixel,
layout_engine=ImageFont.Layout.BASIC,
)
ascent, descent = self.font.getmetrics()
self.max_font_height = ascent + descent
def put_text(self, draw, font, c, pos, debug=False):
(w, h) = pos
draw.text((w, h), c, (255, 255, 255) if self.chan > 1 else 255, font=font)
bbox = list(font.getbbox(c))
advance = bbox[2] - bbox[0]
ascent, descent = font.getmetrics()
if debug:
bbox[0] += w
bbox[1] += h
bbox[2] += w
bbox[3] += h
draw.rectangle(bbox, outline="red")
draw.line((w, h + ascent, w + self.font_size_pixel, h + ascent), fill="red", width=3)
draw.line((w, h, w + self.font_size_pixel, h), fill="blue", width=1)
return advance, ascent + descent
@property
def font_name(self):
return self.font_file.rsplit(".", 1)[0]
@property
def char_table_name(self):
return self.char_table_file.rsplit(".", 1)[0]
@property
def name(self):
return f"{self.font_name}_{self.font_size_pixel}px"
@property
def font_img_advance_filename(self):
return f"{self.name}.txt"
@property
def font_img_bin_filename(self):
return f"{self.name}.bin"
@property
def font_img_filename(self):
return f"{self.name}.png"
def gen(self):
with open(self.char_table_file, encoding="utf-8") as f:
char_table = [line for line in f.read().split("\n") if line]
height, width = (len(char_table) * self.max_font_height, self.font_size_pixel)
shape = (height, width, self.chan) if self.chan > 1 else (height, width)
pil_img = PIL.Image.fromarray(np.zeros(shape, dtype=np.uint8))
draw = ImageDraw.Draw(pil_img)
c_with_advance = []
for idx, c in enumerate(char_table):
advance, height = self.put_text(draw, self.font, c, (0, idx * self.max_font_height))
c_with_advance.append((c, advance, height))
img_array = np.array(pil_img)
img_array_binary = np.where(img_array >= 20, 255, 0).astype(np.uint8)
with cd(self.output_dir):
with open(self.font_img_advance_filename, "w", encoding="utf-8") as f:
f.writelines(f"{c}+{advance}+{height}\n" for c, advance, height in c_with_advance)
img_array_binary.tofile(self.font_img_bin_filename)
pil_img.save(self.font_img_filename)
def cli_parse():
parser = argparse.ArgumentParser(description="Process some parameters.")
parser.add_argument("--font", type=str, required=True, help="Font type.")
parser.add_argument("--font_size_pixel", type=int, required=True, help="Font size in pixel.")
parser.add_argument("--char_table", type=str, required=True, help="Character table.")
parser.add_argument("--output", type=str, default=None, help="Output dir")
args = parser.parse_args()
return args
def main():
args = cli_parse()
render = FontRender(args.font, args.char_table, args.font_size_pixel, args.output, chan=1)
render.gen()
if __name__ == "__main__":
main()