micropython-font-to-py/font_to_py/alt_writer.py

194 wiersze
5.7 KiB
Python
Executable File

# 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