fontconv.py: generate WOFF for newstroke

This commit is contained in:
David Schneider
2026-01-14 20:07:13 +00:00
committed by Seth Hillbrand
parent 25430cfbb8
commit 52a5fc9904
+94 -6
View File
@@ -10,6 +10,17 @@ from typing import Any, NamedTuple
import re
import sys
try:
import fontforge
except ImportError:
print("** fontforge is not installed. woff output will fail")
print("** see https://fontforge.org for installation instructions")
class fontforge:
class glyphPen:
pass
class font:
pass
global_duplicate_point_removal = True
input_fonts = ['symbol', 'font', 'hiragana',
@@ -30,6 +41,10 @@ output_cpp = '../../common/newstroke_font.cpp'
FONT_BASE = 9
FONT_SCALE = 50
FONT_WOFF_THICKNESS = 2.53
FONT_WOFF_XOFFS = 0
FONT_WOFF_YOFFS = FONT_WOFF_THICKNESS/2
FONT_WOFF_SCALE = 29.7
FONT_C_BIAS = ord("R")
FONT_NEWSTROKE = " R"
C_ESC_TRANS = str.maketrans({'"': '\\"', '\\': '\\\\'})
@@ -215,6 +230,24 @@ class Glyph(NamedTuple):
else:
return remove_duplicate_points(data)
def render(self, tr: Transform, ofs: Point, pen: fontforge.glyphPen):
woff_tr = Transform(SX=FONT_WOFF_SCALE, SY=-FONT_WOFF_SCALE, OY=0)
woff_ofs = Point(
x=FONT_WOFF_XOFFS*FONT_WOFF_SCALE,
y=FONT_WOFF_YOFFS*FONT_WOFF_SCALE
)
for stroke in self.strokes:
if not stroke:
continue
transformed = (
p.transformed(tr, ofs).transformed(woff_tr, woff_ofs)
for p in stroke
)
pen.moveTo(next(transformed))
for t in transformed:
pen.lineTo(t)
pen.endPath()
@property
def width(self):
return self.metrics.r - self.metrics.l
@@ -334,6 +367,10 @@ class SubGlyph(NamedTuple):
def as_data(self):
return self.glyph.as_data(self.transform, self.offset)
def render(self, pen: fontforge.glyphPen, ofs: Point):
offset = self.offset.transformed(Transform(), ofs)
self.glyph.render(self.transform, offset, pen)
class Composition(list[SubGlyph]):
__slots__ = ["metrics"]
@@ -356,6 +393,19 @@ class Composition(list[SubGlyph]):
else:
return mdata + gdata
def create_char(self, cp: int, font: fontforge.font):
name = ' '.join(c.tname for c in self if c.tname)
if name == "0": # 0 has special meaning in fonts
name = "ZERO"
glyph = font.createChar(cp, name)
offset = Point(x=-self.metrics.l, y=0)
pen = glyph.glyphPen()
for g in self:
g.render(pen, offset)
pen = None
glyph.stroke("circular", font.strokewidth)
glyph.width = round((self.metrics.r - self.metrics.l) * FONT_WOFF_SCALE)
class Compositions:
"""Compositions of glyphs for every code point described in the character list file"""
@@ -520,7 +570,39 @@ class CFontWriter:
print(f' "{data}", /* U+{codepoint:X} */', file=sout)
class WoffFontWriter:
"""Processes the compositions to fill in a font's glyphs."""
def __init__(self, comps: Compositions):
self.comps = comps
def generate_glyphs(self, start_cp: int, end_cp: int, font: fontforge.font):
fname = self.comps.font_name.replace("_font", "")
font.familyname = fname
font.fontname = f"kicad-{fname}"
font.fullname = f"KiCad {fname.capitalize()}"
font.copyright = 'CC0-1.0'
font.strokedfont = True
font.strokewidth = FONT_WOFF_THICKNESS * FONT_WOFF_SCALE
for cp, composition in enumerate(self.comps.codepoints[start_cp:end_cp+1], start_cp):
# Don't output tofu/blanks into the file
if chr(cp).isspace() or any(c.glyph.strokes and c.glyph != self.comps.default_subglyph.glyph for c in composition):
composition.create_char(cp, font)
if __name__ == "__main__":
cp_min = 0x20
cp_max = 0xFFFF
woff = None
for arg in sys.argv[1:]:
param = arg.partition("=")
if param[0] == "--woff":
woff = param[2]
elif param[0] == "--cp_min":
cp_min = int(param[2], 0)
elif param[0] == "--cp_max":
cp_max = int(param[2], 0)
print('** Reading glyphs from fonts:')
all_glyphs: dict[str, Glyph] = {}
for basename in input_fonts:
@@ -534,11 +616,17 @@ if __name__ == "__main__":
with open(input_charlist, encoding='utf-8') as src:
compositions = Compositions.from_read(src, all_glyphs)
print(f"** Writing {output_cpp}")
font = CFontWriter(compositions)
with open(output_cpp, "w", encoding='utf-8') as dst:
dst.write(header)
font.generate_c_output(0x20, dst)
font.print_stats(dst)
if woff:
print(f"** Writing {woff}")
font = fontforge.font()
WoffFontWriter(compositions).generate_glyphs(cp_min, cp_max, font)
font.generate(woff, flags=("no-FFTM-table",))
else:
print(f"** Writing {output_cpp}")
font = CFontWriter(compositions)
with open(output_cpp, "w", encoding='utf-8') as dst:
dst.write(header)
font.generate_c_output(cp_min, dst)
font.print_stats(dst)
print("** Done")