summaryrefslogtreecommitdiff
path: root/scripts/lib/fontbuild/curveFitPen.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/fontbuild/curveFitPen.py')
-rw-r--r--scripts/lib/fontbuild/curveFitPen.py402
1 files changed, 402 insertions, 0 deletions
diff --git a/scripts/lib/fontbuild/curveFitPen.py b/scripts/lib/fontbuild/curveFitPen.py
new file mode 100644
index 0000000..d050479
--- /dev/null
+++ b/scripts/lib/fontbuild/curveFitPen.py
@@ -0,0 +1,402 @@
+#! /opt/local/bin/pythonw2.7
+
+__all__ = ["SubsegmentPen","SubsegmentsToCurvesPen", "segmentGlyph", "fitGlyph"]
+
+from fontTools.pens.basePen import BasePen
+from fontTools.misc import bezierTools
+from robofab.pens.pointPen import AbstractPointPen
+from robofab.pens.adapterPens import PointToSegmentPen, GuessSmoothPointPen
+import numpy as np
+from numpy.linalg import norm
+from numpy import array as v
+from random import random
+
+from robofab.pens.pointPen import BasePointToSegmentPen
+class SubsegmentsToCurvesPointPen(BasePointToSegmentPen):
+ def __init__(self, glyph, subsegmentGlyph, subsegments):
+ BasePointToSegmentPen.__init__(self)
+ self.glyph = glyph
+ self.subPen = SubsegmentsToCurvesPen(None, glyph.getPen(), subsegmentGlyph, subsegments)
+
+ def setMatchTangents(self, b):
+ self.subPen.matchTangents = b
+
+ def _flushContour(self, segments):
+ #
+ # adapted from robofab.pens.adapterPens.rfUFOPointPen
+ #
+ assert len(segments) >= 1
+ # if we only have one point and it has a name, we must have an anchor
+ first = segments[0]
+ segmentType, points = first
+ pt, smooth, name, kwargs = points[0]
+ if len(segments) == 1 and name != None:
+ self.glyph.appendAnchor(name, pt)
+ return
+ else:
+ segmentType, points = segments[-1]
+ movePt, smooth, name, kwargs = points[-1]
+ if smooth:
+ # last point is smooth, set pen to start smooth
+ self.subPen.setLastSmooth(True)
+ if segmentType == 'line':
+ del segments[-1]
+
+ self.subPen.moveTo(movePt)
+
+ # do the rest of the segments
+ for segmentType, points in segments:
+ isSmooth = True in [smooth for pt, smooth, name, kwargs in points]
+ pp = [pt for pt, smooth, name, kwargs in points]
+ if segmentType == "line":
+ assert len(pp) == 1
+ if isSmooth:
+ self.subPen.smoothLineTo(pp[0])
+ else:
+ self.subPen.lineTo(pp[0])
+ elif segmentType == "curve":
+ assert len(pp) == 3
+ if isSmooth:
+ self.subPen.smoothCurveTo(*pp)
+ else:
+ self.subPen.curveTo(*pp)
+ elif segmentType == "qcurve":
+ assert 0, "qcurve not supported"
+ else:
+ assert 0, "illegal segmentType: %s" % segmentType
+ self.subPen.closePath()
+
+ def addComponent(self, glyphName, transform):
+ self.subPen.addComponent(glyphName, transform)
+
+class SubsegmentsToCurvesPen(BasePen):
+ def __init__(self, glyphSet, otherPen, subsegmentGlyph, subsegments):
+ BasePen.__init__(self, None)
+ self.otherPen = otherPen
+ self.ssglyph = subsegmentGlyph
+ self.subsegments = subsegments
+ self.contourIndex = -1
+ self.segmentIndex = -1
+ self.lastPoint = (0,0)
+ self.lastSmooth = False
+ self.nextSmooth = False
+
+ def setLastSmooth(self, b):
+ self.lastSmooth = b
+
+ def _moveTo(self, (x, y)):
+ self.contourIndex += 1
+ self.segmentIndex = 0
+ self.startPoint = (x,y)
+ p = self.ssglyph.contours[self.contourIndex][0].points[0]
+ self.otherPen.moveTo((p.x, p.y))
+ self.lastPoint = (x,y)
+
+ def _lineTo(self, (x, y)):
+ self.segmentIndex += 1
+ index = self.subsegments[self.contourIndex][self.segmentIndex][0]
+ p = self.ssglyph.contours[self.contourIndex][index].points[0]
+ self.otherPen.lineTo((p.x, p.y))
+ self.lastPoint = (x,y)
+ self.lastSmooth = False
+
+ def smoothLineTo(self, (x, y)):
+ self.lineTo((x,y))
+ self.lastSmooth = True
+
+ def smoothCurveTo(self, (x1, y1), (x2, y2), (x3, y3)):
+ self.nextSmooth = True
+ self.curveTo((x1, y1), (x2, y2), (x3, y3))
+ self.nextSmooth = False
+ self.lastSmooth = True
+
+ def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
+ self.segmentIndex += 1
+ c = self.ssglyph.contours[self.contourIndex]
+ n = len(c)
+ startIndex = (self.subsegments[self.contourIndex][self.segmentIndex-1][0])
+ segmentCount = (self.subsegments[self.contourIndex][self.segmentIndex][1])
+ endIndex = (startIndex + segmentCount + 1) % (n)
+
+ indices = [(startIndex + i) % (n) for i in range(segmentCount + 1)]
+ points = np.array([(c[i].points[0].x, c[i].points[0].y) for i in indices])
+ prevPoint = (c[(startIndex - 1)].points[0].x, c[(startIndex - 1)].points[0].y)
+ nextPoint = (c[(endIndex) % n].points[0].x, c[(endIndex) % n].points[0].y)
+ prevTangent = prevPoint - points[0]
+ nextTangent = nextPoint - points[-1]
+
+ tangent1 = points[1] - points[0]
+ tangent3 = points[-2] - points[-1]
+ prevTangent /= np.linalg.norm(prevTangent)
+ nextTangent /= np.linalg.norm(nextTangent)
+ tangent1 /= np.linalg.norm(tangent1)
+ tangent3 /= np.linalg.norm(tangent3)
+
+ tangent1, junk = self.smoothTangents(tangent1, prevTangent, self.lastSmooth)
+ tangent3, junk = self.smoothTangents(tangent3, nextTangent, self.nextSmooth)
+ if self.matchTangents == True:
+ cp = fitBezier(points, tangent1, tangent3)
+ cp[1] = norm(cp[1] - cp[0]) * tangent1 / norm(tangent1) + cp[0]
+ cp[2] = norm(cp[2] - cp[3]) * tangent3 / norm(tangent3) + cp[3]
+ else:
+ cp = fitBezier(points)
+ # if self.ssglyph.name == 'r':
+ # print "-----------"
+ # print self.lastSmooth, self.nextSmooth
+ # print "%i %i : %i %i \n %i %i : %i %i \n %i %i : %i %i"%(x1,y1, cp[1,0], cp[1,1], x2,y2, cp[2,0], cp[2,1], x3,y3, cp[3,0], cp[3,1])
+ self.otherPen.curveTo((cp[1,0], cp[1,1]), (cp[2,0], cp[2,1]), (cp[3,0], cp[3,1]))
+ self.lastPoint = (x3, y3)
+ self.lastSmooth = False
+
+ def smoothTangents(self,t1,t2,forceSmooth = False):
+ if forceSmooth or (abs(t1.dot(t2)) > .95 and norm(t1-t2) > 1):
+ # print t1,t2,
+ t1 = (t1 - t2) / 2
+ t2 = -t1
+ # print t1,t2
+ return t1 / norm(t1), t2 / norm(t2)
+
+
+ def _closePath(self):
+ self.otherPen.closePath()
+
+ def _endPath(self):
+ self.otherPen.endPath()
+
+
+ def addComponent(self, glyphName, transformation):
+ self.otherPen.addComponent(glyphName, transformation)
+
+
+class SubsegmentPointPen(BasePointToSegmentPen):
+ def __init__(self, glyph, resolution):
+ BasePointToSegmentPen.__init__(self)
+ self.glyph = glyph
+ self.resolution = resolution
+ self.subPen = SubsegmentPen(None, glyph.getPen())
+
+ def getSubsegments(self):
+ return self.subPen.subsegments[:]
+
+ def _flushContour(self, segments):
+ #
+ # adapted from robofab.pens.adapterPens.rfUFOPointPen
+ #
+ assert len(segments) >= 1
+ # if we only have one point and it has a name, we must have an anchor
+ first = segments[0]
+ segmentType, points = first
+ pt, smooth, name, kwargs = points[0]
+ if len(segments) == 1 and name != None:
+ self.glyph.appendAnchor(name, pt)
+ return
+ else:
+ segmentType, points = segments[-1]
+ movePt, smooth, name, kwargs = points[-1]
+ if segmentType == 'line':
+ del segments[-1]
+
+ self.subPen.moveTo(movePt)
+
+ # do the rest of the segments
+ for segmentType, points in segments:
+ points = [pt for pt, smooth, name, kwargs in points]
+ if segmentType == "line":
+ assert len(points) == 1
+ self.subPen.lineTo(points[0])
+ elif segmentType == "curve":
+ assert len(points) == 3
+ self.subPen.curveTo(*points)
+ elif segmentType == "qcurve":
+ assert 0, "qcurve not supported"
+ else:
+ assert 0, "illegal segmentType: %s" % segmentType
+ self.subPen.closePath()
+
+ def addComponent(self, glyphName, transform):
+ self.subPen.addComponent(glyphName, transform)
+
+class SubsegmentPen(BasePen):
+
+ def __init__(self, glyphSet, otherPen, resolution=25):
+ BasePen.__init__(self,glyphSet)
+ self.resolution = resolution
+ self.otherPen = otherPen
+ self.subsegments = []
+ self.startContour = (0,0)
+ self.contourIndex = -1
+
+ def _moveTo(self, (x, y)):
+ self.contourIndex += 1
+ self.segmentIndex = 0
+ self.subsegments.append([])
+ self.subsegmentCount = 0
+ self.subsegments[self.contourIndex].append([self.subsegmentCount, 0])
+ self.startContour = (x,y)
+ self.lastPoint = (x,y)
+ self.otherPen.moveTo((x,y))
+
+ def _lineTo(self, (x, y)):
+ count = self.stepsForSegment((x,y),self.lastPoint)
+ if count < 1:
+ count = 1
+ self.subsegmentCount += count
+ self.subsegments[self.contourIndex].append([self.subsegmentCount, count])
+ for i in range(1,count+1):
+ x1 = self.lastPoint[0] + (x - self.lastPoint[0]) * i/float(count)
+ y1 = self.lastPoint[1] + (y - self.lastPoint[1]) * i/float(count)
+ self.otherPen.lineTo((x1,y1))
+ self.lastPoint = (x,y)
+
+ def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
+ count = self.stepsForSegment((x3,y3),self.lastPoint)
+ if count < 2:
+ count = 2
+ self.subsegmentCount += count
+ self.subsegments[self.contourIndex].append([self.subsegmentCount,count])
+ x = self.renderCurve((self.lastPoint[0],x1,x2,x3),count)
+ y = self.renderCurve((self.lastPoint[1],y1,y2,y3),count)
+ assert len(x) == count
+ if (x3 == self.startContour[0] and y3 == self.startContour[1]):
+ count -= 1
+ for i in range(count):
+ self.otherPen.lineTo((x[i],y[i]))
+ self.lastPoint = (x3,y3)
+
+ def _closePath(self):
+ if not (self.lastPoint[0] == self.startContour[0] and self.lastPoint[1] == self.startContour[1]):
+ self._lineTo(self.startContour)
+ self.otherPen.closePath()
+
+ def _endPath(self):
+ self.otherPen.endPath()
+
+ def addComponent(self, glyphName, transformation):
+ self.otherPen.addComponent(glyphName, transformation)
+
+ def stepsForSegment(self, p1, p2):
+ dist = np.linalg.norm(v(p1) - v(p2))
+ out = int(dist / self.resolution)
+ return out
+
+ def renderCurve(self,p,count):
+ curvePoints = []
+ t = 1.0 / float(count)
+ temp = t * t
+
+ f = p[0]
+ fd = 3 * (p[1] - p[0]) * t
+ fdd_per_2 = 3 * (p[0] - 2 * p[1] + p[2]) * temp
+ fddd_per_2 = 3 * (3 * (p[1] - p[2]) + p[3] - p[0]) * temp * t
+
+ fddd = fddd_per_2 + fddd_per_2
+ fdd = fdd_per_2 + fdd_per_2
+ fddd_per_6 = fddd_per_2 * (1.0 / 3)
+
+ for i in range(count):
+ f = f + fd + fdd_per_2 + fddd_per_6
+ fd = fd + fdd + fddd_per_2
+ fdd = fdd + fddd
+ fdd_per_2 = fdd_per_2 + fddd_per_2
+ curvePoints.append(f)
+
+ return curvePoints
+
+
+
+def fitBezierSimple(pts):
+ T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))]
+ tsum = np.sum(T)
+ T = [0] + T
+ T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))]
+ T = [[t**3, t**2, t, 1] for t in T]
+ T = np.array(T)
+ M = np.array([[-1, 3, -3, 1],
+ [ 3, -6, 3, 0],
+ [-3, 3, 0, 0],
+ [ 1, 0, 0, 0]])
+ T = T.dot(M)
+ T = np.concatenate((T, np.array([[100,0,0,0], [0,0,0,100]])))
+ # pts = np.vstack((pts, pts[0] * 100, pts[-1] * 100))
+ C = np.linalg.lstsq(T, pts)
+ return C[0]
+
+
+def subdivideLineSegment(pts):
+ out = [pts[0]]
+ for i in range(1, len(pts)):
+ out.append(pts[i-1] + (pts[i] - pts[i-1]) * .5)
+ out.append(pts[i])
+ return np.array(out)
+
+
+def fitBezier(pts,tangent0=None,tangent3=None):
+ if len(pts < 4):
+ pts = subdivideLineSegment(pts)
+ T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))]
+ tsum = np.sum(T)
+ T = [0] + T
+ T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))]
+ T = [[t**3, t**2, t, 1] for t in T]
+ T = np.array(T)
+ M = np.array([[-1, 3, -3, 1],
+ [ 3, -6, 3, 0],
+ [-3, 3, 0, 0],
+ [ 1, 0, 0, 0]])
+ T = T.dot(M)
+ n = len(pts)
+ pout = pts.copy()
+ pout[:,0] -= (T[:,0] * pts[0,0]) + (T[:,3] * pts[-1,0])
+ pout[:,1] -= (T[:,0] * pts[0,1]) + (T[:,3] * pts[-1,1])
+
+ TT = np.zeros((n*2,4))
+ for i in range(n):
+ for j in range(2):
+ TT[i*2,j*2] = T[i,j+1]
+ TT[i*2+1,j*2+1] = T[i,j+1]
+ pout = pout.reshape((n*2,1),order="C")
+
+ if tangent0 != None and tangent3 != None:
+ tangentConstraintsT = np.array([
+ [tangent0[1], -tangent0[0], 0, 0],
+ [0, 0, tangent3[1], -tangent3[0]]
+ ])
+ tangentConstraintsP = np.array([
+ [pts[0][1] * -tangent0[0] + pts[0][0] * tangent0[1]],
+ [pts[-1][1] * -tangent3[0] + pts[-1][0] * tangent3[1]]
+ ])
+ TT = np.concatenate((TT, tangentConstraintsT * 1000))
+ pout = np.concatenate((pout, tangentConstraintsP * 1000))
+ C = np.linalg.lstsq(TT,pout)[0].reshape((2,2))
+ return np.array([pts[0], C[0], C[1], pts[-1]])
+
+
+def segmentGlyph(glyph,resolution=50):
+ g1 = glyph.copy()
+ g1.clear()
+ dp = SubsegmentPointPen(g1, resolution)
+ glyph.drawPoints(dp)
+ return g1, dp.getSubsegments()
+
+
+def fitGlyph(glyph, subsegmentGlyph, subsegmentIndices, matchTangents=True):
+ outGlyph = glyph.copy()
+ outGlyph.clear()
+ fitPen = SubsegmentsToCurvesPointPen(outGlyph, subsegmentGlyph, subsegmentIndices)
+ fitPen.setMatchTangents(matchTangents)
+ # smoothPen = GuessSmoothPointPen(fitPen)
+ glyph.drawPoints(fitPen)
+ outGlyph.width = subsegmentGlyph.width
+ return outGlyph
+
+
+if __name__ == '__main__':
+ p = SubsegmentPen(None, None)
+ pts = np.array([
+ [0,0],
+ [.5,.5],
+ [.5,.5],
+ [1,1]
+ ])
+ print np.array(p.renderCurve(pts,10)) * 10
+ \ No newline at end of file