123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- # 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"
-
- def __init__(self,f=None):
- 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:
- self.glyphs[g.name] = FGlyph(g)
- self.hstems = [s for s in f.info.postscriptStemSnapH]
- self.vstems = [s for s in f.info.postscriptStemSnapV]
- self.kerning = f.kerning.asDict()
- def getGlyph(self, gname):
- try:
- return self.glyphs[gname]
- except:
- return None
-
- 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.contours = []
- self.width = 0.
- self.components = []
- 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[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)):
- 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, dtype=float)
- self.dataY = array(valuesY, dtype=float)
-
- 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 = (self._derefX(self.components[i][0] + 0, asInt=False),
- self._derefY(self.components[i][1] + 0, asInt=False))
- 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 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):
- 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 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
- return gF
-
- def copy(self):
- ng = FGlyph()
- ng.contours = list(self.contours)
- ng.width = self.width
- ng.components = list(self.components)
- 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, asInt=True):
- val = self.dataX[id]
- return int(round(val)) if asInt else val
-
- def _derefY(self,id, asInt=True):
- val = self.dataY[id]
- return int(round(val)) if asInt else val
-
- def addDiff(self,gB,gC):
- newGlyph = self + (gB - gC)
- return newGlyph
-
-
- class Master:
- def __init__(self, font=None, v=0, 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.ffont = FFont(font)
- if isinstance(v,float) or isinstance(v,int):
- self.v = RPoint(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):
- self.font = OpenFont(path)
- for g in self.font:
- size = len(g)
- csize = len(g.components)
- if (size > 0 and csize > 0):
- decomposeGlyph(self.font, g.name)
- if overlayPath != None:
- overlayFont = OpenFont(overlayPath)
- font = self.font
- for overlayGlyph in overlayFont:
- font.insertGlyph(overlayGlyph)
- 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 = RPoint(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)
- ffont.kerning = self.mixKerns()
- return ffont
-
- def generateFont(self, baseFont):
- newFont = baseFont.copy()
- for g in newFont:
- gF = self.mixGlyphs(g.name)
- if gF == None:
- g.mark = True
- else:
- gF.copyToGlyph(g)
- newFont.kerning.clear()
- newFont.kerning.update(self.mixKerns() or {})
- 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 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 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(kA, kB, v):
- kerns = {}
- for pair, val in kA.items():
- kerns[pair] = interpolate(val, kB.get(pair, 0), v.x)
- for pair, val in kB.items():
- lerped_val = interpolate(val, kA.get(pair, 0), 1 - v.x)
- if pair in kerns:
- assert abs(kerns[pair] - lerped_val) < 1e-6
- else:
- kerns[pair] = lerped_val
- return kerns
|