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