#! /usr/bin/env python3 # -*- coding: utf-8 -*- # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2022 Peter Hinch import sys from font_to_py import ByteWriter, var_write, write_func, STR02, STR02H STR01 = """# Code generated by c_to_python_font.py. # Char set: {} # Cmd: {} version = '0.10' """ class Glyph: dstart = 0 # Start index of next glyph def __init__(self): self.width = 0 self.height = 0 def populate(self, fn, data, idx): try: with open(fn, "r") as f: s = f.readline() elements = s.split(" ") if elements[1].endswith("width"): self.width = int(elements[2]) data.extend((self.width).to_bytes(2, 'little')) else: return False s = f.readline() elements = s.split(" ") if elements[1].endswith("height"): self.height = int(elements[2]) else: return False s = f.readline() if not s.startswith("static"): return False while s := f.readline(): if (lb := s.find("}")) != -1: s = s[:lb] # Strip trailing }; p = s.strip().split(',') if not p[-1]: p = p[: -1] z = [int(x, 16) for x in p] data.extend(bytearray(z)) # index points to start of current data block idx.extend((Glyph.dstart).to_bytes(2, 'little')) Glyph.dstart += ((self.width - 1)//8 + 1) * self.height + 2 except OSError: return False return True class Font: def __init__(self): self.glyphs = [] self.height = 0 self.max_width = 0 self.data = bytearray() self.index = bytearray() def __getitem__(self, glyph_index): return self.glyphs[glyph_index] def populate(self, filename="filenames.txt"): try: with open(filename, "r") as f: while fn := f.readline().strip(): # Get current C file if (lc := fn.find("#")) != -1: if (fn := fn[:lc].strip()) == "": continue g = Glyph() if g.populate(fn, self.data, self.index): if ht := self.height: if ht != g.height: print(f"Fatal: file {fn} height is {g.height} while font height is {ht}") return False else: self.height = g.height self.glyphs.append(g) self.max_width = max(self.max_width, g.width) else: print('Failed to read', fn) return False except OSError: print("Failed to read", filename) return False return True def output(self, stream): minchar = ord("A") nglyphs = len(self.glyphs) - 1 # Number less default glyph maxchar = minchar + nglyphs - 1 st = "" for z in range(nglyphs): st = "".join((st, chr(minchar + z))) cl = ' '.join(sys.argv) stream.write(STR01.format(st, cl)) write_func(stream, 'height', self.height) write_func(stream, 'baseline', self.height) write_func(stream, 'max_width', self.max_width) write_func(stream, 'hmap', True) write_func(stream, 'reverse', True) # ???? write_func(stream, 'monospaced', False) write_func(stream, 'min_ch', minchar) write_func(stream, 'max_ch', maxchar) bw_font = ByteWriter(stream, '_font') bw_font.odata(self.data) bw_font.eot() bw_index = ByteWriter(stream, '_index') bw_index.odata(self.index) bw_index.eot() stream.write(STR02.format(minchar, maxchar)) stream.write(STR02H.format(self.height)) def make_font(infile="filenames.txt", outfile="icofont.py"): try: with open(outfile, "w") as f: font = Font() if font.populate(infile): font.output(f) else: return # Failed except OSError: print(f"Failed to open {outfile} for writing.") print(f"{outfile} successfully written.") def version_check(): n = sys.implementation.name v = sys.implementation.version if n == "cpython": if v[0] == 3: return v[1] >= 8 return v[0] > 3 return False # Requires CPython usage = """Convert a set of C bitmaps to a Python font file. Usage: c_to_python_font.py [infile [outfile]] infile contains a list of C bitmap files, one per line. outfile is the name of the output Python font. Default args: infile: filenames.txt outfile: icofont.py e.g. $ ./c_to_python_font.py my_file_list.txt my_font.py """ if __name__ == "__main__": a = sys.argv if len(a) >= 2 and a[1] in ("--help", "-h", "help"): print(usage) elif not version_check(): print("This script requires Python 3.8 or above.") else: infile = a[1] if len(a) >= 2 else "filenames.txt" outfile = a[2] if len(a) >= 3 else "icofont.py" make_font(infile, outfile)