Spaces:
Running
Running
from fontTools.pens.basePen import BasePen | |
from functools import partial | |
from itertools import count | |
import sympy as sp | |
import sys | |
n = 3 # Max Bezier degree; 3 for cubic, 2 for quadratic | |
t, x, y = sp.symbols("t x y", real=True) | |
c = sp.symbols("c", real=False) # Complex representation instead of x/y | |
X = tuple(sp.symbols("x:%d" % (n + 1), real=True)) | |
Y = tuple(sp.symbols("y:%d" % (n + 1), real=True)) | |
P = tuple(zip(*(sp.symbols("p:%d[%s]" % (n + 1, w), real=True) for w in "01"))) | |
C = tuple(sp.symbols("c:%d" % (n + 1), real=False)) | |
# Cubic Bernstein basis functions | |
BinomialCoefficient = [(1, 0)] | |
for i in range(1, n + 1): | |
last = BinomialCoefficient[-1] | |
this = tuple(last[j - 1] + last[j] for j in range(len(last))) + (0,) | |
BinomialCoefficient.append(this) | |
BinomialCoefficient = tuple(tuple(item[:-1]) for item in BinomialCoefficient) | |
del last, this | |
BernsteinPolynomial = tuple( | |
tuple(c * t**i * (1 - t) ** (n - i) for i, c in enumerate(coeffs)) | |
for n, coeffs in enumerate(BinomialCoefficient) | |
) | |
BezierCurve = tuple( | |
tuple( | |
sum(P[i][j] * bernstein for i, bernstein in enumerate(bernsteins)) | |
for j in range(2) | |
) | |
for n, bernsteins in enumerate(BernsteinPolynomial) | |
) | |
BezierCurveC = tuple( | |
sum(C[i] * bernstein for i, bernstein in enumerate(bernsteins)) | |
for n, bernsteins in enumerate(BernsteinPolynomial) | |
) | |
def green(f, curveXY): | |
f = -sp.integrate(sp.sympify(f), y) | |
f = f.subs({x: curveXY[0], y: curveXY[1]}) | |
f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1)) | |
return f | |
class _BezierFuncsLazy(dict): | |
def __init__(self, symfunc): | |
self._symfunc = symfunc | |
self._bezfuncs = {} | |
def __missing__(self, i): | |
args = ["p%d" % d for d in range(i + 1)] | |
f = green(self._symfunc, BezierCurve[i]) | |
f = sp.gcd_terms(f.collect(sum(P, ()))) # Optimize | |
return sp.lambdify(args, f) | |
class GreenPen(BasePen): | |
_BezierFuncs = {} | |
def _getGreenBezierFuncs(celf, func): | |
funcstr = str(func) | |
if not funcstr in celf._BezierFuncs: | |
celf._BezierFuncs[funcstr] = _BezierFuncsLazy(func) | |
return celf._BezierFuncs[funcstr] | |
def __init__(self, func, glyphset=None): | |
BasePen.__init__(self, glyphset) | |
self._funcs = self._getGreenBezierFuncs(func) | |
self.value = 0 | |
def _moveTo(self, p0): | |
self._startPoint = p0 | |
def _closePath(self): | |
p0 = self._getCurrentPoint() | |
if p0 != self._startPoint: | |
self._lineTo(self._startPoint) | |
def _endPath(self): | |
p0 = self._getCurrentPoint() | |
if p0 != self._startPoint: | |
# Green theorem is not defined on open contours. | |
raise NotImplementedError | |
def _lineTo(self, p1): | |
p0 = self._getCurrentPoint() | |
self.value += self._funcs[1](p0, p1) | |
def _qCurveToOne(self, p1, p2): | |
p0 = self._getCurrentPoint() | |
self.value += self._funcs[2](p0, p1, p2) | |
def _curveToOne(self, p1, p2, p3): | |
p0 = self._getCurrentPoint() | |
self.value += self._funcs[3](p0, p1, p2, p3) | |
# Sample pens. | |
# Do not use this in real code. | |
# Use fontTools.pens.momentsPen.MomentsPen instead. | |
AreaPen = partial(GreenPen, func=1) | |
MomentXPen = partial(GreenPen, func=x) | |
MomentYPen = partial(GreenPen, func=y) | |
MomentXXPen = partial(GreenPen, func=x * x) | |
MomentYYPen = partial(GreenPen, func=y * y) | |
MomentXYPen = partial(GreenPen, func=x * y) | |
def printGreenPen(penName, funcs, file=sys.stdout, docstring=None): | |
if docstring is not None: | |
print('"""%s"""' % docstring) | |
print( | |
"""from fontTools.pens.basePen import BasePen, OpenContourError | |
try: | |
import cython | |
COMPILED = cython.compiled | |
except (AttributeError, ImportError): | |
# if cython not installed, use mock module with no-op decorators and types | |
from fontTools.misc import cython | |
COMPILED = False | |
__all__ = ["%s"] | |
class %s(BasePen): | |
def __init__(self, glyphset=None): | |
BasePen.__init__(self, glyphset) | |
""" | |
% (penName, penName), | |
file=file, | |
) | |
for name, f in funcs: | |
print(" self.%s = 0" % name, file=file) | |
print( | |
""" | |
def _moveTo(self, p0): | |
self._startPoint = p0 | |
def _closePath(self): | |
p0 = self._getCurrentPoint() | |
if p0 != self._startPoint: | |
self._lineTo(self._startPoint) | |
def _endPath(self): | |
p0 = self._getCurrentPoint() | |
if p0 != self._startPoint: | |
raise OpenContourError( | |
"Glyph statistics is not defined on open contours." | |
) | |
""", | |
end="", | |
file=file, | |
) | |
for n in (1, 2, 3): | |
subs = {P[i][j]: [X, Y][j][i] for i in range(n + 1) for j in range(2)} | |
greens = [green(f, BezierCurve[n]) for name, f in funcs] | |
greens = [sp.gcd_terms(f.collect(sum(P, ()))) for f in greens] # Optimize | |
greens = [f.subs(subs) for f in greens] # Convert to p to x/y | |
defs, exprs = sp.cse( | |
greens, | |
optimizations="basic", | |
symbols=(sp.Symbol("r%d" % i) for i in count()), | |
) | |
print() | |
for name, value in defs: | |
print(" @cython.locals(%s=cython.double)" % name, file=file) | |
if n == 1: | |
print( | |
"""\ | |
@cython.locals(x0=cython.double, y0=cython.double) | |
@cython.locals(x1=cython.double, y1=cython.double) | |
def _lineTo(self, p1): | |
x0,y0 = self._getCurrentPoint() | |
x1,y1 = p1 | |
""", | |
file=file, | |
) | |
elif n == 2: | |
print( | |
"""\ | |
@cython.locals(x0=cython.double, y0=cython.double) | |
@cython.locals(x1=cython.double, y1=cython.double) | |
@cython.locals(x2=cython.double, y2=cython.double) | |
def _qCurveToOne(self, p1, p2): | |
x0,y0 = self._getCurrentPoint() | |
x1,y1 = p1 | |
x2,y2 = p2 | |
""", | |
file=file, | |
) | |
elif n == 3: | |
print( | |
"""\ | |
@cython.locals(x0=cython.double, y0=cython.double) | |
@cython.locals(x1=cython.double, y1=cython.double) | |
@cython.locals(x2=cython.double, y2=cython.double) | |
@cython.locals(x3=cython.double, y3=cython.double) | |
def _curveToOne(self, p1, p2, p3): | |
x0,y0 = self._getCurrentPoint() | |
x1,y1 = p1 | |
x2,y2 = p2 | |
x3,y3 = p3 | |
""", | |
file=file, | |
) | |
for name, value in defs: | |
print(" %s = %s" % (name, value), file=file) | |
print(file=file) | |
for name, value in zip([f[0] for f in funcs], exprs): | |
print(" self.%s += %s" % (name, value), file=file) | |
print( | |
""" | |
if __name__ == '__main__': | |
from fontTools.misc.symfont import x, y, printGreenPen | |
printGreenPen('%s', [""" | |
% penName, | |
file=file, | |
) | |
for name, f in funcs: | |
print(" ('%s', %s)," % (name, str(f)), file=file) | |
print(" ])", file=file) | |
if __name__ == "__main__": | |
pen = AreaPen() | |
pen.moveTo((100, 100)) | |
pen.lineTo((100, 200)) | |
pen.lineTo((200, 200)) | |
pen.curveTo((200, 250), (300, 300), (250, 350)) | |
pen.lineTo((200, 100)) | |
pen.closePath() | |
print(pen.value) | |