diff options
Diffstat (limited to 'scripts/lib/fontbuild/curveFitPen.py')
-rw-r--r-- | scripts/lib/fontbuild/curveFitPen.py | 107 |
1 files changed, 56 insertions, 51 deletions
diff --git a/scripts/lib/fontbuild/curveFitPen.py b/scripts/lib/fontbuild/curveFitPen.py index 7c232c0..f7c0cae 100644 --- a/scripts/lib/fontbuild/curveFitPen.py +++ b/scripts/lib/fontbuild/curveFitPen.py @@ -17,25 +17,24 @@ __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 numpy.linalg import norm +from robofab.pens.adapterPens import GuessSmoothPointPen 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 @@ -56,13 +55,13 @@ class SubsegmentsToCurvesPointPen(BasePointToSegmentPen): 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] + pp = [pt for pt, smooth, name, kwargs in points] if segmentType == "line": assert len(pp) == 1 if isSmooth: @@ -73,17 +72,18 @@ class SubsegmentsToCurvesPointPen(BasePointToSegmentPen): assert len(pp) == 3 if isSmooth: self.subPen.smoothCurveTo(*pp) - else: + 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) @@ -95,10 +95,10 @@ class SubsegmentsToCurvesPen(BasePen): 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 @@ -106,7 +106,7 @@ class SubsegmentsToCurvesPen(BasePen): 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] @@ -114,39 +114,39 @@ class SubsegmentsToCurvesPen(BasePen): 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)): + + 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: @@ -162,7 +162,7 @@ class SubsegmentsToCurvesPen(BasePen): 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, @@ -170,15 +170,13 @@ class SubsegmentsToCurvesPen(BasePen): 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) @@ -189,10 +187,10 @@ class SubsegmentPointPen(BasePointToSegmentPen): 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 @@ -210,9 +208,9 @@ class SubsegmentPointPen(BasePointToSegmentPen): 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] @@ -227,12 +225,13 @@ class SubsegmentPointPen(BasePointToSegmentPen): 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 @@ -240,7 +239,7 @@ class SubsegmentPen(BasePen): self.subsegments = [] self.startContour = (0,0) self.contourIndex = -1 - + def _moveTo(self, (x, y)): self.contourIndex += 1 self.segmentIndex = 0 @@ -250,7 +249,7 @@ class SubsegmentPen(BasePen): 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: @@ -262,7 +261,7 @@ class SubsegmentPen(BasePen): 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: @@ -277,46 +276,53 @@ class SubsegmentPen(BasePen): 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) + + # round values used by otherPen (a RoboFab SegmentToPointPen) to decide + # whether to delete duplicate points at start and end of contour + #TODO(jamesgk) figure out why we have to do this hack, then remove it + c = self.otherPen.contour + for i in [0, -1]: + c[i] = [[round(n, 5) for n in c[i][0]]] + list(c[i][1:]) + 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 + return curvePoints def fitBezierSimple(pts): @@ -363,14 +369,14 @@ def fitBezier(pts,tangent0=None,tangent3=None): 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], @@ -414,4 +420,3 @@ if __name__ == '__main__': [1,1] ]) print np.array(p.renderCurve(pts,10)) * 10 - |