November — a single stroke vector font editor

Heikki Lotvonen | written on 4.2.2026

I spent November on building a single-stroke vector font editor based on Hershey fonts with g-conic curves, and a live text layout panel with semi-justification and optical kerning.

An experimental single stroke vector font editor

As great as the Hershey fonts are, I wanted to make my own single stroke vector fonts, so I spent November making an editor for that. It's not "officially" out yet, but you can already use it at https://hlnet.neocities.org/hershey/

The editor is split into two panels: in the right panel you design the font, and in the left panel you can compose paragraphs. The text changes in real time as the font is edited, which actually makes for a really great design feedback loop because you can immediately see the how changes in design works in the context of actual text, and not just as separate entities.

The font editor

The font editor side is heavily based upon the simplicity of the Hershey font format. It's based on a coarse grid (each point is snapped to the grid), and has the following simple commands: moveTo and lineTo. But, I also added a new command: conicTo, which is a g-conic curve.

I first read about g-conics from the 1994 book Font Technology, Methods and Tools by Peter Karow. According to Karow, g-conic curves were used for rendering font outlines with the long-forgotten F3 font format in the late 80's and early 90's, but fell out of use as cubic Bézier curves and TrueType fonts became standard.

G-conics refer to a mathematical funtion to draw conic sections (like parabolas, ellipses, and hyperbolas) using a set of three points and a "sharpness" value. Nowadays g-conics are mostly known as "rational quadratic Bézier curves", which sounds less exciting but is maybe more straightforward to understand. Basically, it's the same as a normal quadratic Bézier, or constraining a cubic Bézier curve to the "magic triangle", as described by a tutorial for Glyphs for drawing good paths [1], but with an additional parameter that determines how pointy the curve is. Here's an interactive comparison that should make it clear. Crank the "sharpness" slider above ~0.73, and you get a sharper curve than what is possible with conventional Béziers:

Because "good type design" already constrains Béziers to a magic triangle, but overall homogenizes the way fonts look [2], the sharpness parameter adds a layer that can break the algorithmic logic of how we design fonts.

In addition to having the sharpness slider, the curves also automatically follow cardinal directions, which removes the need to manually adjust the handle directions. And, with shortcut "x", the direction of the curve can be toggled between clockwise and counter-clockwise directions.

Overall, it's really fast to design lettershapes with it. Here's an early test I made in a few hours with no planning or extra thought (in other words, I can use it quickly & effectively. Making an equivalent font in Glyphs, for example, would take infinitely more time):

The paragraph composer

The other part of the editor is the paragraph composer. It's fairly simple: you can add text, give it a position (x & y), size (width & height), font size, line height and tracking. But the way it composes the paragraph is also quite unconventional. Words are spaced with collision based optical kerning, and the paragraphs are composed with an obscure semi-justification method.

Collision based optical kerning

The simplest way to handle composing words is to place the letters next to each other based on their bounding boxes (or advance widths) and add a uniform amount of tracking between each letter. This is of course not satisfactory if we want to have decent looking typography, because of inconsistent spacing due to missing kerning adjustments. The coventional way to handle kerning is to assign each letter some default left and right bearings, and then have a additional table for adjusting those numbers for each letter pair. Doing this manually is a huge amount of work. I didn't want that, so I made a collision based kerning system which is more automatic, but still generates decent results. With tracking set to 0, it looks like this:

Instead of using some set number for kerning values, it just uses the form of the letter to pack them as tightly as possible. Then tracking is added on top. It has a big drawback though: if you have a letter like "C" and then type a dash "—", then the dash will go fully inside the shape of the letter C. So, I added a "minWidth" parameter to each letter, which acts as a barrier for the collision kerning. For example, if the letter C is 20 cells wide, I can set the minWidth to 19, and it stops the dash from going all the way in by 1 cell width. It works quite well and is only one additional number to fiddle with.

The last remaining drawback is that straight horizontal shapes in letter pairs like "ll" are too close to each other. I'm still trying to figure out the best way to automatically give them a bit more space, but I haven't figured out a simple yet foolproof way of doing that yet. Behdad Esfahbod on typo.social mentioned a method he uses for halfkern [3] as follows:

The way the tool works is that for every pair of letters that are considered, it will blur their renderings and space the two such that the blurred images overlap a certain amount. This certain amount is found by first calibrating using the "ll", "nn", and "oo" pairs.

It seems to produce really good results, completely automatically, so I might give it a shot at some point. Jackson (@Okay) also reminded me of bubblekern [4], but that also requires drawing the collision shapes by hand, so not quite what I want.

Semi-justified text

The paragraphs then are composed with a semi-justification algorithm I came up with. I wrote about it in detail on my blog [5], so I'm not going to repeat everything here. In a nutshell it's like a basic greedy justification, but the wordspaces are bounded to some min and max width:

By bounding word-spaces, we don't need to decide which lines should be justified, because lines automatically self-select: if a line can reach the target width within acceptable spacing bounds, it gets justified, and if not, it stays ragged. This is achieved by restricting how much word-spaces are allowed to expand during space distribution. In addition, we can also allow the word-spaces to shrink for more flexibility.

And here's a demo that demonstrates how it works. Adjust the line width to see how the paragraph goes fully ragged for narrow paragraphs, and more justified for wider paragraphs.


I really like this text alignment method. It hits the sweet spot of simple-to-implement, simple-to-compute and good looking with a lot of utility. I just wish it was available anywhere! I think it would be the perfect justification method for the web (I made a comparison of text aligment methods on codepen [6]).

I'm very excited about the direction this tool is going. I can make a font and layout some paragraphs with it, export it as SVG and print it, all using my own editor. And the results are completely legible, but also completely unique and kind of strange. Here's some test prints:

Because it's all single stoke vectors, I hope to do some plotter prints at the Aalto workshops in the spring with this. Let's see!

    Links

  1. https://glyphsapp.com/learn/drawing-good-paths
  2. https://beyondbezier.ch/
  3. https://github.com/behdad/halfkern
  4. https://tosche.net/non-fonts/bubblekern
  5. https://blog.glyphdrawing.club/semi-justified-text/
  6. https://codepen.io/heikkilotvonen/pen/EaVeZBP
show page source