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, docstring=None): 112 113 if docstring is not None: 114 print('"""%s"""' % docstring) 115 116 print( 117'''from fontTools.pens.basePen import BasePen, OpenContourError 118try: 119 import cython 120except ImportError: 121 # if cython not installed, use mock module with no-op decorators and types 122 from fontTools.misc import cython 123 124if cython.compiled: 125 # Yep, I'm compiled. 126 COMPILED = True 127else: 128 # Just a lowly interpreted script. 129 COMPILED = False 130 131 132__all__ = ["%s"] 133 134class %s(BasePen): 135 136 def __init__(self, glyphset=None): 137 BasePen.__init__(self, glyphset) 138'''% (penName, penName), file=file) 139 for name,f in funcs: 140 print(' self.%s = 0' % name, file=file) 141 print(''' 142 def _moveTo(self, p0): 143 self.__startPoint = p0 144 145 def _closePath(self): 146 p0 = self._getCurrentPoint() 147 if p0 != self.__startPoint: 148 self._lineTo(self.__startPoint) 149 150 def _endPath(self): 151 p0 = self._getCurrentPoint() 152 if p0 != self.__startPoint: 153 # Green theorem is not defined on open contours. 154 raise OpenContourError( 155 "Green theorem is not defined on open contours." 156 ) 157''', end='', file=file) 158 159 for n in (1, 2, 3): 160 161 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 170 print() 171 for name,value in defs: 172 print(' @cython.locals(%s=cython.double)' % name, file=file) 173 if n == 1: 174 print('''\ 175 @cython.locals(x0=cython.double, y0=cython.double) 176 @cython.locals(x1=cython.double, y1=cython.double) 177 def _lineTo(self, p1): 178 x0,y0 = self._getCurrentPoint() 179 x1,y1 = p1 180''', file=file) 181 elif n == 2: 182 print('''\ 183 @cython.locals(x0=cython.double, y0=cython.double) 184 @cython.locals(x1=cython.double, y1=cython.double) 185 @cython.locals(x2=cython.double, y2=cython.double) 186 def _qCurveToOne(self, p1, p2): 187 x0,y0 = self._getCurrentPoint() 188 x1,y1 = p1 189 x2,y2 = p2 190''', file=file) 191 elif n == 3: 192 print('''\ 193 @cython.locals(x0=cython.double, y0=cython.double) 194 @cython.locals(x1=cython.double, y1=cython.double) 195 @cython.locals(x2=cython.double, y2=cython.double) 196 @cython.locals(x3=cython.double, y3=cython.double) 197 def _curveToOne(self, p1, p2, p3): 198 x0,y0 = self._getCurrentPoint() 199 x1,y1 = p1 200 x2,y2 = p2 201 x3,y3 = p3 202''', file=file) 203 for name,value in defs: 204 print(' %s = %s' % (name, value), file=file) 205 206 print(file=file) 207 for name,value in zip([f[0] for f in funcs], exprs): 208 print(' self.%s += %s' % (name, value), file=file) 209 210 print(''' 211if __name__ == '__main__': 212 from fontTools.misc.symfont import x, y, printGreenPen 213 printGreenPen('%s', ['''%penName, file=file) 214 for name,f in funcs: 215 print(" ('%s', %s)," % (name, str(f)), file=file) 216 print(' ])', file=file) 217 218 219if __name__ == '__main__': 220 pen = AreaPen() 221 pen.moveTo((100,100)) 222 pen.lineTo((100,200)) 223 pen.lineTo((200,200)) 224 pen.curveTo((200,250),(300,300),(250,350)) 225 pen.lineTo((200,100)) 226 pen.closePath() 227 print(pen.value) 228