A FontForge plug-in to harmonize or tunnify (balance) or add inflection points to the selected part

While working on my type family Elemaints, I have written several scripts that enhance the capabilities of FontForge. On https://github.com/linusromer/harmonize-tunnify-inflection I have published some of these tools, as you might find them useful too, if you are using FontForge.



  • ivan louetteivan louette Posts: 327
    edited November 2019
    @Linus Romer WOW ! That's very good news. I have spent so much time to add inflection points to my FF drawings. Thanks you so much !
  • Grzegorz Luk (gluk)Grzegorz Luk (gluk) Posts: 134
    edited November 2019
    @Linus Romer  very useful tool, first of all inflection points!
    btw. if I can suggest a little trick in Fontforge-Python that I discovered too late, because it is useful. You can avoid the definitions of
    harmonize_contours, harmonize_handles_contours, tunnify_contours, inflection_contours, harmonize_glyphs, harmonize_handles_glyphs, tunnify_glyphs, inflection_glyphs
    and use "junk" :smile: variable. Instead of:
    <code>&nbsp;&nbsp; fontforge.registerMenuItem(harmonize_glyphs,are_glyphs_selected,None,"Font",None,"Harmonize");
    ...<br>def modify_glyphs(junk,font,action):<br>...<br>def harmonize_glyphs(junk,font):<br>&nbsp;&nbsp;&nbsp; modify_glyphs(junk,font,"harmonize")<br>...<br>if fontforge.hasUserInterface():<br>
    You can directly
    <div>...<br></div>def modify_glyphs(action,font):<br>...<br>if fontforge.hasUserInterface():<br>&nbsp;&nbsp; fontforge.registerMenuItem(modify_glyphs,are_glyphs_selected,"harmonize","Font",None,"Harmonize");<br>...
  • [Deleted User][Deleted User] Posts: 0
    edited November 2019
    The user and all related content has been deleted.
  • ivan louetteivan louette Posts: 327
    edited November 2019
    @Linus Romer I installed it as explained and it appears in the Tools Menu. My FF version is the last PPA for Ubuntu 18.04 . I get this error :

  • it’s exciting to see another person coding plugins for FontForge! Honestly, I thought I was the only one for a while. None of mine are publicly available yet, though, so you’ve 1-upped me there ;-)
  • Linus RomerLinus Romer Posts: 171
    edited November 2019
    I have updated the plugin on https://github.com/linusromer/harmonize-tunnify-inflection. The plugin no longer relies on fontforge.point() and therefore should work now with older versions of FontForge (e.g. from 2017) too.
    @Grzegorz Luk (gluk) Indeed, this is a cool trick, it makes the code much more compact. I have implemented the trick in the new version. Thank you for the hint!
    If needed I could have placed those additional points of inflection without the need of this script.

    In automated processes one will still need a script in order to add points of inflection.

    That point on the right side of the shape really should be at the extreme and not where it is at the moment of being "tunnified". Use your eyes!

    I have never claimed any of these contours to be perfect. The non-horizontal-or-vertical node on the dot contour should demonstrate that the tools will work for such nodes, too.

    @ivan louette I think I have figured out what probably happend: You inserted an inflection point at the end of a contour. This led to a problem because the end node is identical with the start node. I have fixed this bug now. I hope it will work now for you.

    @AbrahamLee Unfortunately, there are hardly any plugins of FontForge available. I have only found the plugin in the FontForge documentation and those of David Crossland (which were worth a lot for me because the documentation is so sparse). I am looking forward to the publication of your scripts.

  • @Linus Romer Thanks for the update. However now Tool menu remain greyed and I get this message :

  • Linus RomerLinus Romer Posts: 171
    edited November 2019
    @ivan louette According to the error message I assume you have downloaded the html-file instead of the raw file. The direct link to the file (without html view) is:
  • @Linus Romer OOOOPS ! I was so silly ! :smiley: Now it works like a charm on my current 2017 FF ! Thanks for your patience :wink:
  • Stefan PeevStefan Peev Posts: 92
    edited November 2019
    @Linus Romer I downloaded the file from the link above, but...

    I use Windows 8.1 and the latest an official version of FontForge.
  • @AbrahamLee Unfortunately, there are hardly any plugins of FontForge available. I have only found the plugin in the FontForge documentation and those of David Crossland (which were worth a lot for me because the documentation is so sparse).
    Absolutely true. This was a really frustrating realization I came to as well when there are so many awesome ones for Glyphs, Robofont, Fontlab, etc. Once I was able to really figure out how to do it (and it was very much NOT easy or obvious), I have had way too much fun creating useful plugins for all levels of font production/deveopment (i.e., font level, glyph level, contour level, etc.).
    I am looking forward to the publication of your scripts.
    Me too ;-) I don't have a specific plan for this, but I really do hope to some day.
  • Linus RomerLinus Romer Posts: 171
    edited November 2019
    @Stefan Peev Thanks for the note. This bug was due to the windows path "C:\Users..." in the docstring. Under Linux there were no complaints but under Windows "\U" obviously must not be contained in the docstring. I have fixed the bug and tested the new version with Windows (and it worked):

  • @Linus Romer Thank you. Now it works for me too.
  • I was asked if my plugin could be part of the (future) contrib scripts of FontForge. Therefore, I had to change a few things and I will release the updated plugin under a new name (because the compatibility to FontForge2017 will be broken). However, I am not sure what name will fit the plugin. At the moment, I have chosen "Curvatura" because my tools are related to curvature and "Curvatura" is understandable in many languages and "Curvatura.py" is not as overused as "Curvature.py":
    If you have a better name for the plugin, I would be glad to read it here.
  • This is really interesting and useful. Any interest in doing the same for FL5?
  • @Jeremy Dooley Unfortunately, I do not own a copy of Fontlab 5, which is a bad precondition for testing and supporting. However, if someone wants to adapt the script: Feel free to use my script (under the conditions of the GPL). For all the actions in my plugin there are low level methods that can be used 1:1 for FontLab.
  • Thanks for the info, Linus and for your contribution to the community.
  • Anyone want to make a quick $200 on my dime and contribute to the community?

  • @Jeremy Dooley, sorry I don’t have an account at UpWork, but I’m curious to know what this is about. What’s the task?
  • Hi @AbrahamLee, the task is to adapt the "tunnify" fontforge plugin to FL5.
  • Gotcha. Thanks for explaining.
  • Looks like I posted a private URL. Here's the public listing: https://www.upwork.com/jobs/~012c24b2d860806f01
  • The first and original tunnify script was written for FontLab 5, by Andres Torresi. I wrote about this more here:


  • @Andres Torresi
    Is this script still around? I've found your github repository, but I can't identify it. Much appreciated.
  • Curvatura is the successor of harmonize-tunnify-inflection . Curvatura now also supports G3-continuity (see this thread).
  • Linus,that's fantastic
  • I created both balance and harmonize macros for Windows FontLab 5.2 a few months ago and just now published them to github. The functions for the macros are contained in an extension module which I wrote in Cython C++, which makes the macros perform much faster than if they were run as plain Python.

    They were created out of the need to harmonize individual curve segments without changing anything not selected, something RMX has difficulties with.

    The balance macro works the same way as the harmonize macro. This is a feature sadly missing from RMX.

    So far this particular project is still unfinished, but outside of a some edge cases, it mostly works as expected.

  • Linus RomerLinus Romer Posts: 171
    edited February 2020
    @Jameson R Spires Laudable that you share your macros! I hope you do not mind, if I give you a hint: The harmonize algorithm of Simon Cozens works in most of the cases but depends on finding an intersection point, which may not exist. Therefore, I use a different algorithm (with the same results in generic cases) in the latest version of curvatura: (the code environment does not work on typedrawers.com, sorry)

    # Returns the signed distance of the point p from the line
    # starting in q and going to r. The value is positive, iff
    # p is right from the line.
    def side(px,py,qx,qy,rx,ry):
        a, b = rx-qx, ry-qy
        return ((py-qy)*a-(px-qx)*b)/(a**2+b**2)**.5

    # Given two adjacent cubic bezier curves (a,b), (c,d), (e,f), (g,h)
    # and (g,h), (i,j), (k,l), (m,n) that are smooth at (g,h)
    # this method calculates a new point (g,h) such that
    # the curves are G2-continuous in (g,h).
    def harmonize_cubic(a,b,c,d,e,f,g,h,i,j,k,l,m,n):
        if e==i and f==j:
            return g, h # no changes
        d2 = abs(side(c,d,e,f,i,j))
        l2 = abs(side(k,l,e,f,i,j))
        if d2 == l2: # then (g,h) is in mid between handles
            return .5*(e+i), .5*(f+j)
        t = (d2-(d2*l2)**.5)/(d2-l2)
        return (1-t)*e+t*i, (1-t)*f+t*j
    This algorithm may be faster as well (but please check yourself). The mathematics behind are explained in https://github.com/linusromer/curvatura/blob/master/curvatura-doc.pdf. The idea behind the function `side()` is: length of the crossproduct over the length of the one vector equals the height of the spanned parallelogram.

  • Jameson R SpiresJameson R Spires Posts: 20
    edited February 2020
    I adapted your method above to the harmonize_curves() function in the extension module. It should be quicker due to considerably fewer calculations, but I did not measure it. Thanks for your help.
    cdef double harmonize_distance(cPoint &p0, cPoint &p1, cPoint &p2) nogil:
    double i = p2.x - p1.x
    double j = p2.y - p1.y
    return fabs((((p0.y - p1.y) * i) - ((p0.x - p1.x) * j)) / sqrt(pow(i, 2) + pow(j, 2)))

    cdef cPoint harmonize_cubic(cCurve &curve, cCurve &next_curve) nogil:

    if curve.p2.x == next_curve.p1.x and curve.p2.y == next_curve.p1.y:
    return curve.p3

    double d0 = harmonize_distance(curve.p1, curve.p2, next_curve.p1)
    double d1 = harmonize_distance(next_curve.p2, curve.p2, next_curve.p1)

    if d0 == d1:
    return Point(.5 * (curve.p2.x + next_curve.p1.x), .5 * (curve.p2.y + next_curve.p1.y), 0)

    double t = (d0 - sqrt(d0 * d1)) / (d0 - d1)
    double t_1 = 1 - t

    return Point(t_1 * curve.p2.x + t * next_curve.p1.x, t_1 * curve.p2.y + t * next_curve.p1.y, 0)
  • My pleasure! I see, that you have optimized abs(), which is absolutely correct in this case. I use side() for other functions as well and therefore side() does not include abs(). But after looking at your code, I think I should split side() in two functions harmonize_distance() and sideness() just as you.
Sign In or Register to comment.