symfont.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. except (AttributeError, ImportError):
  99. # if cython not installed, use mock module with no-op decorators and types
  100. from fontTools.misc import cython
  101. COMPILED = cython.compiled
  102. __all__ = ["%s"]
  103. class %s(BasePen):
  104. def __init__(self, glyphset=None):
  105. BasePen.__init__(self, glyphset)
  106. """
  107. % (penName, penName),
  108. file=file,
  109. )
  110. for name, f in funcs:
  111. print(" self.%s = 0" % name, file=file)
  112. print(
  113. """
  114. def _moveTo(self, p0):
  115. self._startPoint = p0
  116. def _closePath(self):
  117. p0 = self._getCurrentPoint()
  118. if p0 != self._startPoint:
  119. self._lineTo(self._startPoint)
  120. def _endPath(self):
  121. p0 = self._getCurrentPoint()
  122. if p0 != self._startPoint:
  123. raise OpenContourError(
  124. "Glyph statistics is not defined on open contours."
  125. )
  126. """,
  127. end="",
  128. file=file,
  129. )
  130. for n in (1, 2, 3):
  131. subs = {P[i][j]: [X, Y][j][i] for i in range(n + 1) for j in range(2)}
  132. greens = [green(f, BezierCurve[n]) for name, f in funcs]
  133. greens = [sp.gcd_terms(f.collect(sum(P, ()))) for f in greens] # Optimize
  134. greens = [f.subs(subs) for f in greens] # Convert to p to x/y
  135. defs, exprs = sp.cse(
  136. greens,
  137. optimizations="basic",
  138. symbols=(sp.Symbol("r%d" % i) for i in count()),
  139. )
  140. print()
  141. for name, value in defs:
  142. print(" @cython.locals(%s=cython.double)" % name, file=file)
  143. if n == 1:
  144. print(
  145. """\
  146. @cython.locals(x0=cython.double, y0=cython.double)
  147. @cython.locals(x1=cython.double, y1=cython.double)
  148. def _lineTo(self, p1):
  149. x0,y0 = self._getCurrentPoint()
  150. x1,y1 = p1
  151. """,
  152. file=file,
  153. )
  154. elif n == 2:
  155. print(
  156. """\
  157. @cython.locals(x0=cython.double, y0=cython.double)
  158. @cython.locals(x1=cython.double, y1=cython.double)
  159. @cython.locals(x2=cython.double, y2=cython.double)
  160. def _qCurveToOne(self, p1, p2):
  161. x0,y0 = self._getCurrentPoint()
  162. x1,y1 = p1
  163. x2,y2 = p2
  164. """,
  165. file=file,
  166. )
  167. elif n == 3:
  168. print(
  169. """\
  170. @cython.locals(x0=cython.double, y0=cython.double)
  171. @cython.locals(x1=cython.double, y1=cython.double)
  172. @cython.locals(x2=cython.double, y2=cython.double)
  173. @cython.locals(x3=cython.double, y3=cython.double)
  174. def _curveToOne(self, p1, p2, p3):
  175. x0,y0 = self._getCurrentPoint()
  176. x1,y1 = p1
  177. x2,y2 = p2
  178. x3,y3 = p3
  179. """,
  180. file=file,
  181. )
  182. for name, value in defs:
  183. print(" %s = %s" % (name, value), file=file)
  184. print(file=file)
  185. for name, value in zip([f[0] for f in funcs], exprs):
  186. print(" self.%s += %s" % (name, value), file=file)
  187. print(
  188. """
  189. if __name__ == '__main__':
  190. from fontTools.misc.symfont import x, y, printGreenPen
  191. printGreenPen('%s', ["""
  192. % penName,
  193. file=file,
  194. )
  195. for name, f in funcs:
  196. print(" ('%s', %s)," % (name, str(f)), file=file)
  197. print(" ])", file=file)
  198. if __name__ == "__main__":
  199. pen = AreaPen()
  200. pen.moveTo((100, 100))
  201. pen.lineTo((100, 200))
  202. pen.lineTo((200, 200))
  203. pen.curveTo((200, 250), (300, 300), (250, 350))
  204. pen.lineTo((200, 100))
  205. pen.closePath()
  206. print(pen.value)