Category Archives: geek

2020-07-06 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, planet.emacslife.com, YouTube, the Emacs NEWS file and emacs-devel.

2020-06-29 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, planet.emacslife.com, YouTube, the Emacs NEWS file and emacs-devel.

2020-06-22 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, planet.emacslife.com, YouTube, the Emacs NEWS file and emacs-devel.

2020-06-15 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, planet.emacslife.com, YouTube, the Emacs NEWS file and emacs-devel.

2020-06-08 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, planet.emacslife.com, YouTube, the Emacs NEWS file and emacs-devel.

Having fun kerning using Org Mode and FontForge

It turns out that working with font bearings and kerning tables using Org Mode makes lots of things so much easier.

Bearings in the top left, kerning matrix in the top right

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:

Testing the font with a few blog headings
Kerning pairs: black for specified pairs, gray for defaults

Not bad… Now Emacs is my font editor! The code is at https://github.com/sachac/sachac-hand .