summaryrefslogtreecommitdiff
path: root/scripts/lib/fontbuild/mix.py
diff options
context:
space:
mode:
authorChristian Robertson <robertsonc@google.com>2014-05-19 16:09:20 -0700
committerChristian Robertson <robertsonc@google.com>2014-05-19 16:09:20 -0700
commit7051939fa74b5f8f453be605f5dc3b4c23e1d1d9 (patch)
tree65cb31b34a64b2b07f9f6fd1bcb58d9a4945a92d /scripts/lib/fontbuild/mix.py
Importing Roboto 2.0
Diffstat (limited to 'scripts/lib/fontbuild/mix.py')
-rwxr-xr-xscripts/lib/fontbuild/mix.py334
1 files changed, 334 insertions, 0 deletions
diff --git a/scripts/lib/fontbuild/mix.py b/scripts/lib/fontbuild/mix.py
new file mode 100755
index 0000000..86bf4ed
--- /dev/null
+++ b/scripts/lib/fontbuild/mix.py
@@ -0,0 +1,334 @@
+from FL import *
+from numpy import array, append
+import copy
+
+class FFont:
+ "Font wrapper for floating point operations"
+
+ def __init__(self,f=None):
+ self.glyphs = {}
+ self.hstems = []
+ self.vstems = []
+ 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)
+ elif f != None:
+ self.copyFromFont(f)
+
+ def copyFromFont(self,f):
+ for g in f.glyphs:
+ 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:
+ 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
+
+ def getGlyph(self, gname):
+ try:
+ return self.glyphs[gname]
+ except:
+ return None
+
+ def setGlyph(self, gname, glyph):
+ self.glyphs[gname] = glyph
+
+ def addDiff(self,b,c):
+ newFont = FFont(self)
+ for key,g in newFont.glyphs.iteritems():
+ gB = b.getGlyph(key)
+ gC = c.getGlyph(key)
+ try:
+ 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.width = 0.
+ self.components = []
+ self.kerning = []
+ self.anchors = []
+ if g != None:
+ self.copyFromGlyph(g)
+
+ def copyFromGlyph(self,g):
+ self.name = g.name
+ valuesX = []
+ valuesY = []
+ 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)
+
+ 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))
+
+ self.dataX = array(valuesX)
+ self.dataY = array(valuesY)
+
+ def copyToGlyph(self,g):
+ 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 = []
+ 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)
+
+ def __add__(self,g):
+ if self.isCompatible(g):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX + g.dataX
+ newGlyph.dataY = self.dataY + g.dataY
+ return newGlyph
+ else:
+ print "Add failed for '%s'" %(self.name)
+ raise Exception
+
+ def __sub__(self,g):
+ if self.isCompatible(g):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX - g.dataX
+ newGlyph.dataY = self.dataY - g.dataY
+ return newGlyph
+ else:
+ print "Subtract failed for '%s'" %(self.name)
+ raise Exception
+
+ def __mul__(self,scalar):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX * scalar
+ newGlyph.dataY = self.dataY * scalar
+ return newGlyph
+
+ def scaleX(self,scalar):
+ newGlyph = self.copy()
+ if len(self.dataX) > 0:
+ newGlyph.dataX = self.dataX * scalar
+ for i in range(len(newGlyph.components)):
+ newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]]
+ return newGlyph
+
+ def shift(self,ammount):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX + ammount
+ for i in range(len(newGlyph.components)):
+ newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]]
+ return newGlyph
+
+ def interp(self, g, v):
+ gF = self.copy()
+ if not self.isCompatible(g):
+ print "Interpolate failed for '%s'; outlines incompatible" %(self.name)
+ raise Exception
+
+ 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.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()
+ ng.name = self.name
+ return ng
+
+ def _derefX(self,id):
+ return int(round(self.dataX[id]))
+
+ def _derefY(self,id):
+ return int(round(self.dataY[id]))
+
+ def addDiff(self,gB,gC):
+ newGlyph = self + (gB - gC)
+ return newGlyph
+
+
+
+class Master:
+
+
+ def __init__(self,font=None,v=0,ifont=None, kernlist=None, overlay=None):
+ if isinstance(font,FFont):
+ self.font = None
+ self.ffont = font
+ elif isinstance(font,str):
+ self.openFont(font,overlay)
+ 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)
+ else:
+ self.v = v
+ if kernlist != None:
+ kerns = [i.strip().split() for i in open(kernlist).readlines()]
+
+ self.kernlist = [{'left':k[0], 'right':k[1], 'value': k[2]}
+ for k in kerns
+ if not k[0].startswith("#")
+ 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
+ self.font = fl.font
+ if overlayPath != None:
+ fl.Open(overlayPath,True)
+ ifont = self.ifont
+ font = self.font
+ overlayIfont = fl.ifont
+ overlayFont = fl.font
+
+ 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
+ else:
+ font.glyphs.append(overlayGlyph)
+ fl.UpdateFont(ifont)
+ fl.Close(overlayIfont)
+ self.ffont = FFont(self.font)
+
+
+class Mix:
+ def __init__(self,masters,v):
+ self.masters = masters
+ if isinstance(v,float) or isinstance(v,int):
+ self.v = Point(v,v)
+ else:
+ self.v = v
+
+ def getFGlyph(self, master, gname):
+ if isinstance(master.font, Mix):
+ return font.mixGlyphs(gname)
+ return master.ffont.getGlyph(gname)
+
+ def getGlyphMasters(self,gname):
+ masters = self.masters
+ if len(masters) <= 2:
+ return self.getFGlyph(masters[0], gname), self.getFGlyph(masters[-1], gname)
+
+ def generateFFont(self):
+ ffont = FFont(self.masters[0].ffont)
+ for key,g in ffont.glyphs.iteritems():
+ ffont.glyphs[key] = self.mixGlyphs(key)
+ return ffont
+
+ def generateFont(self, baseFont):
+ newFont = Font(baseFont)
+ #self.mixStems(newFont) todo _ fix stems code
+ for g in newFont.glyphs:
+ 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"
+ return newFont
+
+ def mixGlyphs(self,gname):
+ gA,gB = self.getGlyphMasters(gname)
+ try:
+ return gA.interp(gB,self.v)
+ except:
+ print "mixglyph failed for %s" %(gname)
+ if gA != None:
+ return gA.copy()
+
+def narrowFLGlyph(g, gThin, factor=.75):
+ gF = FGlyph(g)
+ if not isinstance(gThin,FGlyph):
+ gThin = FGlyph(gThin)
+ gCondensed = gThin.scaleX(factor)
+ try:
+ gNarrow = gF + (gCondensed - gThin)
+ gNarrow.copyToGlyph(g)
+ except:
+ print "No dice for: " + g.name
+
+def interpolate(a,b,v,e=0):
+ if e == 0:
+ return a+(b-a)*v
+ qe = (b-a)*v*v*v + a #cubic easing
+ 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
+ # 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