Having fun kerning using Org Mode and FontForge
| emacs, orgIt turns out that working with font bearings and kerning tables using Org Mode makes lots of things so much easier.
While trying to figure out kerning, I came across this issue that described how you sometimes need a character-pair kern table instead of just class-based kerning. Since I had figured out character-based kerning before I figured out class-based kerning, it was easy to restore my Python code that takes the same kerning matrix and generates character pairs. Here's what that code looks like.
def kern_by_char(font, kerning_matrix):
# Add kerning by character as backup
font.addLookupSubtable("kern", "kern-2")
offsets = np.asarray(kerning_matrix)
classes_right = [None if (x == "" or x == "None") else x.split(",") for x in offsets[0,1:]]
classes_left = [None if (x == "" or x == "None") else x.split(',') for x in offsets[1:,0]]
for r, row in enumerate(classes_left):
if row is None: continue
for first_letter in row:
g = font.createMappedChar(first_letter)
for c, column in enumerate(classes_right):
if column is None: continue
for second_letter in column:
if kerning_matrix[r + 1][c + 1]:
g.addPosSub("kern-2", second_letter, 0, 0, kerning_matrix[r + 1][c + 1], 0, 0, 0, 0, 0)
return font
I wanted to be able to easily compare different versions of my font: my original glyphs versus my tweaked glyphs, simple spacing versus kerned. This was a hassle with FontForge, since I had to open different font files in different Metrics windows. If I execute a little bit of source code in my Org Mode, though, I can use my test web page to view all the different versions. By arranging my Emacs windows a certain way and adding :eval no
to the Org Babel blocks I'm not currently using, I can easily change the relevant table entries and evaluate the whole buffer to regenerate the font versions, including exports to OTF and WOFF. Here's the code for that:
<<params>>
<<def_import_glyphs>>
<<def_set_bearings>>
<<def_kern_classes>>
<<def_kern_by_char>>
font = fontforge.font()
font = import_glyphs(font, params)
font = set_bearings(font, bearings)
save_font(font, {**params, "new_otf": "sachacHandRaw.otf"})
font = kern_classes(font, kerning_matrix)
font = kern_by_char(font, kerning_matrix)
save_font(font, {**params, "new_otf": "sachacHandRawKerned.otf"})
font = load_font('SachaHandEdited.sfd')
font = set_bearings(font, bearings)
font.removeLookup('kern')
save_font(font, {**params, "new_otf": "sachacHandEdited.otf"})
font = kern_classes(font, kerning_matrix)
font = kern_by_char(font, kerning_matrix)
save_font(font, {**params, "new_otf": "sachacHand.otf"})
I also like the way it's pretty easy to update multiple kerning values without clicking around. I sometimes use FontForge to get the number to set it to and then copy that into my table, but I also sometimes just tweak the number in Org Mode directly.
To see the results, I can generate a test HTML that shows me text with different versions of my font. I can also look at lots of kerning pairs at the same time. Here are the components of that test page:
def test_css(fonts):
doc, tag, text, line = Doc().ttl()
with tag('style'):
for f in fonts:
text("@font-face { font-family: '%s'; src: url('%s'); }" % (f[0], f[1]))
text(".%s { font-family: '%s'; }" % (f[0], f[0]))
text("table { font-size: inherit; font-weight: inherit }")
text("td { text-align: left }")
text(".blog-heading { font-weight: bold; font-size: 32px }")
text(".default { color: gray }")
text("body { font-family: woff, Arial, sans-serif; font-size: 32px; padding: 10px }")
return doc.getvalue()
def test_html(strings):
doc, tag, text, line = Doc().ttl()
with doc.tag('table', style='border-bottom: 1px solid gray; width: 100%; border-collapse: collapse'):
for s in strings:
for i, f in enumerate(fonts):
style = 'border-top: 1px solid gray' if (i == 0) else ""
with tag('tr', klass=f[0], style=style):
line('td', f[0])
line('td', s)
return doc.getvalue()
def test_kerning_matrix(kerning_matrix):
doc, tag, text, line = Doc().ttl()
with tag('table'):
for r, row in enumerate(classes_left):
if row is None: continue
for first_letter in row:
with tag('tr'):
line('td', first_letter)
for c, column in enumerate(classes_right):
if column is None: continue
for second_letter in column:
klass = "kerned" if kerning_matrix[r + 1][c + 1] else "default"
line('td', aglfn.to_glyph(first_letter) + aglfn.to_glyph(second_letter), klass=klass)
return doc.getvalue()
This code actually generates the test file:
from yattag import Doc
import numpy as np
import aglfn
<<def_test_html>>
doc, tag, text, line = Doc().ttl()
fonts = [['raw', 'sachacHandRaw.otf'],
['raw-kerned', 'sachacHandRawKerned.otf'],
['edited', 'sachacHandEdited.otf'],
['woff', 'sachacHand.woff']]
strings = ["Python+FontForge+Org: I made a font based on my handwriting!",
"Monthly review: May 2020",
"Emacs News 2020-06-01"]
offsets = np.asarray(kerning_matrix)
classes_left = [None if (x == "" or x == "None") else x.split(',') for x in offsets[1:,0]]
classes_right = [None if (x == "" or x == "None") else x.split(",") for x in offsets[0,1:]]
with tag('html'):
with tag('head'):
doc.asis(test_css(fonts))
with tag('body'):
line('h1', 'Test headings')
with tag('div', klass="blog-heading"):
doc.asis(test_html(strings))
line('h1', 'Kerning matrix')
doc.asis(test_kerning_matrix(kerning_matrix))
return doc.getvalue()
And here's what that test.html looks like:
Not bad… Now Emacs is my font editor! The code is at https://github.com/sachac/sachac-hand .