symfont.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. from fontTools.pens.basePen import BasePen
  2. from functools import partial
  3. from itertools import count
  4. import sympy as sp
  5. import sys
  6. n = 3 # Max Bezier degree; 3 for cubic, 2 for quadratic
  7. t, x, y = sp.symbols("t x y", real=True)
  8. c = sp.symbols("c", real=False) # Complex representation instead of x/y
  9. X = tuple(sp.symbols("x:%d" % (n + 1), real=True))
  10. Y = tuple(sp.symbols("y:%d" % (n + 1), real=True))
  11. P = tuple(zip(*(sp.symbols("p:%d[%s]" % (n + 1, w), real=True) for w in "01")))
  12. C = tuple(sp.symbols("c:%d" % (n + 1), real=False))
  13. # Cubic Bernstein basis functions
  14. BinomialCoefficient = [(1, 0)]
  15. for i in range(1, n + 1):
  16. last = BinomialCoefficient[-1]
  17. this = tuple(last[j - 1] + last[j] for j in range(len(last))) + (0,)
  18. BinomialCoefficient.append(this)
  19. BinomialCoefficient = tuple(tuple(item[:-1]) for item in BinomialCoefficient)
  20. del last, this
  21. BernsteinPolynomial = tuple(
  22. tuple(c * t**i * (1 - t) ** (n - i) for i, c in enumerate(coeffs))
  23. for n, coeffs in enumerate(BinomialCoefficient)
  24. )
  25. BezierCurve = tuple(
  26. tuple(
  27. sum(P[i][j] * bernstein for i, bernstein in enumerate(bernsteins))
  28. for j in range(2)
  29. )
  30. for n, bernsteins in enumerate(BernsteinPolynomial)
  31. )
  32. BezierCurveC = tuple(
  33. sum(C[i] * bernstein for i, bernstein in enumerate(bernsteins))
  34. for n, bernsteins in enumerate(BernsteinPolynomial)
  35. )
  36. def green(f, curveXY):
  37. f = -sp.integrate(sp.sympify(f), y)
  38. f = f.subs({x: curveXY[0], y: curveXY[1]})
  39. f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1))
  40. return f
  41. class _BezierFuncsLazy(dict):
  42. def __init__(self, symfunc):
  43. self._symfunc = symfunc
  44. self._bezfuncs = {}
  45. def __missing__(self, i):
  46. args = ["p%d" % d for d in range(i + 1)]
  47. f = green(self._symfunc, BezierCurve[i])
  48. f = sp.gcd_terms(f.collect(sum(P, ()))) # Optimize
  49. return sp.lambdify(args, f)
  50. class GreenPen(BasePen):
  51. _BezierFuncs = {}
  52. @classmethod
  53. def _getGreenBezierFuncs(celf, func):
  54. funcstr = str(func)
  55. if not funcstr in celf._BezierFuncs:
  56. celf._BezierFuncs[funcstr] = _BezierFuncsLazy(func)
  57. return celf._BezierFuncs[funcstr]
  58. def __init__(self, func, glyphset=None):
  59. BasePen.__init__(self, glyphset)
  60. self._funcs = self._getGreenBezierFuncs(func)
  61. self.value = 0
  62. def _moveTo(self, p0):
  63. self.__startPoint = p0
  64. def _closePath(self):
  65. p0 = self._getCurrentPoint()
  66. if p0 != self.__startPoint:
  67. self._lineTo(self.__startPoint)
  68. def _endPath(self):
  69. p0 = self._getCurrentPoint()
  70. if p0 != self.__startPoint:
  71. # Green theorem is not defined on open contours.
  72. raise NotImplementedError
  73. def _lineTo(self, p1):
  74. p0 = self._getCurrentPoint()
  75. self.value += self._funcs[1](p0, p1)
  76. def _qCurveToOne(self, p1, p2):
  77. p0 = self._getCurrentPoint()
  78. self.value += self._funcs[2](p0, p1, p2)
  79. def _curveToOne(self, p1, p2, p3):
  80. p0 = self._getCurrentPoint()
  81. self.value += self._funcs[3](p0, p1, p2, p3)
  82. # Sample pens.
  83. # Do not use this in real code.
  84. # Use fontTools.pens.momentsPen.MomentsPen instead.
  85. AreaPen = partial(GreenPen, func=1)
  86. MomentXPen = partial(GreenPen, func=x)
  87. MomentYPen = partial(GreenPen, func=y)
  88. MomentXXPen = partial(GreenPen, func=x * x)
  89. MomentYYPen = partial(GreenPen, func=y * y)
  90. MomentXYPen = partial(GreenPen, func=x * y)
  91. def printGreenPen(penName, funcs, file=sys.stdout, docstring=None):
  92. if docstring is not None:
  93. print('"""%s"""' % docstring)
  94. print(
  95. """from fontTools.pens.basePen import BasePen, OpenContourError
  96. try:
  97. import cython
  98. COMPILED = cython.compiled
  99. except (AttributeError, ImportError):
  100. # if cython not installed, use mock module with no-op decorators and types
  101. from fontTools.misc import cython
  102. COMPILED = False
  103. __all__ = ["%s"]
  104. class %s(BasePen):
  105. def __init__(self, glyphset=None):
  106. BasePen.__init__(self, glyphset)
  107. """
  108. % (penName, penName),
  109. file=file,
  110. )
  111. for name, f in funcs:
  112. print(" self.%s = 0" % name, file=file)
  113. print(
  114. """
  115. def _moveTo(self, p0):
  116. self.__startPoint = p0
  117. def _closePath(self):
  118. p0 = self._getCurrentPoint()
  119. if p0 != self.__startPoint:
  120. self._lineTo(self.__startPoint)
  121. def _endPath(self):
  122. p0 = self._getCurrentPoint()
  123. if p0 != self.__startPoint:
  124. # Green theorem is not defined on open contours.
  125. raise OpenContourError(
  126. "Green theorem is not defined on open contours."
  127. )
  128. """,
  129. end="",
  130. file=file,
  131. )
  132. for n in (1, 2, 3):
  133. subs = {P[i][j]: [X, Y][j][i] for i in range(n + 1) for j in range(2)}
  134. greens = [green(f, BezierCurve[n]) for name, f in funcs]
  135. greens = [sp.gcd_terms(f.collect(sum(P, ()))) for f in greens] # Optimize
  136. greens = [f.subs(subs) for f in greens] # Convert to p to x/y
  137. defs, exprs = sp.cse(
  138. greens,
  139. optimizations="basic",
  140. symbols=(sp.Symbol("r%d" % i) for i in count()),
  141. )
  142. print()
  143. for name, value in defs:
  144. print(" @cython.locals(%s=cython.double)" % name, file=file)
  145. if n == 1:
  146. print(
  147. """\
  148. @cython.locals(x0=cython.double, y0=cython.double)
  149. @cython.locals(x1=cython.double, y1=cython.double)
  150. def _lineTo(self, p1):
  151. x0,y0 = self._getCurrentPoint()
  152. x1,y1 = p1
  153. """,
  154. file=file,
  155. )
  156. elif n == 2:
  157. print(
  158. """\
  159. @cython.locals(x0=cython.double, y0=cython.double)
  160. @cython.locals(x1=cython.double, y1=cython.double)
  161. @cython.locals(x2=cython.double, y2=cython.double)
  162. def _qCurveToOne(self, p1, p2):
  163. x0,y0 = self._getCurrentPoint()
  164. x1,y1 = p1
  165. x2,y2 = p2
  166. """,
  167. file=file,
  168. )
  169. elif n == 3:
  170. print(
  171. """\
  172. @cython.locals(x0=cython.double, y0=cython.double)
  173. @cython.locals(x1=cython.double, y1=cython.double)
  174. @cython.locals(x2=cython.double, y2=cython.double)
  175. @cython.locals(x3=cython.double, y3=cython.double)
  176. def _curveToOne(self, p1, p2, p3):
  177. x0,y0 = self._getCurrentPoint()
  178. x1,y1 = p1
  179. x2,y2 = p2
  180. x3,y3 = p3
  181. """,
  182. file=file,
  183. )
  184. for name, value in defs:
  185. print(" %s = %s" % (name, value), file=file)
  186. print(file=file)
  187. for name, value in zip([f[0] for f in funcs], exprs):
  188. print(" self.%s += %s" % (name, value), file=file)
  189. print(
  190. """
  191. if __name__ == '__main__':
  192. from fontTools.misc.symfont import x, y, printGreenPen
  193. printGreenPen('%s', ["""
  194. % penName,
  195. file=file,
  196. )
  197. for name, f in funcs:
  198. print(" ('%s', %s)," % (name, str(f)), file=file)
  199. print(" ])", file=file)
  200. if __name__ == "__main__":
  201. pen = AreaPen()
  202. pen.moveTo((100, 100))
  203. pen.lineTo((100, 200))
  204. pen.lineTo((200, 200))
  205. pen.curveTo((200, 250), (300, 300), (250, 350))
  206. pen.lineTo((200, 100))
  207. pen.closePath()
  208. print(pen.value)