1from fontTools.pens.basePen import BasePen 2from functools import partial 3from itertools import count 4import sympy as sp 5import sys 6 7n = 3 # Max Bezier degree; 3 for cubic, 2 for quadratic 8 9t, x, y = sp.symbols('t x y', real=True) 10c = sp.symbols('c', real=False) # Complex representation instead of x/y 11 12X = tuple(sp.symbols('x:%d'%(n+1), real=True)) 13Y = tuple(sp.symbols('y:%d'%(n+1), real=True)) 14P = tuple(zip(*(sp.symbols('p:%d[%s]'%(n+1,w), real=True) for w in '01'))) 15C = tuple(sp.symbols('c:%d'%(n+1), real=False)) 16 17# Cubic Bernstein basis functions 18BinomialCoefficient = [(1, 0)] 19for i in range(1, n+1): 20 last = BinomialCoefficient[-1] 21 this = tuple(last[j-1]+last[j] for j in range(len(last)))+(0,) 22 BinomialCoefficient.append(this) 23BinomialCoefficient = tuple(tuple(item[:-1]) for item in BinomialCoefficient) 24del last, this 25 26BernsteinPolynomial = tuple( 27 tuple(c * t**i * (1-t)**(n-i) for i,c in enumerate(coeffs)) 28 for n,coeffs in enumerate(BinomialCoefficient)) 29 30BezierCurve = tuple( 31 tuple(sum(P[i][j]*bernstein for i,bernstein in enumerate(bernsteins)) 32 for j in range(2)) 33 for n,bernsteins in enumerate(BernsteinPolynomial)) 34BezierCurveC = tuple( 35 sum(C[i]*bernstein for i,bernstein in enumerate(bernsteins)) 36 for n,bernsteins in enumerate(BernsteinPolynomial)) 37 38 39def green(f, curveXY): 40 f = -sp.integrate(sp.sympify(f), y) 41 f = f.subs({x:curveXY[0], y:curveXY[1]}) 42 f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1)) 43 return f 44 45 46class _BezierFuncsLazy(dict): 47 48 def __init__(self, symfunc): 49 self._symfunc = symfunc 50 self._bezfuncs = {} 51 52 def __missing__(self, i): 53 args = ['p%d'%d for d in range(i+1)] 54 f = green(self._symfunc, BezierCurve[i]) 55 f = sp.gcd_terms(f.collect(sum(P,()))) # Optimize 56 return sp.lambdify(args, f) 57 58class GreenPen(BasePen): 59 60 _BezierFuncs = {} 61 62 @classmethod 63 def _getGreenBezierFuncs(celf, func): 64 funcstr = str(func) 65 if not funcstr in celf._BezierFuncs: 66 celf._BezierFuncs[funcstr] = _BezierFuncsLazy(func) 67 return celf._BezierFuncs[funcstr] 68 69 def __init__(self, func, glyphset=None): 70 BasePen.__init__(self, glyphset) 71 self._funcs = self._getGreenBezierFuncs(func) 72 self.value = 0 73 74 def _moveTo(self, p0): 75 self.__startPoint = p0 76 77 def _closePath(self): 78 p0 = self._getCurrentPoint() 79 if p0 != self.__startPoint: 80 self._lineTo(self.__startPoint) 81 82 def _endPath(self): 83 p0 = self._getCurrentPoint() 84 if p0 != self.__startPoint: 85 # Green theorem is not defined on open contours. 86 raise NotImplementedError 87 88 def _lineTo(self, p1): 89 p0 = self._getCurrentPoint() 90 self.value += self._funcs[1](p0, p1) 91 92 def _qCurveToOne(self, p1, p2): 93 p0 = self._getCurrentPoint() 94 self.value += self._funcs[2](p0, p1, p2) 95 96 def _curveToOne(self, p1, p2, p3): 97 p0 = self._getCurrentPoint() 98 self.value += self._funcs[3](p0, p1, p2, p3) 99 100# Sample pens. 101# Do not use this in real code. 102# Use fontTools.pens.momentsPen.MomentsPen instead. 103AreaPen = partial(GreenPen, func=1) 104MomentXPen = partial(GreenPen, func=x) 105MomentYPen = partial(GreenPen, func=y) 106MomentXXPen = partial(GreenPen, func=x*x) 107MomentYYPen = partial(GreenPen, func=y*y) 108MomentXYPen = partial(GreenPen, func=x*y) 109 110 111def printGreenPen(penName, funcs, file=sys.stdout): 112 113 print( 114'''from fontTools.pens.basePen import BasePen 115 116class %s(BasePen): 117 118 def __init__(self, glyphset=None): 119 BasePen.__init__(self, glyphset) 120'''%penName, file=file) 121 for name,f in funcs: 122 print(' self.%s = 0' % name, file=file) 123 print(''' 124 def _moveTo(self, p0): 125 self.__startPoint = p0 126 127 def _closePath(self): 128 p0 = self._getCurrentPoint() 129 if p0 != self.__startPoint: 130 self._lineTo(self.__startPoint) 131 132 def _endPath(self): 133 p0 = self._getCurrentPoint() 134 if p0 != self.__startPoint: 135 # Green theorem is not defined on open contours. 136 raise NotImplementedError 137''', end='', file=file) 138 139 for n in (1, 2, 3): 140 141 if n == 1: 142 print(''' 143 def _lineTo(self, p1): 144 x0,y0 = self._getCurrentPoint() 145 x1,y1 = p1 146''', file=file) 147 elif n == 2: 148 print(''' 149 def _qCurveToOne(self, p1, p2): 150 x0,y0 = self._getCurrentPoint() 151 x1,y1 = p1 152 x2,y2 = p2 153''', file=file) 154 elif n == 3: 155 print(''' 156 def _curveToOne(self, p1, p2, p3): 157 x0,y0 = self._getCurrentPoint() 158 x1,y1 = p1 159 x2,y2 = p2 160 x3,y3 = p3 161''', file=file) 162 subs = {P[i][j]: [X, Y][j][i] for i in range(n+1) for j in range(2)} 163 greens = [green(f, BezierCurve[n]) for name,f in funcs] 164 greens = [sp.gcd_terms(f.collect(sum(P,()))) for f in greens] # Optimize 165 greens = [f.subs(subs) for f in greens] # Convert to p to x/y 166 defs, exprs = sp.cse(greens, 167 optimizations='basic', 168 symbols=(sp.Symbol('r%d'%i) for i in count())) 169 for name,value in defs: 170 print(' %s = %s' % (name, value), file=file) 171 print(file=file) 172 for name,value in zip([f[0] for f in funcs], exprs): 173 print(' self.%s += %s' % (name, value), file=file) 174 175 print(''' 176if __name__ == '__main__': 177 from fontTools.misc.symfont import x, y, printGreenPen 178 printGreenPen('%s', ['''%penName, file=file) 179 for name,f in funcs: 180 print(" ('%s', %s)," % (name, str(f)), file=file) 181 print(' ])', file=file) 182 183 184if __name__ == '__main__': 185 pen = AreaPen() 186 pen.moveTo((100,100)) 187 pen.lineTo((100,200)) 188 pen.lineTo((200,200)) 189 pen.curveTo((200,250),(300,300),(250,350)) 190 pen.lineTo((200,100)) 191 pen.closePath() 192 print(pen.value) 193