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