1# Copyright 2015 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15
16from numpy import array, append
17import copy
18import json
19from robofab.objects.objectsRF import RPoint
20from robofab.world import OpenFont
21from decomposeGlyph import decomposeGlyph
22
23
24class FFont:
25    "Font wrapper for floating point operations"
26
27    def __init__(self,f=None):
28        self.glyphs = {}
29        self.hstems = []
30        self.vstems = []
31        self.kerning = {}
32        if isinstance(f,FFont):
33            #self.glyphs = [g.copy() for g in f.glyphs]
34            for key,g in f.glyphs.iteritems():
35                self.glyphs[key] = g.copy()
36            self.hstems = list(f.hstems)
37            self.vstems = list(f.vstems)
38            self.kerning = dict(f.kerning)
39        elif f != None:
40            self.copyFromFont(f)
41
42    def copyFromFont(self, f):
43        for g in f:
44            self.glyphs[g.name] = FGlyph(g)
45        self.hstems = [s for s in f.info.postscriptStemSnapH]
46        self.vstems = [s for s in f.info.postscriptStemSnapV]
47        self.kerning = f.kerning.asDict()
48
49
50    def copyToFont(self, f):
51        for g in f:
52            try:
53                gF = self.glyphs[g.name]
54                gF.copyToGlyph(g)
55            except:
56                print "Copy to glyph failed for" + g.name
57        f.info.postscriptStemSnapH = self.hstems
58        f.info.postscriptStemSnapV = self.vstems
59        for pair in self.kerning:
60            f.kerning[pair] = self.kerning[pair]
61
62    def getGlyph(self, gname):
63        try:
64            return self.glyphs[gname]
65        except:
66            return None
67
68    def setGlyph(self, gname, glyph):
69        self.glyphs[gname] = glyph
70
71    def addDiff(self,b,c):
72        newFont = FFont(self)
73        for key,g in newFont.glyphs.iteritems():
74            gB = b.getGlyph(key)
75            gC = c.getGlyph(key)
76            try:
77                newFont.glyphs[key] = g.addDiff(gB,gC)
78            except:
79                print "Add diff failed for '%s'" %key
80        return newFont
81
82class FGlyph:
83    "provides a temporary floating point compatible glyph data structure"
84
85    def __init__(self, g=None):
86        self.contours = []
87        self.width = 0.
88        self.components = []
89        self.anchors = []
90        if g != None:
91            self.copyFromGlyph(g)
92
93    def copyFromGlyph(self,g):
94        self.name = g.name
95        valuesX = []
96        valuesY = []
97        self.width = len(valuesX)
98        valuesX.append(g.width)
99        for c in g.components:
100            self.components.append((len(valuesX), len(valuesY)))
101            valuesX.append(c.scale[0])
102            valuesY.append(c.scale[1])
103            valuesX.append(c.offset[0])
104            valuesY.append(c.offset[1])
105
106        for a in g.anchors:
107            self.anchors.append((len(valuesX), len(valuesY)))
108            valuesX.append(a.x)
109            valuesY.append(a.y)
110
111        for i in range(len(g)):
112            self.contours.append([])
113            for j in range (len(g[i].points)):
114                self.contours[i].append((len(valuesX), len(valuesY)))
115                valuesX.append(g[i].points[j].x)
116                valuesY.append(g[i].points[j].y)
117
118        self.dataX = array(valuesX, dtype=float)
119        self.dataY = array(valuesY, dtype=float)
120
121    def copyToGlyph(self,g):
122        g.width = self._derefX(self.width)
123        if len(g.components) == len(self.components):
124            for i in range(len(self.components)):
125                g.components[i].scale = (self._derefX(self.components[i][0] + 0, asInt=False),
126                                         self._derefY(self.components[i][1] + 0, asInt=False))
127                g.components[i].offset = (self._derefX(self.components[i][0] + 1),
128                                          self._derefY(self.components[i][1] + 1))
129        if len(g.anchors) == len(self.anchors):
130            for i in range(len(self.anchors)):
131                g.anchors[i].x = self._derefX( self.anchors[i][0])
132                g.anchors[i].y = self._derefY( self.anchors[i][1])
133        for i in range(len(g)) :
134            for j in range (len(g[i].points)):
135                g[i].points[j].x = self._derefX(self.contours[i][j][0])
136                g[i].points[j].y = self._derefY(self.contours[i][j][1])
137
138    def isCompatible(self, g):
139        return (len(self.dataX) == len(g.dataX) and
140                len(self.dataY) == len(g.dataY) and
141                len(g.contours) == len(self.contours))
142
143    def __add__(self,g):
144        if self.isCompatible(g):
145            newGlyph = self.copy()
146            newGlyph.dataX = self.dataX + g.dataX
147            newGlyph.dataY = self.dataY + g.dataY
148            return newGlyph
149        else:
150            print "Add failed for '%s'" %(self.name)
151            raise Exception
152
153    def __sub__(self,g):
154        if self.isCompatible(g):
155            newGlyph = self.copy()
156            newGlyph.dataX = self.dataX - g.dataX
157            newGlyph.dataY = self.dataY - g.dataY
158            return newGlyph
159        else:
160            print "Subtract failed for '%s'" %(self.name)
161            raise Exception
162
163    def __mul__(self,scalar):
164        newGlyph = self.copy()
165        newGlyph.dataX = self.dataX * scalar
166        newGlyph.dataY = self.dataY * scalar
167        return newGlyph
168
169    def scaleX(self,scalar):
170        newGlyph = self.copy()
171        if len(self.dataX) > 0:
172            newGlyph.dataX = self.dataX * scalar
173            for i in range(len(newGlyph.components)):
174                newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]]
175        return newGlyph
176
177    def shift(self,ammount):
178        newGlyph = self.copy()
179        newGlyph.dataX = self.dataX + ammount
180        for i in range(len(newGlyph.components)):
181            newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]]
182        return newGlyph
183
184    def interp(self, g, v):
185        gF = self.copy()
186        if not self.isCompatible(g):
187            print "Interpolate failed for '%s'; outlines incompatible" %(self.name)
188            raise Exception
189
190        gF.dataX += (g.dataX - gF.dataX) * v.x
191        gF.dataY += (g.dataY - gF.dataY) * v.y
192        return gF
193
194    def copy(self):
195        ng = FGlyph()
196        ng.contours = list(self.contours)
197        ng.width = self.width
198        ng.components = list(self.components)
199        ng.anchors = list(self.anchors)
200        ng.dataX = self.dataX.copy()
201        ng.dataY = self.dataY.copy()
202        ng.name = self.name
203        return ng
204
205    def _derefX(self,id, asInt=True):
206        val = self.dataX[id]
207        return int(round(val)) if asInt else val
208
209    def _derefY(self,id, asInt=True):
210        val = self.dataY[id]
211        return int(round(val)) if asInt else val
212
213    def addDiff(self,gB,gC):
214        newGlyph = self + (gB - gC)
215        return newGlyph
216
217
218
219class Master:
220
221    def __init__(self, font=None, v=0, kernlist=None, overlay=None):
222        if isinstance(font, FFont):
223            self.font = None
224            self.ffont = font
225        elif isinstance(font,str):
226            self.openFont(font,overlay)
227        elif isinstance(font,Mix):
228            self.font = font
229        else:
230            self.font = font
231            self.ffont = FFont(font)
232        if isinstance(v,float) or isinstance(v,int):
233            self.v = RPoint(v, v)
234        else:
235            self.v = v
236        if kernlist != None:
237            kerns = [i.strip().split() for i in open(kernlist).readlines()]
238
239            self.kernlist = [{'left':k[0], 'right':k[1], 'value': k[2]}
240                            for k in kerns
241                            if not k[0].startswith("#")
242                            and not k[0] == ""]
243            #TODO implement class based kerning / external kerning file
244
245    def openFont(self, path, overlayPath=None):
246        self.font = OpenFont(path)
247        for g in self.font:
248          size = len(g)
249          csize = len(g.components)
250          if (size > 0 and csize > 0):
251            decomposeGlyph(self.font, g.name)
252
253        if overlayPath != None:
254            overlayFont = OpenFont(overlayPath)
255            font = self.font
256            for overlayGlyph in overlayFont:
257                font.insertGlyph(overlayGlyph)
258
259        self.ffont = FFont(self.font)
260
261
262class Mix:
263    def __init__(self,masters,v):
264        self.masters = masters
265        if isinstance(v,float) or isinstance(v,int):
266            self.v = RPoint(v,v)
267        else:
268            self.v = v
269
270    def getFGlyph(self, master, gname):
271        if isinstance(master.font, Mix):
272            return font.mixGlyphs(gname)
273        return master.ffont.getGlyph(gname)
274
275    def getGlyphMasters(self,gname):
276        masters = self.masters
277        if len(masters) <= 2:
278            return self.getFGlyph(masters[0], gname), self.getFGlyph(masters[-1], gname)
279
280    def generateFFont(self):
281        ffont = FFont(self.masters[0].ffont)
282        for key,g in ffont.glyphs.iteritems():
283            ffont.glyphs[key] = self.mixGlyphs(key)
284        ffont.kerning = self.mixKerns()
285        return ffont
286
287    def generateFont(self, baseFont):
288        newFont = baseFont.copy()
289        #self.mixStems(newFont)  todo _ fix stems code
290        for g in newFont:
291            gF = self.mixGlyphs(g.name)
292            if gF == None:
293                g.mark = True
294            else:
295                gF.copyToGlyph(g)
296        newFont.kerning.clear()
297        newFont.kerning.update(self.mixKerns() or {})
298        return newFont
299
300    def mixGlyphs(self,gname):
301        gA,gB = self.getGlyphMasters(gname)
302        try:
303            return gA.interp(gB,self.v)
304        except:
305            print "mixglyph failed for %s" %(gname)
306            if gA != None:
307                return gA.copy()
308
309    def getKerning(self, master):
310        if isinstance(master.font, Mix):
311            return master.font.mixKerns()
312        return master.ffont.kerning
313
314    def mixKerns(self):
315        masters = self.masters
316        kA, kB = self.getKerning(masters[0]), self.getKerning(masters[-1])
317        return interpolateKerns(kA, kB, self.v)
318
319
320def narrowFLGlyph(g, gThin, factor=.75):
321    gF = FGlyph(g)
322    if not isinstance(gThin,FGlyph):
323        gThin = FGlyph(gThin)
324    gCondensed = gThin.scaleX(factor)
325    try:
326        gNarrow = gF + (gCondensed - gThin)
327        gNarrow.copyToGlyph(g)
328    except:
329        print "No dice for: " + g.name
330
331def interpolate(a,b,v,e=0):
332    if e == 0:
333        return a+(b-a)*v
334    qe = (b-a)*v*v*v + a   #cubic easing
335    le = a+(b-a)*v   # linear easing
336    return le + (qe-le) * e
337
338def interpolateKerns(kA, kB, v):
339    kerns = {}
340    for pair, val in kA.items():
341        kerns[pair] = interpolate(val, kB.get(pair, 0), v.x)
342    for pair, val in kB.items():
343        lerped_val = interpolate(val, kA.get(pair, 0), 1 - v.x)
344        if pair in kerns:
345            assert abs(kerns[pair] - lerped_val) < 1e-6
346        else:
347            kerns[pair] = lerped_val
348    return kerns
349