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