123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- 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:
- # Green theorem is not defined on open contours.
- raise OpenContourError(
- "Green theorem 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)
|