diff options
Diffstat (limited to 'scripts/lib/fontbuild/mix.py')
-rw-r--r-- | scripts/lib/fontbuild/mix.py | 219 |
1 files changed, 115 insertions, 104 deletions
diff --git a/scripts/lib/fontbuild/mix.py b/scripts/lib/fontbuild/mix.py index c55512a..2c741fb 100644 --- a/scripts/lib/fontbuild/mix.py +++ b/scripts/lib/fontbuild/mix.py @@ -1,6 +1,25 @@ -from FL import * +# Copyright 2015 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from numpy import array, append import copy +import json +from robofab.objects.objectsRF import RPoint +from robofab.world import OpenFont +from decomposeGlyph import decomposeGlyph + class FFont: "Font wrapper for floating point operations" @@ -9,32 +28,37 @@ class FFont: self.glyphs = {} self.hstems = [] self.vstems = [] + self.kerning = {} if isinstance(f,FFont): #self.glyphs = [g.copy() for g in f.glyphs] for key,g in f.glyphs.iteritems(): self.glyphs[key] = g.copy() self.hstems = list(f.hstems) self.vstems = list(f.vstems) + self.kerning = dict(f.kerning) elif f != None: self.copyFromFont(f) - - def copyFromFont(self,f): - for g in f.glyphs: + + def copyFromFont(self, f): + for g in f: self.glyphs[g.name] = FGlyph(g) - self.hstems = [s for s in f.stem_snap_h[0]] - self.vstems = [s for s in f.stem_snap_v[0]] - - - def copyToFont(self,f): - for g in f.glyphs: + self.hstems = [s for s in f.info.postscriptStemSnapH] + self.vstems = [s for s in f.info.postscriptStemSnapV] + self.kerning = f.kerning.asDict() + + + def copyToFont(self, f): + for g in f: try: gF = self.glyphs[g.name] gF.copyToGlyph(g) except: print "Copy to glyph failed for" + g.name - f.stem_snap_h[0] = self.hstems - f.stem_snap_v[0] = self.vstems - + f.info.postscriptStemSnapH = self.hstems + f.info.postscriptStemSnapV = self.vstems + for pair in self.kerning: + f.kerning[pair] = self.kerning[pair] + def getGlyph(self, gname): try: return self.glyphs[gname] @@ -46,7 +70,6 @@ class FFont: def addDiff(self,b,c): newFont = FFont(self) - for key,g in newFont.glyphs.iteritems(): gB = b.getGlyph(key) gC = c.getGlyph(key) @@ -54,17 +77,15 @@ class FFont: newFont.glyphs[key] = g.addDiff(gB,gC) except: print "Add diff failed for '%s'" %key - return newFont class FGlyph: "provides a temporary floating point compatible glyph data structure" def __init__(self, g=None): - self.nodes = [] + self.contours = [] self.width = 0. self.components = [] - self.kerning = [] self.anchors = [] if g != None: self.copyFromGlyph(g) @@ -76,27 +97,24 @@ class FGlyph: self.width = len(valuesX) valuesX.append(g.width) for c in g.components: - self.components.append((len(valuesX),len(valuesY))) - valuesX.append(c.scale.x) - valuesY.append(c.scale.y) - valuesX.append(c.delta.x) - valuesY.append(c.delta.y) - + self.components.append((len(valuesX), len(valuesY))) + valuesX.append(c.scale[0]) + valuesY.append(c.scale[1]) + valuesX.append(c.offset[0]) + valuesY.append(c.offset[1]) + for a in g.anchors: self.anchors.append((len(valuesX), len(valuesY))) valuesX.append(a.x) valuesY.append(a.y) - - for i in range(len(g.nodes)): - self.nodes.append([]) - for j in range (len(g.nodes[i])): - self.nodes[i].append( (len(valuesX), len(valuesY)) ) - valuesX.append(g.nodes[i][j].x) - valuesY.append(g.nodes[i][j].y) - - for k in g.kerning: - self.kerning.append(KerningPair(k)) - + + for i in range(len(g)): + self.contours.append([]) + for j in range (len(g[i].points)): + self.contours[i].append((len(valuesX), len(valuesY))) + valuesX.append(g[i].points[j].x) + valuesY.append(g[i].points[j].y) + self.dataX = array(valuesX) self.dataY = array(valuesY) @@ -104,24 +122,23 @@ class FGlyph: g.width = self._derefX(self.width) if len(g.components) == len(self.components): for i in range(len(self.components)): - g.components[i].scale.x = self._derefX( self.components[i][0] + 0) - g.components[i].scale.y = self._derefY( self.components[i][1] + 0) - g.components[i].deltas[0].x = self._derefX( self.components[i][0] + 1) - g.components[i].deltas[0].y = self._derefY( self.components[i][1] + 1) - g.kerning = [] + g.components[i].scale = (self._derefX(self.components[i][0] + 0), + self._derefY(self.components[i][1] + 0)) + g.components[i].offset = (self._derefX(self.components[i][0] + 1), + self._derefY(self.components[i][1] + 1)) if len(g.anchors) == len(self.anchors): for i in range(len(self.anchors)): g.anchors[i].x = self._derefX( self.anchors[i][0]) g.anchors[i].y = self._derefY( self.anchors[i][1]) - for k in self.kerning: - g.kerning.append(KerningPair(k)) - for i in range( len(g.nodes)) : - for j in range (len(g.nodes[i])): - g.nodes[i][j].x = self._derefX( self.nodes[i][j][0] ) - g.nodes[i][j].y = self._derefY( self.nodes[i][j][1] ) - - def isCompatible(self,g): - return len(self.dataX) == len(g.dataX) and len(self.dataY) == len(g.dataY) and len(g.nodes) == len(self.nodes) + for i in range(len(g)) : + for j in range (len(g[i].points)): + g[i].points[j].x = self._derefX(self.contours[i][j][0]) + g[i].points[j].y = self._derefY(self.contours[i][j][1]) + + def isCompatible(self, g): + return (len(self.dataX) == len(g.dataX) and + len(self.dataY) == len(g.dataY) and + len(g.contours) == len(self.contours)) def __add__(self,g): if self.isCompatible(g): @@ -172,15 +189,13 @@ class FGlyph: gF.dataX += (g.dataX - gF.dataX) * v.x gF.dataY += (g.dataY - gF.dataY) * v.y - gF.kerning = interpolateKerns(self,g,v) return gF def copy(self): ng = FGlyph() - ng.nodes = list(self.nodes) + ng.contours = list(self.contours) ng.width = self.width ng.components = list(self.components) - ng.kerning = list(self.kerning) ng.anchors = list(self.anchors) ng.dataX = self.dataX.copy() ng.dataY = self.dataY.copy() @@ -188,10 +203,10 @@ class FGlyph: return ng def _derefX(self,id): - return int(round(self.dataX[id])) + return self.dataX[id] def _derefY(self,id): - return int(round(self.dataY[id])) + return self.dataY[id] def addDiff(self,gB,gC): newGlyph = self + (gB - gC) @@ -200,22 +215,21 @@ class FGlyph: class Master: - - - def __init__(self,font=None,v=0,ifont=None, kernlist=None, overlay=None): - if isinstance(font,FFont): + + def __init__(self, font=None, v=0, kernlist=None, overlay=None, + anchorPath=None): + if isinstance(font, FFont): self.font = None self.ffont = font elif isinstance(font,str): - self.openFont(font,overlay) + self.openFont(font,overlay, anchorPath) elif isinstance(font,Mix): self.font = font else: self.font = font - self.ifont = ifont self.ffont = FFont(font) if isinstance(v,float) or isinstance(v,int): - self.v = Point(v,v) + self.v = RPoint(v, v) else: self.v = v if kernlist != None: @@ -227,37 +241,30 @@ class Master: and not k[0] == ""] #TODO implement class based kerning / external kerning file - def openFont(self, path, overlayPath=None): - fl.Open(path,True) - self.ifont = fl.ifont - for g in fl.font.glyphs: + def openFont(self, path, overlayPath=None, anchorPath=None): + self.font = OpenFont(path) + for g in self.font: size = len(g) csize = len(g.components) if (size > 0 and csize > 0): - g.Decompose() + decomposeGlyph(g) - self.ifont = fl.ifont - self.font = fl.font if overlayPath != None: - fl.Open(overlayPath,True) - ifont = self.ifont + overlayFont = OpenFont(overlayPath) font = self.font - overlayIfont = fl.ifont - overlayFont = fl.font + for overlayGlyph in overlayFont: + font.insertGlyph(overlayGlyph) + + # work around a bug with vfb2ufo in which anchors are dropped from + # glyphs containing components and no contours. "anchorPath" should + # point to the output of src/v2/get_dropped_anchors.py + if anchorPath: + anchorData = json.load(open(anchorPath)) + for glyphName, anchors in anchorData.items(): + glyph = self.font[glyphName] + for name, (x, y) in anchors.items(): + glyph.appendAnchor(str(name), (x, y)) - for overlayGlyph in overlayFont.glyphs: - glyphIndex = font.FindGlyph(overlayGlyph.name) - if glyphIndex != -1: - oldGlyph = Glyph(font.glyphs[glyphIndex]) - kernlist = [KerningPair(k) for k in oldGlyph.kerning] - font.glyphs[glyphIndex] = Glyph(overlayGlyph) - font.glyphs[glyphIndex].kerning = kernlist - if 0 == overlayGlyph: - font.glyphs[glyphIndex].width = 0 - else: - font.glyphs.append(overlayGlyph) - fl.UpdateFont(ifont) - fl.Close(overlayIfont) self.ffont = FFont(self.font) @@ -265,7 +272,7 @@ class Mix: def __init__(self,masters,v): self.masters = masters if isinstance(v,float) or isinstance(v,int): - self.v = Point(v,v) + self.v = RPoint(v,v) else: self.v = v @@ -283,21 +290,20 @@ class Mix: ffont = FFont(self.masters[0].ffont) for key,g in ffont.glyphs.iteritems(): ffont.glyphs[key] = self.mixGlyphs(key) + ffont.kerning = self.mixKerns() return ffont def generateFont(self, baseFont): - newFont = Font(baseFont) + newFont = baseFont.copy() #self.mixStems(newFont) todo _ fix stems code - for g in newFont.glyphs: + for g in newFont: gF = self.mixGlyphs(g.name) if gF == None: g.mark = True else: - # FIX THIS #print gF.name, g.name, len(gF.nodes),len(g.nodes),len(gF.components),len(g.components) - try: - gF.copyToGlyph(g) - except: - "Nodes incompatible" + gF.copyToGlyph(g) + newFont.kerning.clear() + newFont.kerning.update(self.mixKerns() or {}) return newFont def mixGlyphs(self,gname): @@ -309,6 +315,17 @@ class Mix: if gA != None: return gA.copy() + def getKerning(self, master): + if isinstance(master.font, Mix): + return master.font.mixKerns() + return master.ffont.kerning + + def mixKerns(self): + masters = self.masters + kA, kB = self.getKerning(masters[0]), self.getKerning(masters[-1]) + return interpolateKerns(kA, kB, self.v) + + def narrowFLGlyph(g, gThin, factor=.75): gF = FGlyph(g) if not isinstance(gThin,FGlyph): @@ -327,19 +344,13 @@ def interpolate(a,b,v,e=0): le = a+(b-a)*v # linear easing return le + (qe-le) * e -def interpolateKerns(gA,gB,v): - kerns = [] - for kA in gA.kerning: - key = kA.key - matchedKern = None - for kB in gA.kerning: - if key == kB.key: - matchedKern = kB - break +def interpolateKerns(kA, kB, v): + kerns = {} + for pair in kA.keys(): + matchedKern = kB.get(pair) # if matchedkern == None: # matchedkern = Kern(kA) # matchedkern.value = 0 if matchedKern != None: - kernValue = interpolate(kA.value, matchedKern.value, v.x) - kerns.append(KerningPair(kA.key,kernValue)) - return kerns
\ No newline at end of file + kerns[pair] = interpolate(kA[pair], matchedKern, v.x) + return kerns |