kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
font_to_py.py: Run Black over code.
rodzic
21dc7be17f
commit
6f117b1fe7
|
@ -49,7 +49,7 @@ installed using `pip3`. On Linux (you may need a root prompt):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# apt-get install python3-pip
|
# apt-get install python3-pip
|
||||||
# pip3 install freetype-py
|
# pip install freetype-py
|
||||||
```
|
```
|
||||||
|
|
||||||
# 3. Usage
|
# 3. Usage
|
||||||
|
|
294
font_to_py.py
294
font_to_py.py
|
@ -34,13 +34,14 @@
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import freetype
|
import freetype
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
print('font_to_py requires the freetype library. Please see FONT_TO_PY.md.')
|
print("font_to_py requires the freetype library. Please see FONT_TO_PY.md.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if freetype.version()[0] < 1:
|
if freetype.version()[0] < 1:
|
||||||
print('freetype version should be >= 1. Please see FONT_TO_PY.md')
|
print("freetype version should be >= 1. Please see FONT_TO_PY.md")
|
||||||
|
|
||||||
MINCHAR = 32 # Ordinal values of default printable ASCII set
|
MINCHAR = 32 # Ordinal values of default printable ASCII set
|
||||||
MAXCHAR = 126 # 94 chars
|
MAXCHAR = 126 # 94 chars
|
||||||
|
@ -53,12 +54,13 @@ MAXCHAR = 126 # 94 chars
|
||||||
|
|
||||||
# Lines are broken with \ for readability.
|
# Lines are broken with \ for readability.
|
||||||
|
|
||||||
|
|
||||||
class ByteWriter:
|
class ByteWriter:
|
||||||
bytes_per_line = 16
|
bytes_per_line = 16
|
||||||
|
|
||||||
def __init__(self, stream, varname):
|
def __init__(self, stream, varname):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.stream.write('{} =\\\n'.format(varname))
|
self.stream.write("{} =\\\n".format(varname))
|
||||||
self.bytecount = 0 # For line breaks
|
self.bytecount = 0 # For line breaks
|
||||||
|
|
||||||
def _eol(self):
|
def _eol(self):
|
||||||
|
@ -74,7 +76,7 @@ class ByteWriter:
|
||||||
def obyte(self, data):
|
def obyte(self, data):
|
||||||
if not self.bytecount:
|
if not self.bytecount:
|
||||||
self._bol()
|
self._bol()
|
||||||
self.stream.write('\\x{:02x}'.format(data))
|
self.stream.write("\\x{:02x}".format(data))
|
||||||
self.bytecount += 1
|
self.bytecount += 1
|
||||||
self.bytecount %= self.bytes_per_line
|
self.bytecount %= self.bytes_per_line
|
||||||
if not self.bytecount:
|
if not self.bytecount:
|
||||||
|
@ -89,12 +91,13 @@ class ByteWriter:
|
||||||
def eot(self): # User force EOL if one hasn't occurred
|
def eot(self): # User force EOL if one hasn't occurred
|
||||||
if self.bytecount:
|
if self.bytecount:
|
||||||
self._eot()
|
self._eot()
|
||||||
self.stream.write('\n')
|
self.stream.write("\n")
|
||||||
|
|
||||||
|
|
||||||
# Define a global
|
# Define a global
|
||||||
def var_write(stream, name, value):
|
def var_write(stream, name, value):
|
||||||
stream.write('{} = {}\n'.format(name, value))
|
stream.write("{} = {}\n".format(name, value))
|
||||||
|
|
||||||
|
|
||||||
# FONT HANDLING
|
# FONT HANDLING
|
||||||
|
|
||||||
|
@ -105,6 +108,7 @@ class Bitmap:
|
||||||
the state of a single pixel in the bitmap. A value of 0 indicates that the
|
the state of a single pixel in the bitmap. A value of 0 indicates that the
|
||||||
pixel is `off` and any other value indicates that it is `on`.
|
pixel is `off` and any other value indicates that it is `on`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, width, height, pixels=None):
|
def __init__(self, width, height, pixels=None):
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
@ -114,8 +118,8 @@ class Bitmap:
|
||||||
"""Print the bitmap's pixels."""
|
"""Print the bitmap's pixels."""
|
||||||
for row in range(self.height):
|
for row in range(self.height):
|
||||||
for col in range(self.width):
|
for col in range(self.width):
|
||||||
char = '#' if self.pixels[row * self.width + col] else '.'
|
char = "#" if self.pixels[row * self.width + col] else "."
|
||||||
print(char, end='')
|
print(char, end="")
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
@ -279,12 +283,17 @@ class Font(dict):
|
||||||
# except that item 0 is the default char.
|
# except that item 0 is the default char.
|
||||||
if defchar is None: # Binary font
|
if defchar is None: # Binary font
|
||||||
self.charset = [chr(ordv) for ordv in self.crange]
|
self.charset = [chr(ordv) for ordv in self.crange]
|
||||||
elif charset == '':
|
elif charset == "":
|
||||||
self.charset = [chr(defchar)] + [chr(ordv) for ordv in self.crange]
|
self.charset = [chr(defchar)] + [chr(ordv) for ordv in self.crange]
|
||||||
else:
|
else:
|
||||||
cl = [ord(x) for x in chr(defchar) + charset if self._face.get_char_index(x) != 0 ]
|
cl = [ord(x) for x in chr(defchar) + charset if self._face.get_char_index(x) != 0]
|
||||||
self.crange = range(min(cl), max(cl) + 1) # Inclusive ordinal value range
|
self.crange = range(min(cl), max(cl) + 1) # Inclusive ordinal value range
|
||||||
cs = [chr(ordv) if chr(ordv) in charset and self._face.get_char_index(chr(ordv)) != 0 else '' for ordv in self.crange]
|
cs = [
|
||||||
|
chr(ordv)
|
||||||
|
if chr(ordv) in charset and self._face.get_char_index(chr(ordv)) != 0
|
||||||
|
else ""
|
||||||
|
for ordv in self.crange
|
||||||
|
]
|
||||||
# .charset has an item for all chars in range. '' if unsupported.
|
# .charset has an item for all chars in range. '' if unsupported.
|
||||||
# item 0 is the default char. Subsequent chars are in increasing ordinal value.
|
# item 0 is the default char. Subsequent chars are in increasing ordinal value.
|
||||||
self.charset = [chr(defchar)] + cs
|
self.charset = [chr(defchar)] + cs
|
||||||
|
@ -305,17 +314,16 @@ class Font(dict):
|
||||||
max_ascent = max(max_ascent, glyph.ascent)
|
max_ascent = max(max_ascent, glyph.ascent)
|
||||||
max_descent = max(max_descent, glyph.descent)
|
max_descent = max(max_descent, glyph.descent)
|
||||||
# for a few chars e.g. _ glyph.width > glyph.advance_width
|
# for a few chars e.g. _ glyph.width > glyph.advance_width
|
||||||
max_width = int(max(max_width, glyph.advance_width,
|
max_width = int(max(max_width, glyph.advance_width, glyph.width))
|
||||||
glyph.width))
|
|
||||||
|
|
||||||
self.height = int(max_ascent + max_descent)
|
self.height = int(max_ascent + max_descent)
|
||||||
self._max_ascent = int(max_ascent)
|
self._max_ascent = int(max_ascent)
|
||||||
self._max_descent = int(max_descent)
|
self._max_descent = int(max_descent)
|
||||||
print('Requested height', height)
|
print("Requested height", height)
|
||||||
print('Actual height', self.height)
|
print("Actual height", self.height)
|
||||||
print('Max width', max_width)
|
print("Max width", max_width)
|
||||||
print('Max descent', self._max_descent)
|
print("Max descent", self._max_descent)
|
||||||
print('Max ascent', self._max_ascent)
|
print("Max ascent", self._max_ascent)
|
||||||
return max_width
|
return max_width
|
||||||
|
|
||||||
# n-pass solution to setting a precise height.
|
# n-pass solution to setting a precise height.
|
||||||
|
@ -336,27 +344,24 @@ class Font(dict):
|
||||||
max_ascent = max(max_ascent, glyph.ascent)
|
max_ascent = max(max_ascent, glyph.ascent)
|
||||||
max_descent = max(max_descent, glyph.descent)
|
max_descent = max(max_descent, glyph.descent)
|
||||||
# for a few chars e.g. _ glyph.width > glyph.advance_width
|
# for a few chars e.g. _ glyph.width > glyph.advance_width
|
||||||
max_width = int(max(max_width, glyph.advance_width,
|
max_width = int(max(max_width, glyph.advance_width, glyph.width))
|
||||||
glyph.width))
|
|
||||||
|
|
||||||
new_error = required_height - (max_ascent + max_descent)
|
new_error = required_height - (max_ascent + max_descent)
|
||||||
if (new_error == 0) or (abs(new_error) - abs(error) == 0):
|
if (new_error == 0) or (abs(new_error) - abs(error) == 0):
|
||||||
break
|
break
|
||||||
error = new_error
|
error = new_error
|
||||||
self.height = int(max_ascent + max_descent)
|
self.height = int(max_ascent + max_descent)
|
||||||
st = 'Height set in {} passes. Actual height {} pixels.\nMax character width {} pixels.'
|
st = "Height set in {} passes. Actual height {} pixels.\nMax character width {} pixels."
|
||||||
print(st.format(npass + 1, self.height, max_width))
|
print(st.format(npass + 1, self.height, max_width))
|
||||||
self._max_ascent = int(max_ascent)
|
self._max_ascent = int(max_ascent)
|
||||||
self._max_descent = int(max_descent)
|
self._max_descent = int(max_descent)
|
||||||
return max_width
|
return max_width
|
||||||
|
|
||||||
|
|
||||||
def _glyph_for_character(self, char):
|
def _glyph_for_character(self, char):
|
||||||
# Let FreeType load the glyph for the given character and tell it to
|
# Let FreeType load the glyph for the given character and tell it to
|
||||||
# render a monochromatic bitmap representation.
|
# render a monochromatic bitmap representation.
|
||||||
assert char != ''
|
assert char != ""
|
||||||
self._face.load_char(char, freetype.FT_LOAD_RENDER |
|
self._face.load_char(char, freetype.FT_LOAD_RENDER | freetype.FT_LOAD_TARGET_MONO)
|
||||||
freetype.FT_LOAD_TARGET_MONO)
|
|
||||||
return Glyph.from_glyphslot(self._face.glyph)
|
return Glyph.from_glyphslot(self._face.glyph)
|
||||||
|
|
||||||
def _assign_values(self):
|
def _assign_values(self):
|
||||||
|
@ -393,9 +398,10 @@ class Font(dict):
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
index = bytearray()
|
index = bytearray()
|
||||||
sparse = bytearray()
|
sparse = bytearray()
|
||||||
|
|
||||||
def append_data(data, char):
|
def append_data(data, char):
|
||||||
width = self[char][1]
|
width = self[char][1]
|
||||||
data += (width).to_bytes(2, byteorder='little')
|
data += (width).to_bytes(2, byteorder="little")
|
||||||
data += bytearray(self.stream_char(char, hmap, reverse))
|
data += bytearray(self.stream_char(char, hmap, reverse))
|
||||||
|
|
||||||
# self.charset is contiguous with chars having ordinal values in the
|
# self.charset is contiguous with chars having ordinal values in the
|
||||||
|
@ -406,36 +412,37 @@ class Font(dict):
|
||||||
# Build normal index. Efficient for ASCII set and smaller as
|
# Build normal index. Efficient for ASCII set and smaller as
|
||||||
# entries are 2 bytes (-> data[0] for absent glyph)
|
# entries are 2 bytes (-> data[0] for absent glyph)
|
||||||
for char in self.charset:
|
for char in self.charset:
|
||||||
if char == '':
|
if char == "":
|
||||||
index += bytearray((0, 0))
|
index += bytearray((0, 0))
|
||||||
else:
|
else:
|
||||||
index += (len(data)).to_bytes(2, byteorder='little') # Start
|
index += (len(data)).to_bytes(2, byteorder="little") # Start
|
||||||
append_data(data, char)
|
append_data(data, char)
|
||||||
index += (len(data)).to_bytes(2, byteorder='little') # End
|
index += (len(data)).to_bytes(2, byteorder="little") # End
|
||||||
else:
|
else:
|
||||||
# Sparse index. Entries are 4 bytes but only populated if the char
|
# Sparse index. Entries are 4 bytes but only populated if the char
|
||||||
# has a defined glyph.
|
# has a defined glyph.
|
||||||
append_data(data, self.charset[0]) # data[0] is the default char
|
append_data(data, self.charset[0]) # data[0] is the default char
|
||||||
for char in sorted(self.keys()):
|
for char in sorted(self.keys()):
|
||||||
sparse += ord(char).to_bytes(2, byteorder='little')
|
sparse += ord(char).to_bytes(2, byteorder="little")
|
||||||
pad = len(data) % 8
|
pad = len(data) % 8
|
||||||
if pad: # Ensure len(data) % 8 == 0
|
if pad: # Ensure len(data) % 8 == 0
|
||||||
data += bytearray(8 - pad)
|
data += bytearray(8 - pad)
|
||||||
try:
|
try:
|
||||||
sparse += (len(data) >> 3).to_bytes(2, byteorder='little') # Start
|
sparse += (len(data) >> 3).to_bytes(2, byteorder="little") # Start
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
raise ValueError("Total size of font bitmap exceeds 524287 bytes.")
|
raise ValueError("Total size of font bitmap exceeds 524287 bytes.")
|
||||||
append_data(data, char)
|
append_data(data, char)
|
||||||
return data, index, sparse
|
return data, index, sparse
|
||||||
|
|
||||||
def build_binary_array(self, hmap, reverse, sig):
|
def build_binary_array(self, hmap, reverse, sig):
|
||||||
data = bytearray((0x3f + sig, 0xe7, self.max_width, self.height))
|
data = bytearray((0x3F + sig, 0xE7, self.max_width, self.height))
|
||||||
for char in self.charset:
|
for char in self.charset:
|
||||||
width = self[char][2]
|
width = self[char][2]
|
||||||
data += bytes((width,))
|
data += bytes((width,))
|
||||||
data += bytearray(self.stream_char(char, hmap, reverse))
|
data += bytearray(self.stream_char(char, hmap, reverse))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
# PYTHON FILE WRITING
|
# PYTHON FILE WRITING
|
||||||
# The index only holds the start of data so can't read next_offset but must
|
# The index only holds the start of data so can't read next_offset but must
|
||||||
# calculate it.
|
# calculate it.
|
||||||
|
@ -482,14 +489,14 @@ def get_ch(ch):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Code emitted for horizontally mapped fonts.
|
# Code emitted for horizontally mapped fonts.
|
||||||
STR02H ="""
|
STR02H = """
|
||||||
next_offs = doff + 2 + ((width - 1)//8 + 1) * {0}
|
next_offs = doff + 2 + ((width - 1)//8 + 1) * {0}
|
||||||
return _mvfont[doff + 2:next_offs], {0}, width
|
return _mvfont[doff + 2:next_offs], {0}, width
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Code emitted for vertically mapped fonts.
|
# Code emitted for vertically mapped fonts.
|
||||||
STR02V ="""
|
STR02V = """
|
||||||
next_offs = doff + 2 + (({0} - 1)//8 + 1) * width
|
next_offs = doff + 2 + (({0} - 1)//8 + 1) * width
|
||||||
return _mvfont[doff + 2:next_offs], {0}, width
|
return _mvfont[doff + 2:next_offs], {0}, width
|
||||||
|
|
||||||
|
@ -503,54 +510,69 @@ def glyphs():
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def write_func(stream, name, arg):
|
|
||||||
stream.write('def {}():\n return {}\n\n'.format(name, arg))
|
|
||||||
|
|
||||||
def write_font(op_path, font_path, height, monospaced, hmap, reverse, minchar,
|
def write_func(stream, name, arg):
|
||||||
maxchar, defchar, charset, iterate, bitmapped):
|
stream.write("def {}():\n return {}\n\n".format(name, arg))
|
||||||
|
|
||||||
|
|
||||||
|
def write_font(
|
||||||
|
op_path,
|
||||||
|
font_path,
|
||||||
|
height,
|
||||||
|
monospaced,
|
||||||
|
hmap,
|
||||||
|
reverse,
|
||||||
|
minchar,
|
||||||
|
maxchar,
|
||||||
|
defchar,
|
||||||
|
charset,
|
||||||
|
iterate,
|
||||||
|
bitmapped,
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar, charset, bitmapped)
|
fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar, charset, bitmapped)
|
||||||
except freetype.ft_errors.FT_Exception:
|
except freetype.ft_errors.FT_Exception:
|
||||||
print("Can't open", font_path)
|
print("Can't open", font_path)
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
with open(op_path, 'w', encoding='utf-8') as stream:
|
with open(op_path, "w", encoding="utf-8") as stream:
|
||||||
write_data(stream, fnt, font_path, hmap, reverse, iterate, charset)
|
write_data(stream, fnt, font_path, hmap, reverse, iterate, charset)
|
||||||
except OSError:
|
except OSError:
|
||||||
print("Can't open", op_path, 'for writing')
|
print("Can't open", op_path, "for writing")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def write_data(stream, fnt, font_path, hmap, reverse, iterate, charset):
|
def write_data(stream, fnt, font_path, hmap, reverse, iterate, charset):
|
||||||
height = fnt.height # Actual height, not target height
|
height = fnt.height # Actual height, not target height
|
||||||
minchar = min(fnt.crange)
|
minchar = min(fnt.crange)
|
||||||
maxchar = max(fnt.crange)
|
maxchar = max(fnt.crange)
|
||||||
defchar = fnt.defchar
|
defchar = fnt.defchar
|
||||||
st = '' if charset == '' else ' Char set: {}'.format(charset)
|
st = "" if charset == "" else " Char set: {}".format(charset)
|
||||||
cl = ' '.join(sys.argv)
|
cl = " ".join(sys.argv)
|
||||||
stream.write(STR01.format(os.path.split(font_path)[1], st, cl))
|
stream.write(STR01.format(os.path.split(font_path)[1], st, cl))
|
||||||
write_func(stream, 'height', height)
|
write_func(stream, "height", height)
|
||||||
write_func(stream, 'baseline', fnt._max_ascent)
|
write_func(stream, "baseline", fnt._max_ascent)
|
||||||
write_func(stream, 'max_width', fnt.max_width)
|
write_func(stream, "max_width", fnt.max_width)
|
||||||
write_func(stream, 'hmap', hmap)
|
write_func(stream, "hmap", hmap)
|
||||||
write_func(stream, 'reverse', reverse)
|
write_func(stream, "reverse", reverse)
|
||||||
write_func(stream, 'monospaced', fnt.monospaced)
|
write_func(stream, "monospaced", fnt.monospaced)
|
||||||
write_func(stream, 'min_ch', minchar)
|
write_func(stream, "min_ch", minchar)
|
||||||
write_func(stream, 'max_ch', maxchar)
|
write_func(stream, "max_ch", maxchar)
|
||||||
if iterate:
|
if iterate:
|
||||||
stream.write(STR03.format(''.join(sorted(fnt.keys()))))
|
stream.write(STR03.format("".join(sorted(fnt.keys()))))
|
||||||
data, index, sparse = fnt.build_arrays(hmap, reverse)
|
data, index, sparse = fnt.build_arrays(hmap, reverse)
|
||||||
bw_font = ByteWriter(stream, '_font')
|
bw_font = ByteWriter(stream, "_font")
|
||||||
bw_font.odata(data)
|
bw_font.odata(data)
|
||||||
bw_font.eot()
|
bw_font.eot()
|
||||||
if sparse: # build_arrays() has returned a sparse index
|
if sparse: # build_arrays() has returned a sparse index
|
||||||
bw_sparse = ByteWriter(stream, '_sparse')
|
bw_sparse = ByteWriter(stream, "_sparse")
|
||||||
bw_sparse.odata(sparse)
|
bw_sparse.odata(sparse)
|
||||||
bw_sparse.eot()
|
bw_sparse.eot()
|
||||||
stream.write(STRSP)
|
stream.write(STRSP)
|
||||||
print("Sparse")
|
print("Sparse")
|
||||||
else:
|
else:
|
||||||
bw_index = ByteWriter(stream, '_index')
|
bw_index = ByteWriter(stream, "_index")
|
||||||
bw_index.odata(index)
|
bw_index.odata(index)
|
||||||
bw_index.eot()
|
bw_index.eot()
|
||||||
stream.write(STR02.format(minchar, maxchar))
|
stream.write(STR02.format(minchar, maxchar))
|
||||||
|
@ -560,6 +582,7 @@ def write_data(stream, fnt, font_path, hmap, reverse, iterate, charset):
|
||||||
else:
|
else:
|
||||||
stream.write(STR02V.format(height))
|
stream.write(STR02V.format(height))
|
||||||
|
|
||||||
|
|
||||||
# BINARY OUTPUT
|
# BINARY OUTPUT
|
||||||
# hmap reverse magic bytes
|
# hmap reverse magic bytes
|
||||||
# 0 0 0x3f 0xe7
|
# 0 0 0x3f 0xe7
|
||||||
|
@ -568,7 +591,7 @@ def write_data(stream, fnt, font_path, hmap, reverse, iterate, charset):
|
||||||
# 1 1 0x42 0xe7
|
# 1 1 0x42 0xe7
|
||||||
def write_binary_font(op_path, font_path, height, hmap, reverse):
|
def write_binary_font(op_path, font_path, height, hmap, reverse):
|
||||||
try:
|
try:
|
||||||
fnt = Font(font_path, height, 32, 126, True, None, '') # All chars have same width
|
fnt = Font(font_path, height, 32, 126, True, None, "") # All chars have same width
|
||||||
except freetype.ft_errors.FT_Exception:
|
except freetype.ft_errors.FT_Exception:
|
||||||
print("Can't open", font_path)
|
print("Can't open", font_path)
|
||||||
return False
|
return False
|
||||||
|
@ -576,20 +599,23 @@ def write_binary_font(op_path, font_path, height, hmap, reverse):
|
||||||
if reverse:
|
if reverse:
|
||||||
sig += 2
|
sig += 2
|
||||||
try:
|
try:
|
||||||
with open(op_path, 'wb') as stream:
|
with open(op_path, "wb") as stream:
|
||||||
data = fnt.build_binary_array(hmap, reverse, sig)
|
data = fnt.build_binary_array(hmap, reverse, sig)
|
||||||
stream.write(data)
|
stream.write(data)
|
||||||
except OSError:
|
except OSError:
|
||||||
print("Can't open", op_path, 'for writing')
|
print("Can't open", op_path, "for writing")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# PARSE COMMAND LINE ARGUMENTS
|
# PARSE COMMAND LINE ARGUMENTS
|
||||||
|
|
||||||
|
|
||||||
def quit(msg):
|
def quit(msg):
|
||||||
print(msg)
|
print(msg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
DESC = """font_to_py.py V0.4.0
|
DESC = """font_to_py.py V0.4.0
|
||||||
Utility to convert ttf, otf, bdf and pcf font files to Python source.
|
Utility to convert ttf, otf, bdf and pcf font files to Python source.
|
||||||
Sample usage:
|
Sample usage:
|
||||||
|
@ -609,114 +635,140 @@ Random access font files don't support an error character.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(__file__, description=DESC,
|
parser = argparse.ArgumentParser(
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
__file__, description=DESC, formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
parser.add_argument('infile', type=str, help='Input file path')
|
)
|
||||||
parser.add_argument('height', type=int, help='Font height in pixels')
|
parser.add_argument("infile", type=str, help="Input file path")
|
||||||
parser.add_argument('outfile', type=str,
|
parser.add_argument("height", type=int, help="Font height in pixels")
|
||||||
help='Path and name of output file')
|
parser.add_argument("outfile", type=str, help="Path and name of output file")
|
||||||
|
|
||||||
parser.add_argument('-x', '--xmap', action='store_true',
|
parser.add_argument("-x", "--xmap", action="store_true", help="Horizontal (x) mapping")
|
||||||
help='Horizontal (x) mapping')
|
parser.add_argument("-r", "--reverse", action="store_true", help="Bit reversal")
|
||||||
parser.add_argument('-r', '--reverse', action='store_true',
|
parser.add_argument("-f", "--fixed", action="store_true", help="Fixed width (monospaced) font")
|
||||||
help='Bit reversal')
|
parser.add_argument(
|
||||||
parser.add_argument('-f', '--fixed', action='store_true',
|
"-b", "--binary", action="store_true", help="Produce binary (random access) font file."
|
||||||
help='Fixed width (monospaced) font')
|
)
|
||||||
parser.add_argument('-b', '--binary', action='store_true',
|
parser.add_argument(
|
||||||
help='Produce binary (random access) font file.')
|
"-i",
|
||||||
parser.add_argument('-i', '--iterate', action='store_true',
|
"--iterate",
|
||||||
help='Include generator function to iterate over character set.')
|
action="store_true",
|
||||||
|
help="Include generator function to iterate over character set.",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument('-s', '--smallest',
|
parser.add_argument(
|
||||||
type = int,
|
"-s",
|
||||||
default = MINCHAR,
|
"--smallest",
|
||||||
help = 'Ordinal value of smallest character default %(default)i')
|
type=int,
|
||||||
|
default=MINCHAR,
|
||||||
|
help="Ordinal value of smallest character default %(default)i",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument('-l', '--largest',
|
parser.add_argument(
|
||||||
type = int,
|
"-l",
|
||||||
help = 'Ordinal value of largest character default %(default)i',
|
"--largest",
|
||||||
default = MAXCHAR)
|
type=int,
|
||||||
|
help="Ordinal value of largest character default %(default)i",
|
||||||
|
default=MAXCHAR,
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument('-e', '--errchar',
|
parser.add_argument(
|
||||||
type = int,
|
"-e",
|
||||||
help = 'Ordinal value of error character default %(default)i ("?")',
|
"--errchar",
|
||||||
default = 63)
|
type=int,
|
||||||
|
help='Ordinal value of error character default %(default)i ("?")',
|
||||||
|
default=63,
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument('-c', '--charset',
|
parser.add_argument(
|
||||||
type = str,
|
"-c",
|
||||||
help = 'Character set. e.g. 1234567890: to restrict for a clock display.',
|
"--charset",
|
||||||
default = '')
|
type=str,
|
||||||
|
help="Character set. e.g. 1234567890: to restrict for a clock display.",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument('-k', '--charset_file',
|
parser.add_argument(
|
||||||
type = str,
|
"-k",
|
||||||
help = 'File containing charset e.g. cyrillic_subset.',
|
"--charset_file",
|
||||||
default = '')
|
type=str,
|
||||||
|
help="File containing charset e.g. cyrillic_subset.",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if not args.outfile[0].isalpha():
|
if not args.outfile[0].isalpha():
|
||||||
quit('Font filenames must be valid Python variable names.')
|
quit("Font filenames must be valid Python variable names.")
|
||||||
|
|
||||||
if not os.path.isfile(args.infile):
|
if not os.path.isfile(args.infile):
|
||||||
quit("Font filename does not exist")
|
quit("Font filename does not exist")
|
||||||
|
|
||||||
if not os.path.splitext(args.infile)[1].upper() in ('.TTF', '.OTF', '.BDF', '.PCF'):
|
if not os.path.splitext(args.infile)[1].upper() in (".TTF", ".OTF", ".BDF", ".PCF"):
|
||||||
quit("Font file should be a ttf or otf file.")
|
quit("Font file should be a ttf or otf file.")
|
||||||
|
|
||||||
if args.binary:
|
if args.binary:
|
||||||
if os.path.splitext(args.outfile)[1].upper() == '.PY':
|
if os.path.splitext(args.outfile)[1].upper() == ".PY":
|
||||||
quit('Binary file must not have a .py extension.')
|
quit("Binary file must not have a .py extension.")
|
||||||
|
|
||||||
if args.smallest != 32 or args.largest != 126 or args.errchar != ord('?') or args.charset:
|
if args.smallest != 32 or args.largest != 126 or args.errchar != ord("?") or args.charset:
|
||||||
quit(BINARY)
|
quit(BINARY)
|
||||||
|
|
||||||
print('Writing binary font file.')
|
print("Writing binary font file.")
|
||||||
if not write_binary_font(args.outfile, args.infile, args.height,
|
if not write_binary_font(args.outfile, args.infile, args.height, args.xmap, args.reverse):
|
||||||
args.xmap, args.reverse):
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
if not os.path.splitext(args.outfile)[1].upper() == '.PY':
|
if not os.path.splitext(args.outfile)[1].upper() == ".PY":
|
||||||
quit('Output filename must have a .py extension.')
|
quit("Output filename must have a .py extension.")
|
||||||
|
|
||||||
if args.smallest < 0:
|
if args.smallest < 0:
|
||||||
quit('--smallest must be >= 0')
|
quit("--smallest must be >= 0")
|
||||||
|
|
||||||
if args.largest > 255:
|
if args.largest > 255:
|
||||||
quit('--largest must be < 256')
|
quit("--largest must be < 256")
|
||||||
elif args.largest > 127 and os.path.splitext(args.infile)[1].upper() == '.TTF':
|
elif args.largest > 127 and os.path.splitext(args.infile)[1].upper() == ".TTF":
|
||||||
print('WARNING: extended ASCII characters may not be correctly converted. See docs.')
|
print("WARNING: extended ASCII characters may not be correctly converted. See docs.")
|
||||||
|
|
||||||
if args.errchar < 0 or args.errchar > 255:
|
if args.errchar < 0 or args.errchar > 255:
|
||||||
quit('--errchar must be between 0 and 255')
|
quit("--errchar must be between 0 and 255")
|
||||||
if args.charset and (args.smallest != 32 or args.largest != 126):
|
if args.charset and (args.smallest != 32 or args.largest != 126):
|
||||||
print('WARNING: specified smallest and largest values ignored.')
|
print("WARNING: specified smallest and largest values ignored.")
|
||||||
|
|
||||||
if args.charset_file:
|
if args.charset_file:
|
||||||
try:
|
try:
|
||||||
with open(args.charset_file, 'r', encoding='utf-8') as f:
|
with open(args.charset_file, "r", encoding="utf-8") as f:
|
||||||
cset = f.read()
|
cset = f.read()
|
||||||
except OSError:
|
except OSError:
|
||||||
print("Can't open", args.charset_file, 'for reading.')
|
print("Can't open", args.charset_file, "for reading.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
cset = args.charset
|
cset = args.charset
|
||||||
# dedupe and remove default char. Allow chars in private use area.
|
# dedupe and remove default char. Allow chars in private use area.
|
||||||
# https://github.com/peterhinch/micropython-font-to-py/issues/22
|
# https://github.com/peterhinch/micropython-font-to-py/issues/22
|
||||||
cs = {c for c in cset if c.isprintable() or (0xE000 <= ord(c) <= 0xF8FF) } - {args.errchar}
|
cs = {c for c in cset if c.isprintable() or (0xE000 <= ord(c) <= 0xF8FF)} - {args.errchar}
|
||||||
cs = sorted(list(cs))
|
cs = sorted(list(cs))
|
||||||
cset = ''.join(cs) # Back to string
|
cset = "".join(cs) # Back to string
|
||||||
bitmapped = os.path.splitext(args.infile)[1].upper() in ('.BDF', '.PCF')
|
bitmapped = os.path.splitext(args.infile)[1].upper() in (".BDF", ".PCF")
|
||||||
if bitmapped:
|
if bitmapped:
|
||||||
if args.height != 0:
|
if args.height != 0:
|
||||||
print('Warning: height arg ignored for bitmapped fonts.')
|
print("Warning: height arg ignored for bitmapped fonts.")
|
||||||
chkface = freetype.Face(args.infile)
|
chkface = freetype.Face(args.infile)
|
||||||
args.height = chkface._get_available_sizes()[0].height
|
args.height = chkface._get_available_sizes()[0].height
|
||||||
print("Found font with size " + str(args.height))
|
print("Found font with size " + str(args.height))
|
||||||
|
|
||||||
print('Writing Python font file.')
|
print("Writing Python font file.")
|
||||||
if not write_font(args.outfile, args.infile, args.height, args.fixed,
|
if not write_font(
|
||||||
args.xmap, args.reverse, args.smallest, args.largest,
|
args.outfile,
|
||||||
args.errchar, cset, args.iterate, bitmapped):
|
args.infile,
|
||||||
|
args.height,
|
||||||
|
args.fixed,
|
||||||
|
args.xmap,
|
||||||
|
args.reverse,
|
||||||
|
args.smallest,
|
||||||
|
args.largest,
|
||||||
|
args.errchar,
|
||||||
|
cset,
|
||||||
|
args.iterate,
|
||||||
|
bitmapped,
|
||||||
|
):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(args.outfile, 'written successfully.')
|
print(args.outfile, "written successfully.")
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue