248 lines
6.8 KiB
Python
248 lines
6.8 KiB
Python
|
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 = {}
|
||
|
|
||
|
@classmethod
|
||
|
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)
|