From db07a856bc8806653a085155a473f3c792b57b6e Mon Sep 17 00:00:00 2001 From: Jacques Supcik Date: Thu, 26 Oct 2023 16:07:49 +0200 Subject: [PATCH] Alternative .py writer --- font_to_py/{writer.py => alt_writer.py} | 220 +++++++++++------------- font_to_py/byte_writer.py | 22 +++ font_to_py/cli.py | 54 ++++-- font_to_py/py_writer.py | 11 +- 4 files changed, 168 insertions(+), 139 deletions(-) rename font_to_py/{writer.py => alt_writer.py} (52%) diff --git a/font_to_py/writer.py b/font_to_py/alt_writer.py similarity index 52% rename from font_to_py/writer.py rename to font_to_py/alt_writer.py index 6443a1c..6fecc8a 100755 --- a/font_to_py/writer.py +++ b/font_to_py/alt_writer.py @@ -25,93 +25,145 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import importlib.metadata import sys from pathlib import Path import click import freetype -from .byte_writer import ByteWriter +from .byte_writer import AltByteWriter from .font import Font -VERSION = importlib.metadata.version("micropython-font-to-py") - - -# Define a global -def var_write(stream, name, value): - stream.write(f"{name} = {value}\n") - - -STR01 = """# Code generated by font_to_py. +HEADER_TPL = """# Code generated by font_to_py. # Font: {font:s}{charset:s} # Cmd: {cmd:s} -version = '{version:s}' """ # Code emitted for charsets spanning a small range of ordinal values -STR02 = """_mvfont = memoryview(_font) -_mvi = memoryview(_index) -ifb = lambda l : l[0] | (l[1] << 8) +GET_CH_I_TPL = """def get_ch(self, ch): + def ifb(l): + return l[0] | (l[1] << 8) -def get_ch(ch): oc = ord(ch) - ioff = 2 * (oc - {min:d} + 1) if oc >= {min:d} and oc <= {max:d} else 0 - doff = ifb(_mvi[ioff : ]) - width = ifb(_mvfont[doff : ]) -""" + ioff = 2 * (oc - 32 + 1) if oc >= self.min_ch and oc <= self.max_ch else 0 + doff = ifb(self._index[ioff:]) + width = ifb(self._font[doff:])""" # Code emiited for large charsets, assumed by build_arrays() to be sparse. # Binary search of sorted sparse index. # Offset into data array is saved after dividing by 8 -STRSP = """_mvfont = memoryview(_font) -_mvsp = memoryview(_sparse) -ifb = lambda l : l[0] | (l[1] << 8) +GET_CH_S_TPL = """def get_ch(self, ch): + def ifb(l): + return l[0] | (l[1] << 8) -def bs(lst, val): - while True: - m = (len(lst) & ~ 7) >> 1 - v = ifb(lst[m:]) - if v == val: - return ifb(lst[m + 2:]) - if not m: - return 0 - lst = lst[m:] if v < val else lst[:m] + def bs(lst, val): + while True: + m = (len(lst) & ~ 7) >> 1 + v = ifb(lst[m:]) + if v == val: + return ifb(lst[m + 2:]) + if not m: + return 0 + lst = lst[m:] if v < val else lst[:m] -def get_ch(ch): - doff = bs(_mvsp, ord(ch)) << 3 - width = ifb(_mvfont[doff : ]) -""" + doff = bs(self._sparse, ord(ch)) << 3 + width = ifb(self._font[doff : ])""" # Code emitted for horizontally mapped fonts. -STR02H = """ - next_offs = doff + 2 + ((width - 1)//8 + 1) * {height:d} - return _mvfont[doff + 2:next_offs], {height:d}, width +GET_CH_HMAP_TPL = """ + next_offs = doff + 2 + ((width - 1)//8 + 1) * self.height + return _mvfont[doff + 2:next_offs], self.height, width """ # Code emitted for vertically mapped fonts. -STR02V = """ - next_offs = doff + 2 + (({height:d} - 1)//8 + 1) * width - return _mvfont[doff + 2:next_offs], {height:d}, width +GET_CH_VMAP_TPL = """ + next_offs = doff + 2 + ((self.height - 1)//8 + 1) * width + return _mvfont[doff + 2:next_offs], self.height, width """ # Extra code emitted where -i is specified. -STR03 = ''' -def glyphs(): - for c in """{}""": - yield c, get_ch(c) +GLYPH_TPL = ''' +def glyphs(self): + for c in """{keys:s}""": + yield c, self.get_ch(c) ''' +OBJECT_BEGIN_TPL = """font = type( + "", + (object,), + { +""" -def write_func(stream, name, arg): - stream.write(f"def {name}():\n return {arg}\n\n") +OBJECT_END_TPL = """ }, +)() +""" -def write_font( # noqa: PLR0913 +def write_attribute(stream, name, value, indent=8): + stream.write(" " * indent + f'"{name}": {value},\n') + + +def write_data( # noqa: PLR0913 + stream, fnt, font_path, hmap, reverse, iterate, charset +): + height = fnt.height # Actual height, not target height + minchar = min(fnt.crange) + maxchar = max(fnt.crange) + st = "" if charset == "" else f" Char set: {charset}" + cl = " ".join([str(Path(sys.argv[0]).stem), *sys.argv[1:]]) + stream.write(HEADER_TPL.format(font=Path(font_path).stem, charset=st, cmd=cl)) + + data, index, sparse = fnt.build_arrays(hmap, reverse) + + if sparse: # build_arrays() has returned a sparse index + click.echo("Sparse") + stream.write(GET_CH_S_TPL) + else: + click.echo("Normal") + stream.write(GET_CH_I_TPL) + + if hmap: + stream.write(GET_CH_HMAP_TPL) + else: + stream.write(GET_CH_VMAP_TPL) + + if iterate: + stream.write(GLYPH_TPL.format(keys="".join(sorted(fnt.keys())))) + + stream.write(OBJECT_BEGIN_TPL) + write_attribute(stream, "height", height) + write_attribute(stream, "baseline", fnt._max_ascent) + write_attribute(stream, "max_width", fnt.max_width) + write_attribute(stream, "hmap", hmap) + write_attribute(stream, "reverse", reverse) + write_attribute(stream, "monospaced", fnt.monospaced) + write_attribute(stream, "min_ch", minchar) + write_attribute(stream, "max_ch", maxchar) + + bw_font = AltByteWriter(stream, "_font") + bw_font.odata(data) + bw_font.eot() + + if sparse: # build_arrays() has returned a sparse index + bw_sparse = AltByteWriter(stream, "_sparse") + bw_sparse.odata(sparse) + bw_sparse.eot() + else: + bw_index = AltByteWriter(stream, "_index") + bw_index.odata(index) + bw_index.eot() + + write_attribute(stream, "get_ch", "get_ch") + if iterate: + write_attribute(stream, "glyphs", "glyphs") + stream.write(OBJECT_END_TPL) + + +def write_alt_font( # noqa: PLR0913 op_path, font_path, height, @@ -139,73 +191,3 @@ def write_font( # noqa: PLR0913 click.echo(f"Can't open {op_path} for writing") return False return True - - -def write_data( # noqa: PLR0913 - stream, fnt, font_path, hmap, reverse, iterate, charset -): - height = fnt.height # Actual height, not target height - minchar = min(fnt.crange) - maxchar = max(fnt.crange) - st = "" if charset == "" else f" Char set: {charset}" - cl = " ".join([str(Path(sys.argv[0]).stem), *sys.argv[1:]]) - stream.write( - STR01.format(font=Path(font_path).stem, charset=st, cmd=cl, version=VERSION) - ) - write_func(stream, "height", height) - write_func(stream, "baseline", fnt._max_ascent) - write_func(stream, "max_width", fnt.max_width) - write_func(stream, "hmap", hmap) - write_func(stream, "reverse", reverse) - write_func(stream, "monospaced", fnt.monospaced) - write_func(stream, "min_ch", minchar) - write_func(stream, "max_ch", maxchar) - if iterate: - stream.write(STR03.format("".join(sorted(fnt.keys())))) - data, index, sparse = fnt.build_arrays(hmap, reverse) - bw_font = ByteWriter(stream, "_font") - bw_font.odata(data) - bw_font.eot() - if sparse: # build_arrays() has returned a sparse index - bw_sparse = ByteWriter(stream, "_sparse") - bw_sparse.odata(sparse) - bw_sparse.eot() - stream.write(STRSP) - click.echo("Sparse") - else: - bw_index = ByteWriter(stream, "_index") - bw_index.odata(index) - bw_index.eot() - stream.write(STR02.format(min=minchar, max=maxchar)) - click.echo("Normal") - if hmap: - stream.write(STR02H.format(height=height)) - else: - stream.write(STR02V.format(height=height)) - - -# BINARY OUTPUT -# hmap reverse magic bytes -# 0 0 0x3f 0xe7 -# 1 0 0x40 0xe7 -# 0 1 0x41 0xe7 -# 1 1 0x42 0xe7 -def write_binary_font(op_path, font_path, height, hmap, reverse): - try: - fnt = Font( - font_path, height, 32, 126, True, None, "" - ) # All chars have same width - except freetype.ft_errors.FT_Exception: - click.echo(f"Can't open {font_path}") - return False - sig = 1 if hmap else 0 - if reverse: - sig += 2 - try: - with open(op_path, "wb") as stream: - data = fnt.build_binary_array(hmap, reverse, sig) - stream.write(data) - except OSError: - click.echo(f"Can't open {op_path} for writing") - return False - return True diff --git a/font_to_py/byte_writer.py b/font_to_py/byte_writer.py index d0696dc..137000a 100755 --- a/font_to_py/byte_writer.py +++ b/font_to_py/byte_writer.py @@ -63,3 +63,25 @@ class ByteWriter: if self.bytecount: self._eot() self.stream.write("\n") + + +class AltByteWriter(ByteWriter): + def __init__(self, stream, varname, indent=8): + self.stream = stream + self.indent = indent + self.stream.write(" " * self.indent + f'"{varname}": memoryview(\n') + self.bytecount = 0 # For line breaks + + def _eol(self): + self.stream.write('"\n') + + def _eot(self): + self.stream.write('"\n') + self.stream.write(" " * self.indent + "),\n") + + def _bol(self): + self.stream.write(" " * (self.indent + 4) + 'b"') + + def eot(self): # User force EOL if one hasn't occurred + if self.bytecount: + self._eot() diff --git a/font_to_py/cli.py b/font_to_py/cli.py index daa1e84..2ba1d83 100755 --- a/font_to_py/cli.py +++ b/font_to_py/cli.py @@ -34,6 +34,7 @@ from pathlib import Path import click import freetype +from .alt_writer import write_alt_font from .bin_writer import write_binary_font from .py_writer import write_font @@ -72,6 +73,12 @@ CONTEXT_SETTINGS = dict(max_content_width=100) is_flag=True, help="Produce binary (random access) font file.", ) +@click.option( + "-a", + "--alt", + is_flag=True, + help="Use alt python writer (EXPERIMENTAL).", +) @click.option( "-i", "--iterate", @@ -124,6 +131,7 @@ def main( # noqa: C901, PLR0913, PLR0912 reverse, fixed, binary, + alt, iterate, smallest, largest, @@ -205,20 +213,38 @@ def main( # noqa: C901, PLR0913, PLR0912 click.echo(f"Found font with size {height!s}") click.echo("Writing Python font file.") - if not write_font( - outfile, - infile, - height, - fixed, - xmap, - reverse, - smallest, - largest, - errchar, - cset, - iterate, - bitmapped, - ): + if alt: + res = write_alt_font( + outfile, + infile, + height, + fixed, + xmap, + reverse, + smallest, + largest, + errchar, + cset, + iterate, + bitmapped, + ) + else: + res = write_font( + outfile, + infile, + height, + fixed, + xmap, + reverse, + smallest, + largest, + errchar, + cset, + iterate, + bitmapped, + ) + + if not res: sys.exit(1) click.echo(f"{outfile} written successfully.") diff --git a/font_to_py/py_writer.py b/font_to_py/py_writer.py index 7d15839..d88c003 100755 --- a/font_to_py/py_writer.py +++ b/font_to_py/py_writer.py @@ -37,12 +37,6 @@ from .font import Font VERSION = importlib.metadata.version("micropython-font-to-py") - -# Define a global -def var_write(stream, name, value): - stream.write(f"{name} = {value}\n") - - STR01 = """# Code generated by font_to_py. # Font: {font:s}{charset:s} # Cmd: {cmd:s} @@ -107,6 +101,11 @@ def glyphs(): ''' +# Define a global +def var_write(stream, name, value): + stream.write(f"{name} = {value}\n") + + def write_func(stream, name, arg): stream.write(f"def {name}():\n return {arg}\n\n")