Generalize the process

Now,
 * fonts can have different size settings [not tested]
 * library name automatically determined from font if not specified
 * just dropping a font in fonts/ is enough to generate it
 * variants can be specified in config file (but not yet per font)
This commit is contained in:
Jeff Epler 2023-12-18 15:02:11 -06:00
parent 3ec51eb8df
commit 8ccddec74f
No known key found for this signature in database
GPG key ID: D5BF15AB975AB4DE
4 changed files with 70 additions and 47 deletions

View file

@ -8,14 +8,14 @@ used in CircuitPython by simply importing them.
It can be used with circup for easy installation:
```
circup bundle-add jepler/circuitpython-fonts
circup install font_mono_9_ascii
```sh
circup bundle-add jepler/circuitpython-fonts # You only need to do this once
circup install font_free_mono_9_ascii
```
The font can be used like so:
```
from font_mono_9_ascii import FONT as MONO_9
```python
from font_free_mono_9_ascii import FONT as MONO_9
from adafruit_display_text.bitmap_label import Label
# ...
@ -26,20 +26,17 @@ label = Label(font=MONO_9, text="Hi Mom!")
* Copy the font into `fonts/`
* Add a reuse-recognized `.license` file for it
* Add it to the `[FONTS]` section of `config.toml`
Presently all fonts are generated at the same range of sizes, `SIZES` in `config.toml`. This restriction could be lifted if it's for a good reason.
## Variants
Two variants of each font are generated: The ASCII variant with code points 32-126, and the full variant with all glyphs in the original font. The ASCII variant can be especially useful when flash space is at a premium.
Two variants of each font are generated: The "latin1" variant with code points 32-255 inclusive, and the full variant with all glyphs in the original font. The "latin1" variant can be especially useful when flash space is at a premium.
More variants could be added for a good reason; it should be configurable in config.toml and possibly be settable per font.
More variants could be added for a good reason; it is configurable in config.toml. It is not currently settable per font but if for a good reason it could be.
## Font Sizing
The font sizes in this bundle are in pixels.
Or, to be more technically accurte, they are rendered at a resolution of 1 pixel = 1/72 inch = 1 point.
If you follow the [directions on learn](https://learn.adafruit.com/custom-fonts-for-pyportal-circuitpython-display/use-otf2bdf) your font is rendered at a scale of 1 pixel = 1/100 inch = 0.72 points.
This means the resulting font pixel sizes are 33-40% larger for the Learn guide instructions vs the fonts in this bundle.
This means the resulting font pixel sizes for these libraries are around 38% smaller than if you follow the Learn guide instructions.
In any case, you will likely need to manually test font sizes until you find the best one for your application and display.

71
build.py Normal file → Executable file
View file

@ -1,12 +1,17 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-License-Identifier: MIT
import glob
import os
import re
import shutil
import textwrap
import tomllib
from collections import deque
from multiprocessing import Pool
from subprocess import check_call
import textwrap
from pathlib import Path
import tomllib
from subprocess import check_call
from convert import convert
@ -14,25 +19,25 @@ with open("config.toml", "rb") as f:
config = tomllib.load(f)
def build(src, dest, size, variant):
def build(src, dest, size, variant_name, variant_arg):
src = Path("fonts") / src
font_license = src.with_suffix(src.suffix + ".license")
uvariant = variant.replace("-", "_")
destdir = Path(
f"libraries/circuitpython-font-{dest.replace('_', '-')}-{size}{variant}"
f"libraries/font-{dest.replace('_', '-')}-{size}-{variant_name}".strip("-")
)
print(destdir)
package = f"font_{dest}_{size}{uvariant}"
package = f"font_{dest}_{size}_{variant_name}".replace("-", "_").strip("_")
packagedir = destdir / package
packagedir.mkdir(parents=True)
init_py = packagedir / "__init__.py"
init_py.write_text(
textwrap.dedent(
"""\
f"""\
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-License-Identifier: Unlicense
# CircuitPython font generated from {src} @{size}{" " if variant_name else ""}{variant_name}
from adafruit_bitmap_font import bitmap_font
FONT = bitmap_font.load_font(__file__.rsplit("/", 1)[0] + "/font.pcf")
"""
@ -45,11 +50,11 @@ def build(src, dest, size, variant):
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-License-Identifier: Unlicense
CircuitPython font generated from {src} @{size}{variant}
CircuitPython font generated from {src} @{size}{" " if variant_name else ""}{variant_name}
"""
)
)
convert(src, packagedir / "font.pcf", size, "32_126" if variant else None)
convert(src, packagedir / "font.pcf", size, variant_arg)
dest_font_license = packagedir / "font.pcf.license"
dest_font_license.write_text(font_license.read_text())
@ -83,19 +88,43 @@ def build(src, dest, size, variant):
)
if __name__ == "__main__":
targets = [
(src, dest, size, variant)
for dest, src in config["FONTS"].items()
for size in config["SIZES"]
for variant in ("", "-ascii")
def filename_to_package_name(filename):
s = re.sub("[A-Z]+", lambda m: "_" + m.group(0).lower(), filename)
s = re.sub("[^a-z0-9_]+", "_", s)
s = s.removeprefix("_")
return s
def targets():
defaults = config["defaults"]
variants = config["variants"]
font_files = [
f for f in os.listdir("fonts") if f.lower().endswith((".ttf", ".otf"))
]
for filename in font_files:
basename = filename.rsplit(".", 1)[0]
font_config = dict(defaults)
font_config.update(config.get("font", {}).get(basename, {}))
dest = filename_to_package_name(font_config.get("name", basename))
print(config, dest, basename)
for size in sorted(font_config["sizes"], reverse=True):
yield (filename, dest, size, "", None)
for variant_name, variant_arg in variants.items():
yield (filename, dest, size, variant_name, variant_arg)
if __name__ == "__main__":
if os.access("libraries", os.F_OK):
shutil.rmtree("libraries")
with Pool() as pool:
# This construct causes all the individual calls to finish, discarding the results
deque(pool.starmap(build, targets), 0)
count = sum(1 for _ in pool.starmap(build, targets()))
check_call(
"circuitpython-build-bundles --output_directory dist --filename_prefix circuitpython-fonts --library_location libraries/ --library_depth 1",
shell=True,
)
if not "BUILD_ONLY" in os.environ:
check_call(
"circuitpython-build-bundles --output_directory dist --filename_prefix circuitpython-fonts --library_location libraries/ --library_depth 1",
shell=True,
)
print(f"Generated {count} font libraries")

View file

@ -1,18 +1,13 @@
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-License-Identifier: MIT
SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72]
[defaults]
sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 30, 36, 42, 48, 54, 60, 72, 84, 96, 108, 120, 144]
[FONTS]
mono = "FreeMono.ttf"
mono_bold = "FreeMonoBold.ttf"
mono_oblique = "FreeMonoOblique.ttf"
mono_bold_oblique = "FreeMonoBoldOblique.ttf"
serif = "FreeSerif.ttf"
serif_bold = "FreeSerifBold.ttf"
serif_oblique = "FreeSerifItalic.ttf"
serif_bold_oblique = "FreeSerifBoldItalic.ttf"
sans = "FreeSans.ttf"
sans_bold = "FreeSansBold.ttf"
sans_oblique = "FreeSansOblique.ttf"
sans_bold_oblique = "FreeSansBoldOblique.ttf"
# Note that subset ranges are *inclusive* of the upper bound in otf2bdf
[variants]
#ascii = "32_126"
latin1 = "32_255"
# [FONT.FontFileNameWithoutExtension]
# name = "desired_package_base_name"

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: MIT
from pathlib import Path
from subprocess import run, check_call, DEVNULL, PIPE
from subprocess import DEVNULL, PIPE, check_call, run
import click
@ -16,11 +16,13 @@ def convert(src: Path, dest: Path, size: int, subset=None) -> None:
otf_process = run(otf_command, stdin=DEVNULL, stdout=PIPE)
bdf_content = otf_process.stdout
if otf_process.returncode != 8:
print(f"Note: {otf_command=}")
print(f"Note: End of bdf_content: {bdf_content[-32:]!r}")
raise RuntimeError(
f"otf2bdf failed: exit status was {otf_process.returncode}, not 8"
)
if not bdf_content.endswith(b"ENDFONT\n"):
print(f"Note: {otf_command=}")
print(f"Note: End of bdf_content: {bdf_content[-32:]!r}")
raise RuntimeError(f"otf2bdf failed: output did not end with ENDFONT")
bdf_process = run(["bdftopcf", "-o", f"{dest}", "/dev/stdin"], input=bdf_content)