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