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) 30 31BezierCurve = tuple( 32 tuple( 33 sum(P[i][j] * bernstein for i, bernstein in enumerate(bernsteins)) 34 for j in range(2) 35 ) 36 for n, bernsteins in enumerate(BernsteinPolynomial) 37) 38BezierCurveC = tuple( 39 sum(C[i] * bernstein for i, bernstein in enumerate(bernsteins)) 40 for n, bernsteins in enumerate(BernsteinPolynomial) 41) 42 43 44def green(f, curveXY): 45 f = -sp.integrate(sp.sympify(f), y) 46 f = f.subs({x: curveXY[0], y: curveXY[1]}) 47 f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1)) 48 return f 49 50 51class _BezierFuncsLazy(dict): 52 def __init__(self, symfunc): 53 self._symfunc = symfunc 54 self._bezfuncs = {} 55 56 def __missing__(self, i): 57 args = ["p%d" % d for d in range(i + 1)] 58 f = green(self._symfunc, BezierCurve[i]) 59 f = sp.gcd_terms(f.collect(sum(P, ()))) # Optimize 60 return sp.lambdify(args, f) 61 62 63class GreenPen(BasePen): 64 _BezierFuncs = {} 65 66 @classmethod 67 def _getGreenBezierFuncs(celf, func): 68 funcstr = str(func) 69 if not funcstr in celf._BezierFuncs: 70 celf._BezierFuncs[funcstr] = _BezierFuncsLazy(func) 71 return celf._BezierFuncs[funcstr] 72 73 def __init__(self, func, glyphset=None): 74 BasePen.__init__(self, glyphset) 75 self._funcs = self._getGreenBezierFuncs(func) 76 self.value = 0 77 78 def _moveTo(self, p0): 79 self.__startPoint = p0 80 81 def _closePath(self): 82 p0 = self._getCurrentPoint() 83 if p0 != self.__startPoint: 84 self._lineTo(self.__startPoint) 85 86 def _endPath(self): 87 p0 = self._getCurrentPoint() 88 if p0 != self.__startPoint: 89 # Green theorem is not defined on open contours. 90 raise NotImplementedError 91 92 def _lineTo(self, p1): 93 p0 = self._getCurrentPoint() 94 self.value += self._funcs[1](p0, p1) 95 96 def _qCurveToOne(self, p1, p2): 97 p0 = self._getCurrentPoint() 98 self.value += self._funcs[2](p0, p1, p2) 99 100 def _curveToOne(self, p1, p2, p3): 101 p0 = self._getCurrentPoint() 102 self.value += self._funcs[3](p0, p1, p2, p3) 103 104 105# Sample pens. 106# Do not use this in real code. 107# Use fontTools.pens.momentsPen.MomentsPen instead. 108AreaPen = partial(GreenPen, func=1) 109MomentXPen = partial(GreenPen, func=x) 110MomentYPen = partial(GreenPen, func=y) 111MomentXXPen = partial(GreenPen, func=x * x) 112MomentYYPen = partial(GreenPen, func=y * y) 113MomentXYPen = partial(GreenPen, func=x * y) 114 115 116def printGreenPen(penName, funcs, file=sys.stdout, docstring=None): 117 if docstring is not None: 118 print('"""%s"""' % docstring) 119 120 print( 121 """from fontTools.pens.basePen import BasePen, OpenContourError 122try: 123 import cython 124 125 COMPILED = cython.compiled 126except (AttributeError, ImportError): 127 # if cython not installed, use mock module with no-op decorators and types 128 from fontTools.misc import cython 129 130 COMPILED = False 131 132 133__all__ = ["%s"] 134 135class %s(BasePen): 136 137 def __init__(self, glyphset=None): 138 BasePen.__init__(self, glyphset) 139""" 140 % (penName, penName), 141 file=file, 142 ) 143 for name, f in funcs: 144 print(" self.%s = 0" % name, file=file) 145 print( 146 """ 147 def _moveTo(self, p0): 148 self.__startPoint = p0 149 150 def _closePath(self): 151 p0 = self._getCurrentPoint() 152 if p0 != self.__startPoint: 153 self._lineTo(self.__startPoint) 154 155 def _endPath(self): 156 p0 = self._getCurrentPoint() 157 if p0 != self.__startPoint: 158 # Green theorem is not defined on open contours. 159 raise OpenContourError( 160 "Green theorem is not defined on open contours." 161 ) 162""", 163 end="", 164 file=file, 165 ) 166 167 for n in (1, 2, 3): 168 subs = {P[i][j]: [X, Y][j][i] for i in range(n + 1) for j in range(2)} 169 greens = [green(f, BezierCurve[n]) for name, f in funcs] 170 greens = [sp.gcd_terms(f.collect(sum(P, ()))) for f in greens] # Optimize 171 greens = [f.subs(subs) for f in greens] # Convert to p to x/y 172 defs, exprs = sp.cse( 173 greens, 174 optimizations="basic", 175 symbols=(sp.Symbol("r%d" % i) for i in count()), 176 ) 177 178 print() 179 for name, value in defs: 180 print(" @cython.locals(%s=cython.double)" % name, file=file) 181 if n == 1: 182 print( 183 """\ 184 @cython.locals(x0=cython.double, y0=cython.double) 185 @cython.locals(x1=cython.double, y1=cython.double) 186 def _lineTo(self, p1): 187 x0,y0 = self._getCurrentPoint() 188 x1,y1 = p1 189""", 190 file=file, 191 ) 192 elif n == 2: 193 print( 194 """\ 195 @cython.locals(x0=cython.double, y0=cython.double) 196 @cython.locals(x1=cython.double, y1=cython.double) 197 @cython.locals(x2=cython.double, y2=cython.double) 198 def _qCurveToOne(self, p1, p2): 199 x0,y0 = self._getCurrentPoint() 200 x1,y1 = p1 201 x2,y2 = p2 202""", 203 file=file, 204 ) 205 elif n == 3: 206 print( 207 """\ 208 @cython.locals(x0=cython.double, y0=cython.double) 209 @cython.locals(x1=cython.double, y1=cython.double) 210 @cython.locals(x2=cython.double, y2=cython.double) 211 @cython.locals(x3=cython.double, y3=cython.double) 212 def _curveToOne(self, p1, p2, p3): 213 x0,y0 = self._getCurrentPoint() 214 x1,y1 = p1 215 x2,y2 = p2 216 x3,y3 = p3 217""", 218 file=file, 219 ) 220 for name, value in defs: 221 print(" %s = %s" % (name, value), file=file) 222 223 print(file=file) 224 for name, value in zip([f[0] for f in funcs], exprs): 225 print(" self.%s += %s" % (name, value), file=file) 226 227 print( 228 """ 229if __name__ == '__main__': 230 from fontTools.misc.symfont import x, y, printGreenPen 231 printGreenPen('%s', [""" 232 % penName, 233 file=file, 234 ) 235 for name, f in funcs: 236 print(" ('%s', %s)," % (name, str(f)), file=file) 237 print(" ])", file=file) 238 239 240if __name__ == "__main__": 241 pen = AreaPen() 242 pen.moveTo((100, 100)) 243 pen.lineTo((100, 200)) 244 pen.lineTo((200, 200)) 245 pen.curveTo((200, 250), (300, 300), (250, 350)) 246 pen.lineTo((200, 100)) 247 pen.closePath() 248 print(pen.value) 249