Python library for direct Bezier manipulation
Simon Cozens
Posts: 741
I’ve been asked to write a tool which performs various transformations to outlines in an OTF, and am now trying to get my head around the various parts of the Python font manipulation ecosystem.
Previously I have written plugins for editors which all come with their own libraries for manipulating Beziers, but this one will be a command line tool using fonttools.
What I’m looking for is an independent library which takes an outline in some format and has functions to add extrema and inflections, rotate, translate, offset, simplify, delete and keep shape, split curve at a point, harmonize, etc. - all the usual operations you want when manipulating curves.
Does such a library exist already? It seems like there should be but I have not found anything in fonttools, robofog, etc. - maybe I’m missing the obvious or maybe the documentation isn’t great.
If not, I think it would be useful to have and I wonder if there would be some value in one which also converted between different Bezier representations (array, NSBezierPath, Glyphs, etc.). I’m happy to write one since I will need it for this project.
Previously I have written plugins for editors which all come with their own libraries for manipulating Beziers, but this one will be a command line tool using fonttools.
What I’m looking for is an independent library which takes an outline in some format and has functions to add extrema and inflections, rotate, translate, offset, simplify, delete and keep shape, split curve at a point, harmonize, etc. - all the usual operations you want when manipulating curves.
Does such a library exist already? It seems like there should be but I have not found anything in fonttools, robofog, etc. - maybe I’m missing the obvious or maybe the documentation isn’t great.
If not, I think it would be useful to have and I wonder if there would be some value in one which also converted between different Bezier representations (array, NSBezierPath, Glyphs, etc.). I’m happy to write one since I will need it for this project.
0
Comments
-
Some of the operations you mentioned are simple and require one some lines of code (transformations, adding extrem points...). Others are way more complicated (simplify curve, merge curves).
There is code available for some...0 -
I think most of these operations are simple; the hard part is dealing with the various different representations of a curve that different tools use. But as you know the point of having a library is so we don’t all have to be cutting and pasting these different operations again and again.
Basically I am looking for the Python equivalent of https://pomax.github.io/bezierjs/0 -
Converting between the different representations is not that difficult. Write a pen that outputs your internal format. There are pens for NSBezierPath and ttx.0
-
When you say “pens”, do you mean that fonttools is the library I should be using for manipulating Beziers? (Or robofab? Or either one of those?)0
-
Pens is a commonly used pattern for manipulation of outlines, used both in fonttools and robofab0
-
@Simon Cozens, regretfully there is none. I have been looking for one from quite some time, but with no success.. I even tough reimplementig pomax.bezierjs with their permission, as I saw others have done so in different languages, but didn't had the time... If you find one, or decide to write one and need a helping hand, seek me out - I am very interested, just do not want to do it all by myself (I presume you also )!0
-
I have a module that models a "SuperCubic" which consists of one or more consecutive cubic segments defined by 4 point tuples. The Super Cubic can calculate its own extremum points and inflection points, rasterize into line segments, split at a given t or point (on or close to the curve).
More functions could be added. Especially simplification and harmonizing could be interesting. Maybe the "SuperCubic" is a good starting point because it already can work across multiple cubic segments.
I could upload it to GitHub if there's any interest.1 -
FontTools pens can do some of these operations, see TransformPen for example. There are some more pens in https://github.com/robofab-developers/fontPens/, and there is also https://github.com/googlei18n/cu2qu/ for converting cubic to quadratic (and can convert multiple masters in compatible way).0
-
There is also fontTools.misc.bezierTools and fontTools.misc.symfont and you can probably find more stuff in FontTools.0
-
Jens Kutilek said:
I could upload it to GitHub if there's any interest.0 -
@Jens Kutilek I would love to take a look at the Superbezier module also! Thank you in advance!0
-
I have developed a suite of functionality for manipulating/modifying Bézier curves and glyph contours that I would consider contributing. It’s been all for my own use so far since I use FontForge for my work, but I tried to keep the bulk of it app-agnostic so it can be used in a more general way.0
-
This is my module: jkFontGeometry
Let me know if you have any questions or suggestions. A demo script for Glyphs that extracts the SuperCubics from the current layer is included to get you started.
Some functions are provided in a c extension for speed. This is optional, the same functions are available in pure Python from jkFontGeometry.geometry.3 -
https://github.com/typemytype/booleanOperations implements boolean Operations such as union / intersection etc. (I would consider this as one of the hardest parts of writing an own library; other things like transformations, extrema, subdivision, joining, expanding a stroke are relatively easy).Khaled Hosny already mentioned fontTools.misc.bezierTools: https://github.com/typesupply/defcon and other bigger projects also makes use of fontTools.misc.bezierTools, it may be worth to have a look at them as well, because they implement additional methods.
1 -
Propped path expanding is not so easy (at least not for me). I figured out my own algorithm to remove overlap but my path expansion is not where I like it to be. Especially the inner curve with small radius.2
-
+1 to what Georg said. Actually, path expansion and remove overlap both have some interesting “corner cases,” where things get extra complicated.1
-
Okay, I admit: path expansion is tricky when the curvature radius of the path is smaller than the pen radius.
2 -
There is also https://github.com/dhermes/bezier , https://bezier.readthedocs.io/en/latest/reference/bezier.html .
However, it also does not contain any solutions for offset or simplify. But in my understanding there are no generic solutions for those problems, therefore you wont find them in a base library.1 -
Well, I've started with my version: https://simoncozens.github.io/beziers.py/index.html
Here's an example:fig, ax = plt.subplots() points = [ Point(100,50), Point(50,150), Point(150,250), Point(200,220), Point(250,80), Point(220,50) ] path = BezierPath.fromPoints(points) tl,br = path.bounds() centroid = (tl+br)/2 path.rotate(centroid, math.pi/2) path.balance() path.plot(ax) path.offset(Point(5,5)).plot(ax, color="red") path.offset(Point(-5,-5)).plot(ax, color="green") plt.show()
3 -
@Simon Cozens that's beautiful!0
-
Yeah, that’s excellent, @Simon Cozens! Nice work!
What has happened in the top right corner of the offset curves? How come the offset curve handles are showing corners (i.e., not colinear)?0 -
The way I'm doing offsetting is slightly sneaky. I sample the original curve, offset the sampled points, and then run a curve fitting algorithm. (What can I say? There isn't a good way to do it, and when all you've got is a hammer...)
Currently I'm doing that process segment-wise, rather than over the whole path, which is why you were seeing a corner there. It's not hard to change it to collect the samples over the whole path and path-fit to that:
I could probably improve it a bit by adding more samples around areas of high curvature and less around areas of low curvature; that would smooth out parts like the top right and the left-hand side (around (60,130) or so).2 -
The corner is not totally wrong. In tight corners, you should see a corner.2
-
Am I too stupid to find it or is the source code not yet on github?
Ich second Georg Seifert: The old outlining algorithm seems to have better results at the points with high curvature. Be aware that the outlines may be self-intersecting, when you just interpolate offset sample points. It may be a good idea to examine the curvature along the original path with respect to the stroking radius and then only consider the parts that have a curvature radius >= stroking radius to avoid the self intersection.
Or just remove the self-intersection afterwards (that's what I did).
Ist there a reason, why you make the list of points redundant (the first and the last point are identical)?(255.0,20.0), (385.0,20.0), (526.0,79.0), (566.0,135.0)], [ (566.0,135.0), ...
0 -
Here’s the code: https://github.com/simoncozens/beziers.py
and here are the docs:
https://simoncozens.github.io/beziers.py/index.html
The points are deliberately redundant if you ask for the curve as a segment representation, because you want four points in each segment to make a cubic Bézier. If you don’t want that, you can ask for the curve in node list representation.0 -
Simon Cozens said:Here’s the code: https://github.com/simoncozens/beziers.py
and here are the docs:
https://simoncozens.github.io/beziers.py/index.html
The points are deliberately redundant if you ask for the curve as a segment representation, because you want four points in each segment to make a cubic Bézier. If you don’t want that, you can ask for the curve in node list representation.
I'm trying to convert TTF fonts to OTF with FontTools, specifically T2CharStringPen. I noticed that the generated OTF fonts have more point than the TTF sources. This is the code:def get_cff_charstrings(self) -> dict:
"""
Get T2 charstrings to convert a font from TTF to CFF
:return: A dictionary of charstrings.
"""
charstrings = {}
glyph_set = self.getGlyphSet()
for k, v in glyph_set.items():
# Remove overlaps and fix contours direction
pathops_path = pathops.Path()
pathops_pen = pathops_path.getPen(glyphSet=glyph_set)
glyph_set[k].draw(pathops_pen)
pathops_path.simplify()
# Draw the glyph with T2CharStringPen and get the charstring
t2_pen = T2CharStringPen(v.width, glyphSet=glyph_set)
pathops_path.draw(t2_pen)
charstring = t2_pen.getCharString()
charstrings[k] = charstring
return charstrings
Is there a way to simplify the paths using the tidy() method of BezierPath and pass the new path to pathops pen or t2_pen?
0
Categories
- All Categories
- 43 Introductions
- 3.7K Typeface Design
- 800 Font Technology
- 1K Technique and Theory
- 617 Type Business
- 444 Type Design Critiques
- 541 Type Design Software
- 30 Punchcutting
- 136 Lettering and Calligraphy
- 83 Technique and Theory
- 53 Lettering Critiques
- 483 Typography
- 301 History of Typography
- 114 Education
- 68 Resources
- 498 Announcements
- 79 Events
- 105 Job Postings
- 148 Type Releases
- 165 Miscellaneous News
- 269 About TypeDrawers
- 53 TypeDrawers Announcements
- 116 Suggestions and Bug Reports