# Some code adapted from Daniel Bader's work at the following URL # https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python # With thanks to Stephen Irons @ironss for various improvements, also to # @enigmaniac for ideas around handling `bdf` and `pcf` files. # The MIT License (MIT) # # Copyright (c) 2016-2023 Peter Hinch # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys from pathlib import Path import click import freetype from .byte_writer import AltByteWriter from .font import Font HEADER_TPL = """# Code generated by font_to_py. # Font: {font:s}{charset:s} # Cmd: {cmd:s} """ # Code emitted for charsets spanning a small range of ordinal values GET_CH_I_TPL = """def get_ch(self, ch): def ifb(l): return l[0] | (l[1] << 8) oc = ord(ch) 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 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] doff = bs(self._sparse, ord(ch)) << 3 width = ifb(self._font[doff : ])""" # Code emitted for horizontally mapped fonts. GET_CH_HMAP_TPL = """ next_offs = doff + 2 + ((width - 1)//8 + 1) * self.height return self._font[doff + 2:next_offs], self.height, width """ # Code emitted for vertically mapped fonts. GET_CH_VMAP_TPL = """ next_offs = doff + 2 + ((self.height - 1)//8 + 1) * width return self._font[doff + 2:next_offs], self.height, width """ # Extra code emitted where -i is specified. GLYPH_TPL = ''' def glyphs(self): for c in """{keys:s}""": yield c, self.get_ch(c) ''' OBJECT_BEGIN_TPL = """font = type( "", (object,), { """ OBJECT_END_TPL = """ }, )() """ 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, monospaced, hmap, reverse, minchar, maxchar, defchar, charset, iterate, bitmapped, ): try: fnt = Font( font_path, height, minchar, maxchar, monospaced, defchar, charset, bitmapped ) except freetype.ft_errors.FT_Exception: click.echo(f"Can't open {font_path}") return False try: with open(op_path, "w", encoding="utf-8") as stream: write_data(stream, fnt, font_path, hmap, reverse, iterate, charset) except OSError: click.echo(f"Can't open {op_path} for writing") return False return True