I would like to draw a TrueType Font glyph with PyQt5 QPainterPath. Example glyph fragment: (data from Fonttools ttx )
<pt x="115" y="255" on="1"/>
<pt x="71" y="255" on="0"/>
<pt x="64" y="244" on="0"/>
<pt x="53" y="213" on="0"/>
<pt x="44" y="180" on="0"/>
<pt x="39" y="166" on="1"/>
on=0 means a control point and on=1 means a start/end point I'm assuming this would not use (QPainterPath) quadTo or cubicTo as it is a higher order curve.
True type fonts actually use only quadratic Bézier curves. This makes sense, they are pretty simple curves that don't require a lot of computation, which is good for performance when you have to potentially draw hundreds or thousands of curves even for a simple paragraph.
After realizing this, I found out strange that you have a curve with 4 control points, but then I did a bit of research and found out this interesting answer.
In reality, the TrueType format allows grouping quadratic curves that always share each start or end point at the middle of each control point.
So, starting with your list:
Start <pt x="115" y="255" on="1"/>
C1 <pt x="71" y="255" on="0"/>
C2 <pt x="64" y="244" on="0"/>
C3 <pt x="53" y="213" on="0"/>
C4 <pt x="44" y="180" on="0"/>
End <pt x="39" y="166" on="1"/>
We have 6 points, but there are 4 curves, and the intermediate points between the 4 control points are the remaining start/end points that exist on the curve:
start | control | end |
---|---|---|
Start | C1 | (C2-C1)/2 |
(C2-C1)/2 | C2 | (C3-C2)/2 |
(C3-C2)/2 | C3 | (C4-C3)/2 |
(C4-C3)/2 | C4 | End |
To compute all that, we can cycle through the points and store a reference to the previous, and whenever we have a control point or an on-curve point after them, we add a new quadratic curve to the path.
start | control | end |
---|---|---|
115 x 255 | 71 x 255 | 67.5 x 249.5 |
67.5 x 249.5 | 64 x 244 | 58.5 x 228.5 |
58.5 x 228.5 | 53 x 213 | 48.5 x 106.5 |
48.5 x 106.5 | 44 x 180 | 39 x 166 |
The following code will create a QPainterPath that corresponds to each <contour>
group.
path = QtGui.QPainterPath()
currentCurve = []
started = False
for x, y, onCurve in contour:
point = QtCore.QPointF(x, y)
if onCurve:
if not currentCurve:
# start of curve
currentCurve.append(point)
else:
# end of curve
start, cp = currentCurve
path.quadTo(cp, point)
currentCurve = []
started = False
else:
if len(currentCurve) == 1:
# control point
currentCurve.append(point)
else:
start, cp = currentCurve
# find the midpoint
end = QtCore.QLineF(cp, point).pointAt(.5)
if not started:
# first curve of many
path.moveTo(start)
started = True
path.quadTo(cp, end)
currentCurve = [end, point]