Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

245 lines
6.8 KiB
Python
Raw Normal View History

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)
2017-02-19 13:55:46 -06:00
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")))
2017-02-19 13:55:46 -06:00
C = tuple(sp.symbols("c:%d" % (n + 1), real=False))
2016-03-13 11:24:35 -07:00
# 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)
2017-02-19 13:55:46 -06:00
del last, this
2016-03-13 11:24:35 -07:00
BernsteinPolynomial = tuple(
tuple(c * t**i * (1 - t) ** (n - i) for i, c in enumerate(coeffs))
for n, coeffs in enumerate(BinomialCoefficient)
2022-12-13 11:26:36 +00:00
)
BezierCurve = tuple(
2016-04-26 19:16:46 -05:00
tuple(
sum(P[i][j] * bernstein for i, bernstein in enumerate(bernsteins))
for j in range(2)
2022-12-13 11:26:36 +00:00
)
2016-04-26 19:16:46 -05:00
for n, bernsteins in enumerate(BernsteinPolynomial)
2022-12-13 11:26:36 +00:00
)
2017-02-19 13:55:46 -06:00
BezierCurveC = tuple(
sum(C[i] * bernstein for i, bernstein in enumerate(bernsteins))
for n, bernsteins in enumerate(BernsteinPolynomial)
2022-12-13 11:26:36 +00:00
)
2017-02-20 17:48:09 -06:00
def green(f, curveXY):
f = -sp.integrate(sp.sympify(f), y)
2017-02-19 13:55:46 -06:00
f = f.subs({x: curveXY[0], y: curveXY[1]})
f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1))
return f
2017-02-20 19:53:34 -06:00
2017-02-20 19:41:58 -06:00
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:
2017-02-20 19:41:58 -06:00
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)
2022-12-13 11:26:36 +00:00
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
except (AttributeError, ImportError):
# if cython not installed, use mock module with no-op decorators and types
from fontTools.misc import cython
2024-11-27 18:15:19 -07:00
COMPILED = cython.compiled
__all__ = ["%s"]
class %s(BasePen):
def __init__(self, glyphset=None):
BasePen.__init__(self, glyphset)
2022-12-13 11:26:36 +00:00
"""
% (penName, penName),
file=file,
)
for name, f in funcs:
2017-02-20 08:54:53 -06:00
print(" self.%s = 0" % name, file=file)
print(
2022-12-13 11:26:36 +00:00
"""
def _moveTo(self, p0):
self._startPoint = p0
def _closePath(self):
p0 = self._getCurrentPoint()
if p0 != self._startPoint:
self._lineTo(self._startPoint)
2017-02-20 17:28:19 -06:00
def _endPath(self):
p0 = self._getCurrentPoint()
if p0 != self._startPoint:
raise OpenContourError(
"Glyph statistics is not defined on open contours."
)
2022-12-13 11:26:36 +00:00
""",
2017-02-20 17:28:19 -06:00
end="",
file=file,
)
2022-12-13 11:26:36 +00:00
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()),
)
2022-12-13 11:26:36 +00:00
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
2022-12-13 11:26:36 +00:00
""",
2017-02-20 08:54:53 -06:00
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
2022-12-13 11:26:36 +00:00
""",
2017-02-20 08:54:53 -06:00
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
2022-12-13 11:26:36 +00:00
""",
2017-02-20 08:54:53 -06:00
file=file,
)
for name, value in defs:
2017-02-20 08:54:53 -06:00
print(" %s = %s" % (name, value), file=file)
2022-12-13 11:26:36 +00:00
2017-02-20 08:54:53 -06:00
print(file=file)
for name, value in zip([f[0] for f in funcs], exprs):
2017-02-20 08:54:53 -06:00
print(" self.%s += %s" % (name, value), file=file)
2022-12-13 11:26:36 +00:00
print(
2022-12-13 11:26:36 +00:00
"""
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)
2022-12-13 11:26:36 +00:00
2017-02-20 14:25:08 -06:00
if __name__ == "__main__":
pen = AreaPen()
pen.moveTo((100, 100))
pen.lineTo((100, 200))
pen.lineTo((200, 200))
2017-02-20 17:20:44 -06:00
pen.curveTo((200, 250), (300, 300), (250, 350))
2017-02-20 14:25:08 -06:00
pen.lineTo((200, 100))
pen.closePath()
print(pen.value)