kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
Alternative .py writer
rodzic
ee9dbb2a1b
commit
db07a856bc
|
@ -25,51 +25,39 @@
|
|||
# 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):
|
||||
def bs(lst, val):
|
||||
while True:
|
||||
m = (len(lst) & ~ 7) >> 1
|
||||
v = ifb(lst[m:])
|
||||
|
@ -79,39 +67,103 @@ def bs(lst, val):
|
|||
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
|
|
@ -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()
|
||||
|
|
|
@ -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,7 +213,8 @@ 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(
|
||||
if alt:
|
||||
res = write_alt_font(
|
||||
outfile,
|
||||
infile,
|
||||
height,
|
||||
|
@ -218,7 +227,24 @@ def main( # noqa: C901, PLR0913, PLR0912
|
|||
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.")
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue