diff --git a/README.md b/README.md index fb406aa..402a9e4 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,14 @@ The resultant file is usable with two varieties of display device drivers: # 2. Solution -This comprises three components, links to docs below: +This comprises four components, links to docs below: - 1. [font_to_py.py](./FONT_TO_PY.md) This utility runs on a PC and converts a - font file to Python source. See below. + 1. [font_to_py.py](./FONT_TO_PY.md) This utility runs on a PC and converts an + industry standard font file to Python source. See below. 2. [Writer and CWriter classes](./writer/WRITER.md) These facilitate rendering text to a monochrome or colour display having a suitable device driver. - 3. [Device driver notes](./writer/DRIVERS.md). Notes for authors of display + 3. [Creating icon fonts](./icon_fonts/README.md) + 4. [Device driver notes](./writer/DRIVERS.md). Notes for authors of display device drivers. Provides details of the font file format and information on ensuring comptibility with the `Writer` classes. diff --git a/c_to_python_font.py b/c_to_python_font.py new file mode 100755 index 0000000..2fec77b --- /dev/null +++ b/c_to_python_font.py @@ -0,0 +1,164 @@ +#! /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) diff --git a/icon_fonts/back.c b/icon_fonts/back.c new file mode 100644 index 0000000..01cf90a --- /dev/null +++ b/icon_fonts/back.c @@ -0,0 +1,8 @@ +#define back_width 19 +#define back_height 19 +static unsigned char back_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, + 0x80, 0x03, 0x06, 0xc0, 0x83, 0x07, 0xf0, 0xc3, 0x07, 0xf8, 0xe3, 0x07, + 0xfe, 0xfb, 0x07, 0xff, 0xff, 0x07, 0xfe, 0xfb, 0x07, 0xf8, 0xf3, 0x07, + 0xf0, 0xc3, 0x07, 0xc0, 0x83, 0x07, 0x80, 0x03, 0x07, 0x00, 0x02, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/icon_fonts/default.c b/icon_fonts/default.c new file mode 100644 index 0000000..570240a --- /dev/null +++ b/icon_fonts/default.c @@ -0,0 +1,8 @@ +#define default_width 19 +#define default_height 19 +static unsigned char default_bits[] = { + 0x01, 0x00, 0x04, 0x02, 0x00, 0x02, 0x04, 0x00, 0x01, 0x08, 0x80, 0x00, + 0x10, 0x40, 0x00, 0x20, 0x20, 0x00, 0x40, 0x10, 0x00, 0x80, 0x08, 0x00, + 0x00, 0x05, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00, + 0x40, 0x10, 0x00, 0x20, 0x20, 0x00, 0x10, 0x40, 0x00, 0x08, 0x80, 0x00, + 0x04, 0x00, 0x01, 0x02, 0x00, 0x02, 0x01, 0x00, 0x04}; diff --git a/icon_fonts/filenames.txt b/icon_fonts/filenames.txt new file mode 100644 index 0000000..263a3b1 --- /dev/null +++ b/icon_fonts/filenames.txt @@ -0,0 +1,7 @@ +# Filenames for media player icons +default.c +play.c # Test +stop.c +pause.c +back.c +fwd.c diff --git a/icon_fonts/fwd.c b/icon_fonts/fwd.c new file mode 100644 index 0000000..5e5582d --- /dev/null +++ b/icon_fonts/fwd.c @@ -0,0 +1,8 @@ +#define fwd_width 19 +#define fwd_height 19 +static unsigned char fwd_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, + 0x03, 0x0e, 0x00, 0x0f, 0x1e, 0x00, 0x1f, 0x7e, 0x00, 0x3f, 0xfe, 0x00, + 0xff, 0xfe, 0x03, 0xff, 0xff, 0x07, 0xff, 0xff, 0x07, 0xff, 0xfe, 0x03, + 0x7f, 0xfe, 0x00, 0x1f, 0x3e, 0x00, 0x0f, 0x0e, 0x00, 0x07, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/icon_fonts/icon_font.jpg b/icon_fonts/icon_font.jpg new file mode 100644 index 0000000..554520c Binary files /dev/null and b/icon_fonts/icon_font.jpg differ diff --git a/icon_fonts/pause.c b/icon_fonts/pause.c new file mode 100644 index 0000000..30334be --- /dev/null +++ b/icon_fonts/pause.c @@ -0,0 +1,8 @@ +#define pause_width 19 +#define pause_height 19 +static unsigned char pause_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, + 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, + 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, + 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, 0xf8, 0xf8, 0x00, + 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/icon_fonts/play.c b/icon_fonts/play.c new file mode 100644 index 0000000..988cffb --- /dev/null +++ b/icon_fonts/play.c @@ -0,0 +1,8 @@ +#define play_width 19 +#define play_height 19 +static unsigned char play_bits[] = { + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x7e, 0x00, 0x00, + 0xfe, 0x01, 0x00, 0xfe, 0x07, 0x00, 0xfe, 0x1f, 0x00, 0xfe, 0x7f, 0x00, + 0xfe, 0xff, 0x01, 0xfe, 0xff, 0x07, 0xfe, 0xff, 0x01, 0xfe, 0x7f, 0x00, + 0xfe, 0x1f, 0x00, 0xfe, 0x07, 0x00, 0xfe, 0x01, 0x00, 0x7e, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/icon_fonts/stop.c b/icon_fonts/stop.c new file mode 100644 index 0000000..f07d42f --- /dev/null +++ b/icon_fonts/stop.c @@ -0,0 +1,8 @@ +#define stop_width 19 +#define stop_height 19 +static unsigned char stop_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, + 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, + 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, + 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, 0xfc, 0xff, 0x00, + 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/writer/WRITER.md b/writer/WRITER.md index 5f46790..268a748 100644 --- a/writer/WRITER.md +++ b/writer/WRITER.md @@ -369,6 +369,9 @@ Save this modified font under a new name. Then run `font_to_py` to create a Python font in a chosen size and comprising only those characters (`-c ABCDE`). Instantiate the buttons with e.g. `text="A"`. +Alternatively icons can be created as bitmaps and converted to Python font +files as [described here](../icon_fonts/README.md). + # 4. Notes Possible future enhancements: