diff options
author | Christian Robertson <robertsonc@google.com> | 2014-05-19 16:09:20 -0700 |
---|---|---|
committer | Christian Robertson <robertsonc@google.com> | 2014-05-19 16:09:20 -0700 |
commit | 7051939fa74b5f8f453be605f5dc3b4c23e1d1d9 (patch) | |
tree | 65cb31b34a64b2b07f9f6fd1bcb58d9a4945a92d /scripts/lib |
Importing Roboto 2.0
Diffstat (limited to 'scripts/lib')
-rwxr-xr-x | scripts/lib/fontbuild/Build.py | 227 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/__init__.py | 6 | ||||
-rw-r--r-- | scripts/lib/fontbuild/alignpoints.py | 148 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/anchors.py | 44 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/convertCurves.py | 90 | ||||
-rw-r--r-- | scripts/lib/fontbuild/curveFitPen.py | 402 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/generateGlyph.py | 48 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/instanceNames.py | 184 | ||||
-rw-r--r-- | scripts/lib/fontbuild/italics.py | 267 | ||||
-rw-r--r-- | scripts/lib/fontbuild/italics2.py | 148 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/kerning.py | 26 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/mitreGlyph.py | 121 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/mix.py | 334 |
13 files changed, 2045 insertions, 0 deletions
diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py new file mode 100755 index 0000000..d10cc9d --- /dev/null +++ b/scripts/lib/fontbuild/Build.py @@ -0,0 +1,227 @@ +from FL import * +from fontbuild.mix import Mix,Master,narrowFLGlyph +from fontbuild.instanceNames import setNames +from fontbuild.italics import italicizeGlyph +from fontbuild.convertCurves import glyphCurvesToQuadratic +from fontbuild.mitreGlyph import mitreGlyph +from fontbuild.generateGlyph import generateGlyph +from fontTools.misc.transform import Transform +from fontbuild.kerning import generateFLKernClassesFromOTString +import ConfigParser +import os + + +class FontProject: + + def __init__(self, basefont, basedir, configfile, thinfont = None): + self.basefont = basefont + self.thinfont = thinfont + self.basedir = basedir + self.config = ConfigParser.RawConfigParser() + self.configfile = self.basedir+"/"+configfile + self.config.read(self.configfile) + + diacriticList = open(self.basedir + "/" + self.config.get("res","diacriticfile")).readlines() + self.diacriticList = [line.strip() for line in diacriticList if not line.startswith("#")] + self.ot_classes = open(self.basedir + "/" + self.config.get("res","ot_classesfile")).read() + self.ot_kerningclasses = open(self.basedir + "/" + self.config.get("res","ot_kerningclassesfile")).read() + self.ot_features = open(self.basedir + "/" + self.config.get("res","ot_featuresfile")).read() + + self.builddir = "out" + self.decompose = self.config.get("glyphs","decompose").split() + self.predecompose = self.config.get("glyphs","predecompose").split() + self.lessItalic = self.config.get("glyphs","lessitalic").split() + self.deleteList = self.config.get("glyphs","delete").split() + self.buildnumber = self.loadBuildNumber() + + + def loadBuildNumber(self): + versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+") + buildnumber = int(versionFile.read().strip()) + buildnumber = "%05d" %(int(buildnumber) + 1) + print "BuildNumber: %s" %(buildnumber) + versionFile.close() + return buildnumber + + def incrementBuildNumber(self): + if len(self.buildnumber) > 0: + versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+") + versionFile.seek(0) + versionFile.write(self.buildnumber) + versionFile.truncate() + versionFile.close() + else: + raise Exception("Empty build number") + + + def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185, kern=True): + + n = names.split("/") + log("---------------------\n%s %s\n----------------------" %(n[0],n[1])) + log(">> Mixing masters") + if isinstance( mix, Mix): + f = mix.generateFont(self.basefont) + else: + f = Font(mix) + fl.Add(f) + index = fl.ifont + fl.CallCommand(33239) # Sort glyphs by unicode + if italic == True: + log(">> Italicizing") + fl.UpdateFont(fl.ifont) + tweakAmmount = .085 + narrowAmmount = .93 + if names.find("Thin") != -1: + tweakAmmount = .05 + if names.find("Condensed") != -1: + narrowAmmount = .96 + i = 0 + for g in f.glyphs: + i += 1 + if i % 10 == 0: print g.name + + # if i < 24: + # continue + # if i > 86: + # for i,g in enumerate(fl.font.glyphs): + # fl.UpdateGlyph(i) + # # break + # assert False + + # print g.name + # if self.thinfont != None: + # narrowFLGlyph(g,self.thinfont.getGlyph(g.name),factor=narrowAmmount) + + if g.name != "eight" or g.name != "Q": + g.RemoveOverlap() + + # not sure why FontLab sometimes refuses, seems to work if called twice + + if (g.name in self.lessItalic): + italicizeGlyph(g, 9, stemWidth=stemWidth) + else: + italicizeGlyph(g, 10, stemWidth=stemWidth) + g.RemoveOverlap() + g.width += 10 + fl.UpdateGlyph(i-1) + + if swapSuffixes != None: + for swap in swapSuffixes: + swapList = [g.name for g in f.glyphs if g.name.endswith(swap)] + for gname in swapList: + print gname + swapGlyphs(f, gname.replace(swap,""), gname) + for gname in self.predecompose: + g = f[f.FindGlyph(gname)] + if g != None: + g.Decompose() + + log(">> Generating glyphs") + generateGlyphs(f, self.diacriticList) + log(">> Copying features") + f.ot_classes = self.ot_classes + copyFeatures(self.basefont,f) + fl.UpdateFont(index) + log(">> Decomposing") + for gname in self.decompose: + g = f[f.FindGlyph(gname)] + if g != None: + g.Decompose() + g.Decompose() + + setNames(f, n, foundry=self.config.get('main','foundry'), + version=self.config.get('main','version'), + build=self.buildnumber) + cleanCurves(f) + deleteGlyphs(f,self.deleteList) + if kern: + generateFLKernClassesFromOTString(f,self.ot_kerningclasses) + log(">> Generating font files") + directoryName = n[0].replace(" ","") + directoryPath = "%s/%s/%sTTF"%(self.basedir,self.builddir,directoryName) + if not os.path.exists(directoryPath): + os.makedirs(directoryPath) + ttfName = "%s/%s.ttf"%(directoryPath,f.font_name) + fl.GenerateFont(fl.ifont,ftTRUETYPE,ttfName) + f.modified = 0 + fl.Close(index) + +def transformGlyphMembers(g, m): + g.width = int(g.width * m.a) + g.Transform(m) + for a in g.anchors: + p = Point(a.p) + p.Transform(m) + a.p = p + for c in g.components: + # Assumes that components have also been individually transformed + p = Point(0,0) + d = Point(c.deltas[0]) + d.Transform(m) + p.Transform(m) + d1 = d - p + c.deltas[0].x = d1.x + c.deltas[0].y = d1.y + s = Point(c.scale) + s.Transform(m) + #c.scale = s + +def swapGlyphs(f,gName1,gName2): + try: + g1 = f.glyphs[f.FindGlyph(gName1)] + g2 = f.glyphs[f.FindGlyph(gName2)] + except IndexError: + log("swapGlyphs failed for %s %s"%(gName1, gName2)) + return + g3 = Glyph(g1) + + g1.Clear() + g1.Insert(g2) + g1.SetMetrics(g2.GetMetrics()) + + g2.Clear() + g2.Insert(g3) + g2.SetMetrics(g3.GetMetrics()) + +def log(msg): + print msg + +# def addOTFeatures(f): +# f.ot_classes = ot_classes + +def copyFeatures(f1, f2): + for ft in f1.features: + t = Feature(ft.tag, ft.value) + f2.features.append(t) + #f2.ot_classes = f1.ot_classes + f2.classes = [] + f2.classes = f1.classes + +def generateGlyphs(f, glyphNames): + log(">> Generating diacritics") + glyphnames = [gname for gname in glyphNames if not gname.startswith("#") and gname != ""] + + for glyphName in glyphNames: + generateGlyph(f, glyphName) + +def cleanCurves(f): + log(">> Removing overlaps") + for g in f.glyphs: + g.UnselectAll() + g.RemoveOverlap() + + log(">> Mitring sharp corners") + # for g in f.glyphs: + # mitreGlyph(g, 3., .7) + + log(">> Converting curves to quadratic") + # for g in f.glyphs: + # glyphCurvesToQuadratic(g) + +def deleteGlyphs(f,deleteList): + fl.Unselect() + for name in deleteList: + glyphIndex = f.FindGlyph(name) + if glyphIndex != -1: + del f.glyphs[glyphIndex] + fl.UpdateFont() diff --git a/scripts/lib/fontbuild/__init__.py b/scripts/lib/fontbuild/__init__.py new file mode 100755 index 0000000..4ed7203 --- /dev/null +++ b/scripts/lib/fontbuild/__init__.py @@ -0,0 +1,6 @@ +""" +fontbuild + +A collection of font production tools written for FontLab +""" +version = "0.1"
\ No newline at end of file diff --git a/scripts/lib/fontbuild/alignpoints.py b/scripts/lib/fontbuild/alignpoints.py new file mode 100644 index 0000000..ed502ed --- /dev/null +++ b/scripts/lib/fontbuild/alignpoints.py @@ -0,0 +1,148 @@ + + +import numpy as np +from numpy.linalg import lstsq +import math + +def alignCorners(glyph, va, subsegments): + out = va.copy() + # for i,c in enumerate(subsegments): + # segmentCount = len(glyph.contours[i].segments) - 1 + # n = len(c) + # for j,s in enumerate(c): + # if j < segmentCount: + # seg = glyph.contours[i].segments[j] + # if seg.type == "line": + # subIndex = subsegmentIndex(i,j,subsegments) + # out[subIndex] = alignPoints(va[subIndex]) + + for i,c in enumerate(subsegments): + segmentCount = len(glyph.contours[i].segments) + n = len(c) + for j,s in enumerate(c): + if j < segmentCount - 1: + segType = glyph.contours[i].segments[j].type + segnextType = glyph.contours[i].segments[j+1].type + next = j+1 + elif j == segmentCount -1 and s[1] > 3: + segType = glyph.contours[i].segments[j].type + segNextType = "line" + next = j+1 + elif j == segmentCount: + segType = "line" + segnextType = glyph.contours[i].segments[1].type + if glyph.name == "J": + print s[1] + print segnextType + next = 1 + else: + break + if segType == "line" and segnextType == "line": + subIndex = subsegmentIndex(i,j,subsegments) + pts = va[subIndex] + ptsnext = va[subsegmentIndex(i,next,subsegments)] + # out[subIndex[-1]] = (out[subIndex[-1]] - 500) * 3 + 500 #findCorner(pts, ptsnext) + # print subIndex[-1], subIndex, subsegmentIndex(i,next,subsegments) + try: + out[subIndex[-1]] = findCorner(pts, ptsnext) + except: + pass + # print glyph.name, "Can't find corner: parallel lines" + return out + + +def subsegmentIndex(contourIndex, segmentIndex, subsegments): + # This whole thing is so dumb. Need a better data model for subsegments + + contourOffset = 0 + for i,c in enumerate(subsegments): + if i == contourIndex: + break + contourOffset += c[-1][0] + n = subsegments[contourIndex][-1][0] + # print contourIndex, contourOffset, n + startIndex = subsegments[contourIndex][segmentIndex-1][0] + segmentCount = subsegments[contourIndex][segmentIndex][1] + endIndex = (startIndex + segmentCount + 1) % (n) + + indices = np.array([(startIndex + i) % (n) + contourOffset for i in range(segmentCount + 1)]) + return indices + +def alignPoints(pts, start=None, end=None): + if start == None or end == None: + start, end = fitLine(pts) + out = pts.copy() + for i,p in enumerate(pts): + out[i] = nearestPoint(start, end, p) + return out + +def findCorner(pp, nn): + if len(pp) < 4 or len(nn) < 4: + assert 0, "line too short to fit" + pStart,pEnd = fitLine(pp) + nStart,nEnd = fitLine(nn) + prev = pEnd - pStart + next = nEnd - nStart + # print int(np.arctan2(prev[1],prev[0]) / math.pi * 180), + # print int(np.arctan2(next[1],next[0]) / math.pi * 180) + # if lines are parallel, return simple average of end and start points + if np.dot(prev / np.linalg.norm(prev), + next / np.linalg.norm(next)) > .999999: + # print "parallel lines", np.arctan2(prev[1],prev[0]), np.arctan2(next[1],next[0]) + # print prev, next + assert 0, "parallel lines" + return lineIntersect(pStart, pEnd, nStart, nEnd) + +def lineIntersect((x1,y1),(x2,y2),(x3,y3),(x4,y4)): + x12 = x1 - x2 + x34 = x3 - x4 + y12 = y1 - y2 + y34 = y3 - y4 + + det = x12 * y34 - y12 * x34 + if det == 0: + print "parallel!" + + a = x1 * y2 - y1 * x2 + b = x3 * y4 - y3 * x4 + + x = (a * x34 - b * x12) / det + y = (a * y34 - b * y12) / det + + return (x,y) + +def fitLineLSQ(pts): + "returns a line fit with least squares. Fails for vertical lines" + n = len(pts) + a = np.ones((n,2)) + for i in range(n): + a[i,0] = pts[i,0] + line = lstsq(a,pts[:,1])[0] + return line + +def fitLine(pts): + """returns a start vector and direction vector + Assumes points segments that already form a somewhat smooth line + """ + n = len(pts) + if n < 1: + return (0,0),(0,0) + a = np.zeros((n-1,2)) + for i in range(n-1): + v = pts[i] - pts[i+1] + a[i] = v / np.linalg.norm(v) + direction = np.mean(a[1:-1], axis=0) + start = np.mean(pts[1:-1], axis=0) + return start, start+direction + +def nearestPoint(a,b,c): + "nearest point to point c on line a_b" + magnitude = np.linalg.norm(b-a) + if magnitude == 0: + raise Exception, "Line segment cannot be 0 length" + return (b-a) * np.dot((c-a) / magnitude, (b-a) / magnitude) + a + +# pts = np.array([[1,1],[2,2],[3,3],[4,4]]) +# pts2 = np.array([[1,0],[2,0],[3,0],[4,0]]) +# print alignPoints(pts2, start = pts[0], end = pts[0]+pts[0]) +# # print findCorner(pts,pts2)
\ No newline at end of file diff --git a/scripts/lib/fontbuild/anchors.py b/scripts/lib/fontbuild/anchors.py new file mode 100755 index 0000000..7cc3869 --- /dev/null +++ b/scripts/lib/fontbuild/anchors.py @@ -0,0 +1,44 @@ +#import numpy as np +from FL import * + + +def getGlyph(gname,font): + index = font.FindGlyph(gname) + if index != -1: + return font.glyphs[index] + else: + return None + +def getComponentByName(f,g,componentName): + componentIndex = f.FindGlyph(componentName) + for c in g.components: + if c.index == componentIndex: + return c + +def getAnchorByName(g,anchorName): + for a in g.anchors: + if a.name == anchorName: + return a + + +def alignComponentToAnchor(f,glyphName,baseName,accentName,anchorName): + g = getGlyph(glyphName,f) + base = getGlyph(baseName,f) + accent = getGlyph(accentName,f) + if g == None or base == None or accent == None: + return + a1 = getAnchorByName(base,anchorName) + a2 = getAnchorByName(accent,"_" + anchorName) + if a1 == None or a2 == None: + return + offset = a1.p - a2.p + c = getComponentByName(f,g,accentName) + c.deltas[0].x = offset.x + c.deltas[0].y = offset.y + +def alignComponentsToAnchors(f,glyphName,baseName,accentNames): + for a in accentNames: + if len(a) == 1: + continue + alignComponentToAnchor(f,glyphName,baseName,a[0],a[1]) + diff --git a/scripts/lib/fontbuild/convertCurves.py b/scripts/lib/fontbuild/convertCurves.py new file mode 100755 index 0000000..e900a73 --- /dev/null +++ b/scripts/lib/fontbuild/convertCurves.py @@ -0,0 +1,90 @@ +#! /usr/bin/env python + +""" +Converts a cubic bezier curve to a quadratic spline with +exactly two off curve points. + +""" + +import numpy +from numpy import array,cross,dot +from fontTools.misc import bezierTools +from FL import * + +def calcIntersect(a,b,c,d): + numpy.seterr(all='raise') + e = b-a + f = d-c + p = array([-e[1], e[0]]) + try: + h = dot((a-c),p) / dot(f,p) + except: + print a,b,c,d + raise + return c + dot(f,h) + +def simpleConvertToQuadratic(p0,p1,p2,p3): + p = [array(i.x,i.y) for i in [p0,p1,p2,p3]] + off = calcIntersect(p[0],p[1],p[2],p[3]) + +# OFFCURVE_VECTOR_CORRECTION = -.015 +OFFCURVE_VECTOR_CORRECTION = 0 + +def convertToQuadratic(p0,p1,p2,p3): + # TODO: test for accuracy and subdivide further if needed + p = [(i.x,i.y) for i in [p0,p1,p2,p3]] + # if p[0][0] == p[1][0] and p[0][0] == p[2][0] and p[0][0] == p[2][0] and p[0][0] == p[3][0]: + # return (p[0],p[1],p[2],p[3]) + # if p[0][1] == p[1][1] and p[0][1] == p[2][1] and p[0][1] == p[2][1] and p[0][1] == p[3][1]: + # return (p[0],p[1],p[2],p[3]) + seg1,seg2 = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], .5) + pts1 = [array([i[0], i[1]]) for i in seg1] + pts2 = [array([i[0], i[1]]) for i in seg2] + on1 = seg1[0] + on2 = seg2[3] + try: + off1 = calcIntersect(pts1[0], pts1[1], pts1[2], pts1[3]) + off2 = calcIntersect(pts2[0], pts2[1], pts2[2], pts2[3]) + except: + return (p[0],p[1],p[2],p[3]) + off1 = (on1 - off1) * OFFCURVE_VECTOR_CORRECTION + off1 + off2 = (on2 - off2) * OFFCURVE_VECTOR_CORRECTION + off2 + return (on1,off1,off2,on2) + +def cubicNodeToQuadratic(g,nid): + + node = g.nodes[nid] + if (node.type != nCURVE): + print "Node type not curve" + return + + #pNode,junk = getPrevAnchor(g,nid) + pNode = g.nodes[nid-1] #assumes that a nCURVE type will always be proceeded by another point on the same contour + points = convertToQuadratic(pNode[0],node[1],node[2],node[0]) + points = [Point(p[0],p[1]) for p in points] + curve = [ + Node(nOFF, points[1]), + Node(nOFF, points[2]), + Node(nLINE,points[3]) ] + return curve + +def glyphCurvesToQuadratic(g): + + nodes = [] + for i in range(len(g.nodes)): + n = g.nodes[i] + if n.type == nCURVE: + try: + newNodes = cubicNodeToQuadratic(g, i) + nodes = nodes + newNodes + except Exception: + print g.name, i + raise + else: + nodes.append(Node(g.nodes[i])) + g.Clear() + g.Insert(nodes) + + + + diff --git a/scripts/lib/fontbuild/curveFitPen.py b/scripts/lib/fontbuild/curveFitPen.py new file mode 100644 index 0000000..d050479 --- /dev/null +++ b/scripts/lib/fontbuild/curveFitPen.py @@ -0,0 +1,402 @@ +#! /opt/local/bin/pythonw2.7 + +__all__ = ["SubsegmentPen","SubsegmentsToCurvesPen", "segmentGlyph", "fitGlyph"] + +from fontTools.pens.basePen import BasePen +from fontTools.misc import bezierTools +from robofab.pens.pointPen import AbstractPointPen +from robofab.pens.adapterPens import PointToSegmentPen, GuessSmoothPointPen +import numpy as np +from numpy.linalg import norm +from numpy import array as v +from random import random + +from robofab.pens.pointPen import BasePointToSegmentPen +class SubsegmentsToCurvesPointPen(BasePointToSegmentPen): + def __init__(self, glyph, subsegmentGlyph, subsegments): + BasePointToSegmentPen.__init__(self) + self.glyph = glyph + self.subPen = SubsegmentsToCurvesPen(None, glyph.getPen(), subsegmentGlyph, subsegments) + + def setMatchTangents(self, b): + self.subPen.matchTangents = b + + def _flushContour(self, segments): + # + # adapted from robofab.pens.adapterPens.rfUFOPointPen + # + assert len(segments) >= 1 + # if we only have one point and it has a name, we must have an anchor + first = segments[0] + segmentType, points = first + pt, smooth, name, kwargs = points[0] + if len(segments) == 1 and name != None: + self.glyph.appendAnchor(name, pt) + return + else: + segmentType, points = segments[-1] + movePt, smooth, name, kwargs = points[-1] + if smooth: + # last point is smooth, set pen to start smooth + self.subPen.setLastSmooth(True) + if segmentType == 'line': + del segments[-1] + + self.subPen.moveTo(movePt) + + # do the rest of the segments + for segmentType, points in segments: + isSmooth = True in [smooth for pt, smooth, name, kwargs in points] + pp = [pt for pt, smooth, name, kwargs in points] + if segmentType == "line": + assert len(pp) == 1 + if isSmooth: + self.subPen.smoothLineTo(pp[0]) + else: + self.subPen.lineTo(pp[0]) + elif segmentType == "curve": + assert len(pp) == 3 + if isSmooth: + self.subPen.smoothCurveTo(*pp) + else: + self.subPen.curveTo(*pp) + elif segmentType == "qcurve": + assert 0, "qcurve not supported" + else: + assert 0, "illegal segmentType: %s" % segmentType + self.subPen.closePath() + + def addComponent(self, glyphName, transform): + self.subPen.addComponent(glyphName, transform) + +class SubsegmentsToCurvesPen(BasePen): + def __init__(self, glyphSet, otherPen, subsegmentGlyph, subsegments): + BasePen.__init__(self, None) + self.otherPen = otherPen + self.ssglyph = subsegmentGlyph + self.subsegments = subsegments + self.contourIndex = -1 + self.segmentIndex = -1 + self.lastPoint = (0,0) + self.lastSmooth = False + self.nextSmooth = False + + def setLastSmooth(self, b): + self.lastSmooth = b + + def _moveTo(self, (x, y)): + self.contourIndex += 1 + self.segmentIndex = 0 + self.startPoint = (x,y) + p = self.ssglyph.contours[self.contourIndex][0].points[0] + self.otherPen.moveTo((p.x, p.y)) + self.lastPoint = (x,y) + + def _lineTo(self, (x, y)): + self.segmentIndex += 1 + index = self.subsegments[self.contourIndex][self.segmentIndex][0] + p = self.ssglyph.contours[self.contourIndex][index].points[0] + self.otherPen.lineTo((p.x, p.y)) + self.lastPoint = (x,y) + self.lastSmooth = False + + def smoothLineTo(self, (x, y)): + self.lineTo((x,y)) + self.lastSmooth = True + + def smoothCurveTo(self, (x1, y1), (x2, y2), (x3, y3)): + self.nextSmooth = True + self.curveTo((x1, y1), (x2, y2), (x3, y3)) + self.nextSmooth = False + self.lastSmooth = True + + def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)): + self.segmentIndex += 1 + c = self.ssglyph.contours[self.contourIndex] + n = len(c) + startIndex = (self.subsegments[self.contourIndex][self.segmentIndex-1][0]) + segmentCount = (self.subsegments[self.contourIndex][self.segmentIndex][1]) + endIndex = (startIndex + segmentCount + 1) % (n) + + indices = [(startIndex + i) % (n) for i in range(segmentCount + 1)] + points = np.array([(c[i].points[0].x, c[i].points[0].y) for i in indices]) + prevPoint = (c[(startIndex - 1)].points[0].x, c[(startIndex - 1)].points[0].y) + nextPoint = (c[(endIndex) % n].points[0].x, c[(endIndex) % n].points[0].y) + prevTangent = prevPoint - points[0] + nextTangent = nextPoint - points[-1] + + tangent1 = points[1] - points[0] + tangent3 = points[-2] - points[-1] + prevTangent /= np.linalg.norm(prevTangent) + nextTangent /= np.linalg.norm(nextTangent) + tangent1 /= np.linalg.norm(tangent1) + tangent3 /= np.linalg.norm(tangent3) + + tangent1, junk = self.smoothTangents(tangent1, prevTangent, self.lastSmooth) + tangent3, junk = self.smoothTangents(tangent3, nextTangent, self.nextSmooth) + if self.matchTangents == True: + cp = fitBezier(points, tangent1, tangent3) + cp[1] = norm(cp[1] - cp[0]) * tangent1 / norm(tangent1) + cp[0] + cp[2] = norm(cp[2] - cp[3]) * tangent3 / norm(tangent3) + cp[3] + else: + cp = fitBezier(points) + # if self.ssglyph.name == 'r': + # print "-----------" + # print self.lastSmooth, self.nextSmooth + # print "%i %i : %i %i \n %i %i : %i %i \n %i %i : %i %i"%(x1,y1, cp[1,0], cp[1,1], x2,y2, cp[2,0], cp[2,1], x3,y3, cp[3,0], cp[3,1]) + self.otherPen.curveTo((cp[1,0], cp[1,1]), (cp[2,0], cp[2,1]), (cp[3,0], cp[3,1])) + self.lastPoint = (x3, y3) + self.lastSmooth = False + + def smoothTangents(self,t1,t2,forceSmooth = False): + if forceSmooth or (abs(t1.dot(t2)) > .95 and norm(t1-t2) > 1): + # print t1,t2, + t1 = (t1 - t2) / 2 + t2 = -t1 + # print t1,t2 + return t1 / norm(t1), t2 / norm(t2) + + + def _closePath(self): + self.otherPen.closePath() + + def _endPath(self): + self.otherPen.endPath() + + + def addComponent(self, glyphName, transformation): + self.otherPen.addComponent(glyphName, transformation) + + +class SubsegmentPointPen(BasePointToSegmentPen): + def __init__(self, glyph, resolution): + BasePointToSegmentPen.__init__(self) + self.glyph = glyph + self.resolution = resolution + self.subPen = SubsegmentPen(None, glyph.getPen()) + + def getSubsegments(self): + return self.subPen.subsegments[:] + + def _flushContour(self, segments): + # + # adapted from robofab.pens.adapterPens.rfUFOPointPen + # + assert len(segments) >= 1 + # if we only have one point and it has a name, we must have an anchor + first = segments[0] + segmentType, points = first + pt, smooth, name, kwargs = points[0] + if len(segments) == 1 and name != None: + self.glyph.appendAnchor(name, pt) + return + else: + segmentType, points = segments[-1] + movePt, smooth, name, kwargs = points[-1] + if segmentType == 'line': + del segments[-1] + + self.subPen.moveTo(movePt) + + # do the rest of the segments + for segmentType, points in segments: + points = [pt for pt, smooth, name, kwargs in points] + if segmentType == "line": + assert len(points) == 1 + self.subPen.lineTo(points[0]) + elif segmentType == "curve": + assert len(points) == 3 + self.subPen.curveTo(*points) + elif segmentType == "qcurve": + assert 0, "qcurve not supported" + else: + assert 0, "illegal segmentType: %s" % segmentType + self.subPen.closePath() + + def addComponent(self, glyphName, transform): + self.subPen.addComponent(glyphName, transform) + +class SubsegmentPen(BasePen): + + def __init__(self, glyphSet, otherPen, resolution=25): + BasePen.__init__(self,glyphSet) + self.resolution = resolution + self.otherPen = otherPen + self.subsegments = [] + self.startContour = (0,0) + self.contourIndex = -1 + + def _moveTo(self, (x, y)): + self.contourIndex += 1 + self.segmentIndex = 0 + self.subsegments.append([]) + self.subsegmentCount = 0 + self.subsegments[self.contourIndex].append([self.subsegmentCount, 0]) + self.startContour = (x,y) + self.lastPoint = (x,y) + self.otherPen.moveTo((x,y)) + + def _lineTo(self, (x, y)): + count = self.stepsForSegment((x,y),self.lastPoint) + if count < 1: + count = 1 + self.subsegmentCount += count + self.subsegments[self.contourIndex].append([self.subsegmentCount, count]) + for i in range(1,count+1): + x1 = self.lastPoint[0] + (x - self.lastPoint[0]) * i/float(count) + y1 = self.lastPoint[1] + (y - self.lastPoint[1]) * i/float(count) + self.otherPen.lineTo((x1,y1)) + self.lastPoint = (x,y) + + def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)): + count = self.stepsForSegment((x3,y3),self.lastPoint) + if count < 2: + count = 2 + self.subsegmentCount += count + self.subsegments[self.contourIndex].append([self.subsegmentCount,count]) + x = self.renderCurve((self.lastPoint[0],x1,x2,x3),count) + y = self.renderCurve((self.lastPoint[1],y1,y2,y3),count) + assert len(x) == count + if (x3 == self.startContour[0] and y3 == self.startContour[1]): + count -= 1 + for i in range(count): + self.otherPen.lineTo((x[i],y[i])) + self.lastPoint = (x3,y3) + + def _closePath(self): + if not (self.lastPoint[0] == self.startContour[0] and self.lastPoint[1] == self.startContour[1]): + self._lineTo(self.startContour) + self.otherPen.closePath() + + def _endPath(self): + self.otherPen.endPath() + + def addComponent(self, glyphName, transformation): + self.otherPen.addComponent(glyphName, transformation) + + def stepsForSegment(self, p1, p2): + dist = np.linalg.norm(v(p1) - v(p2)) + out = int(dist / self.resolution) + return out + + def renderCurve(self,p,count): + curvePoints = [] + t = 1.0 / float(count) + temp = t * t + + f = p[0] + fd = 3 * (p[1] - p[0]) * t + fdd_per_2 = 3 * (p[0] - 2 * p[1] + p[2]) * temp + fddd_per_2 = 3 * (3 * (p[1] - p[2]) + p[3] - p[0]) * temp * t + + fddd = fddd_per_2 + fddd_per_2 + fdd = fdd_per_2 + fdd_per_2 + fddd_per_6 = fddd_per_2 * (1.0 / 3) + + for i in range(count): + f = f + fd + fdd_per_2 + fddd_per_6 + fd = fd + fdd + fddd_per_2 + fdd = fdd + fddd + fdd_per_2 = fdd_per_2 + fddd_per_2 + curvePoints.append(f) + + return curvePoints + + + +def fitBezierSimple(pts): + T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))] + tsum = np.sum(T) + T = [0] + T + T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))] + T = [[t**3, t**2, t, 1] for t in T] + T = np.array(T) + M = np.array([[-1, 3, -3, 1], + [ 3, -6, 3, 0], + [-3, 3, 0, 0], + [ 1, 0, 0, 0]]) + T = T.dot(M) + T = np.concatenate((T, np.array([[100,0,0,0], [0,0,0,100]]))) + # pts = np.vstack((pts, pts[0] * 100, pts[-1] * 100)) + C = np.linalg.lstsq(T, pts) + return C[0] + + +def subdivideLineSegment(pts): + out = [pts[0]] + for i in range(1, len(pts)): + out.append(pts[i-1] + (pts[i] - pts[i-1]) * .5) + out.append(pts[i]) + return np.array(out) + + +def fitBezier(pts,tangent0=None,tangent3=None): + if len(pts < 4): + pts = subdivideLineSegment(pts) + T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))] + tsum = np.sum(T) + T = [0] + T + T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))] + T = [[t**3, t**2, t, 1] for t in T] + T = np.array(T) + M = np.array([[-1, 3, -3, 1], + [ 3, -6, 3, 0], + [-3, 3, 0, 0], + [ 1, 0, 0, 0]]) + T = T.dot(M) + n = len(pts) + pout = pts.copy() + pout[:,0] -= (T[:,0] * pts[0,0]) + (T[:,3] * pts[-1,0]) + pout[:,1] -= (T[:,0] * pts[0,1]) + (T[:,3] * pts[-1,1]) + + TT = np.zeros((n*2,4)) + for i in range(n): + for j in range(2): + TT[i*2,j*2] = T[i,j+1] + TT[i*2+1,j*2+1] = T[i,j+1] + pout = pout.reshape((n*2,1),order="C") + + if tangent0 != None and tangent3 != None: + tangentConstraintsT = np.array([ + [tangent0[1], -tangent0[0], 0, 0], + [0, 0, tangent3[1], -tangent3[0]] + ]) + tangentConstraintsP = np.array([ + [pts[0][1] * -tangent0[0] + pts[0][0] * tangent0[1]], + [pts[-1][1] * -tangent3[0] + pts[-1][0] * tangent3[1]] + ]) + TT = np.concatenate((TT, tangentConstraintsT * 1000)) + pout = np.concatenate((pout, tangentConstraintsP * 1000)) + C = np.linalg.lstsq(TT,pout)[0].reshape((2,2)) + return np.array([pts[0], C[0], C[1], pts[-1]]) + + +def segmentGlyph(glyph,resolution=50): + g1 = glyph.copy() + g1.clear() + dp = SubsegmentPointPen(g1, resolution) + glyph.drawPoints(dp) + return g1, dp.getSubsegments() + + +def fitGlyph(glyph, subsegmentGlyph, subsegmentIndices, matchTangents=True): + outGlyph = glyph.copy() + outGlyph.clear() + fitPen = SubsegmentsToCurvesPointPen(outGlyph, subsegmentGlyph, subsegmentIndices) + fitPen.setMatchTangents(matchTangents) + # smoothPen = GuessSmoothPointPen(fitPen) + glyph.drawPoints(fitPen) + outGlyph.width = subsegmentGlyph.width + return outGlyph + + +if __name__ == '__main__': + p = SubsegmentPen(None, None) + pts = np.array([ + [0,0], + [.5,.5], + [.5,.5], + [1,1] + ]) + print np.array(p.renderCurve(pts,10)) * 10 +
\ No newline at end of file diff --git a/scripts/lib/fontbuild/generateGlyph.py b/scripts/lib/fontbuild/generateGlyph.py new file mode 100755 index 0000000..f2214f0 --- /dev/null +++ b/scripts/lib/fontbuild/generateGlyph.py @@ -0,0 +1,48 @@ +from anchors import alignComponentsToAnchors +from FL import * + +def parseComposite(composite): + c = composite.split("=") + d = c[1].split("/") + glyphName = d[0] + if len(d) == 1: + offset = [0,0] + else: + offset = [int(i) for i in d[1].split(",")] + accentString = c[0] + accents = accentString.split("+") + baseName = accents.pop(0) + accentNames = [i.split(":") for i in accents ] + return (glyphName, baseName, accentNames, offset) + +def shiftGlyphMembers(g, x): + g.Shift(Point(x,0)) + for c in g.components: + c.deltas[0].x = c.deltas[0].x + x + +def generateGlyph(f,gname): + if gname.find("_") != -1: + generateString = gname + g = f.GenerateGlyph(generateString) + if f.FindGlyph(g.name) == -1: + f.glyphs.append(g) + return g + else: + glyphName, baseName, accentNames, offset = parseComposite(gname) + components = [baseName] + [i[0] for i in accentNames] + if len(components) == 1: + components.append("NONE") + generateString = "%s=%s" %("+".join(components), glyphName) + g = f.GenerateGlyph(generateString) + if f.FindGlyph(g.name) == -1: + f.glyphs.append(g) + g1 = f.glyphs[f.FindGlyph(g.name)] + if len(accentNames) > 0: + alignComponentsToAnchors(f,glyphName,baseName,accentNames) + if (offset[0] != 0 or offset[1] != 0): + g1.width += offset[1] + offset[0] + shiftGlyphMembers(g1,offset[0]) + return g + +# generateGlyph(fl.font,"A+ogonek=Aogonek") +# fl.UpdateFont()
\ No newline at end of file diff --git a/scripts/lib/fontbuild/instanceNames.py b/scripts/lib/fontbuild/instanceNames.py new file mode 100755 index 0000000..96827c8 --- /dev/null +++ b/scripts/lib/fontbuild/instanceNames.py @@ -0,0 +1,184 @@ +from datetime import date +import re +from random import randint +import string + +class InstanceNames: + "Class that allows easy setting of FontLab name fields. TODO: Add proper italic flags" + + foundry = "" + build = "0000" + version = "1.0" + year = date.today().year + designer = "Christian Robertson" + license = "Licensed under the Apache License, Version 2.0" + licenseURL = "http://www.apache.org/licenses/LICENSE-2.0" + + def __init__(self,names): + if type(names) == type(" "): + names = names.split("/") + + self.longfamily = names[0] + self.longstyle = names[1] + self.shortstyle = names[2] + self.subfamilyAbbrev = names[3] + + self.width = self._getWidth() + self.italic = self._getItalic() + self.weight = self._getWeight() + self.fullname = "%s %s" %(self.longfamily, self.longstyle) + self.postscript = re.sub(' ','', self.longfamily) + "-" + re.sub(' ','',self.longstyle) + + # if self.subfamilyAbbrev != "" and self.subfamilyAbbrev != None and self.subfamilyAbbrev != "Rg": + # self.shortfamily = "%s %s" %(self.longfamily, self.subfamilyAbbrev) + # else: + # self.shortfamily = self.longfamily + self.shortfamily = self.longfamily + + def setRFNames(self,f, version=1, versionMinor=0): + f.info.familyName = self.longfamily + f.info.styleName = self.longstyle + f.info.styleMapFamilyName = self.shortfamily + f.info.styleMapStyleName = self.shortstyle.lower() + f.info.versionMajor = version + f.info.versionMinor = versionMinor + f.info.year = self.year + f.info.copyright = "Font data copyright %s %s" %(self.foundry, self.year) + f.info.trademark = "%s is a trademark of %s." %(self.longfamily, self.foundry) + + f.info.openTypeNameDesigner = "Christian Robertson" + f.info.openTypeNameDesignerURL = self.foundry + ".com" + f.info.openTypeNameManufacturer = self.foundry + f.info.openTypeNameManufacturerURL = self.foundry + ".com" + f.info.openTypeNameLicense = self.license + f.info.openTypeNameLicenseURL = self.licenseURL + f.info.openTypeNameVersion = "%i.%i" %(version,versionMinor) + f.info.openTypeNameUniqueID = "%s:%s:%s" %(self.foundry, self.longfamily, self.year) + # f.info.openTypeNameDescription = "" + # f.info.openTypeNameCompatibleFullName = "" + # f.info.openTypeNameSampleText = "" + if (self.subfamilyAbbrev != "Rg"): + f.info.openTypeNamePreferredFamilyName = self.longfamily + f.info.openTypeNamePreferredSubfamilyName = self.longstyle + + f.info.macintoshFONDName = re.sub(' ','',self.longfamily) + " " + re.sub(' ','',self.longstyle) + + + def setFLNames(self,flFont): + + from FL import NameRecord + + flFont.family_name = self.shortfamily + flFont.mac_compatible = self.fullname + flFont.style_name = self.longstyle + flFont.full_name = self.fullname + flFont.font_name = self.postscript + flFont.font_style = self._getStyleCode() + flFont.menu_name = self.shortfamily + flFont.apple_name = re.sub(' ','',self.longfamily) + " " + re.sub(' ','',self.longstyle) + flFont.fond_id = randint(1000,9999) + flFont.pref_family_name = self.longfamily + flFont.pref_style_name = self.longstyle + flFont.weight = self.weight + flFont.weight_code = self._getWeightCode(self.weight) + flFont.width = self.width + + fn = flFont.fontnames + fn.clean() + fn.append(NameRecord(0,1,0,0, "Font data copyright %s %s" %(self.foundry, self.year) )) + fn.append(NameRecord(0,3,1,1033, "Font data copyright %s %s" %(self.foundry, self.year) )) + fn.append(NameRecord(1,1,0,0, self.longfamily )) + fn.append(NameRecord(1,3,1,1033, self.shortfamily )) + fn.append(NameRecord(2,1,0,0, self.longstyle )) + fn.append(NameRecord(2,3,1,1033, self.longstyle )) + fn.append(NameRecord(3,1,0,0, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) )) + fn.append(NameRecord(3,3,1,1033, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) )) + fn.append(NameRecord(4,1,0,0, self.fullname )) + fn.append(NameRecord(4,3,1,1033, self.fullname )) + fn.append(NameRecord(5,1,0,0, "Version %s%s; %s" %(self.version, self.build, self.year) )) + fn.append(NameRecord(5,3,1,1033, "Version %s%s; %s" %(self.version, self.build, self.year) )) + fn.append(NameRecord(6,1,0,0, self.postscript )) + fn.append(NameRecord(6,3,1,1033, self.postscript )) + fn.append(NameRecord(7,1,0,0, "%s is a trademark of %s." %(self.longfamily, self.foundry) )) + fn.append(NameRecord(7,3,1,1033, "%s is a trademark of %s." %(self.longfamily, self.foundry) )) + fn.append(NameRecord(9,1,0,0, self.foundry )) + fn.append(NameRecord(9,3,1,1033, self.foundry )) + fn.append(NameRecord(11,1,0,0, self.foundry + ".com" )) + fn.append(NameRecord(11,3,1,1033, self.foundry + ".com" )) + fn.append(NameRecord(12,1,0,0, self.designer )) + fn.append(NameRecord(12,3,1,1033, self.designer )) + fn.append(NameRecord(13,1,0,0, self.license )) + fn.append(NameRecord(13,3,1,1033, self.license )) + fn.append(NameRecord(14,1,0,0, self.licenseURL )) + fn.append(NameRecord(14,3,1,1033, self.licenseURL )) + if (self.subfamilyAbbrev != "Rg"): + fn.append(NameRecord(16,3,1,1033, self.longfamily )) + fn.append(NameRecord(17,3,1,1033, self.longstyle)) + #else: + #fn.append(NameRecord(17,3,1,1033,"")) + #fn.append(NameRecord(18,1,0,0, re.sub("Italic","It", self.fullname))) + + def _getSubstyle(self, regex): + substyle = re.findall(regex, self.longstyle) + if len(substyle) > 0: + return substyle[0] + else: + return "" + + def _getItalic(self): + return self._getSubstyle(r"Italic|Oblique|Obliq") + + def _getWeight(self): + w = self._getSubstyle(r"Extrabold|Superbold|Super|Fat|Bold|Semibold|Demibold|Medium|Light|Thin") + if w == "": + w = "Regular" + return w + + def _getWidth(self): + w = self._getSubstyle(r"Condensed|Extended|Narrow|Wide") + if w == "": + w = "Normal" + return w + + def _getStyleCode(self): + styleCode = 0 + if self.shortstyle == "Bold": + styleCode = 32 + if self.shortstyle == "Italic": + styleCode = 1 + if self.shortstyle == "Bold Italic": + styleCode = 33 + if self.longstyle == "Regular": + styleCode = 64 + return styleCode + + def _getWeightCode(self,weight): + if weight == "Thin": + return 250 + elif weight == "Light": + return 300 + elif weight == "Bold": + return 700 + elif weight == "Medium": + return 500 + elif weight == "Semibold": + return 600 + elif weight == "Black": + return 800 + elif weight == "Fat": + return 900 + + return 400 + +def setNames(f,names,foundry="",version="1.0",build="0000"): + InstanceNames.foundry = foundry + InstanceNames.version = version + InstanceNames.build = build + i = InstanceNames(names) + i.setFLNames(f) + +def setNamesRF(f,names,foundry=""): + InstanceNames.foundry = foundry + i = InstanceNames(names) + i.setRFNames(f) +
\ No newline at end of file diff --git a/scripts/lib/fontbuild/italics.py b/scripts/lib/fontbuild/italics.py new file mode 100644 index 0000000..72178b4 --- /dev/null +++ b/scripts/lib/fontbuild/italics.py @@ -0,0 +1,267 @@ +from fontTools.misc.transform import Transform +from robofab.world import CurrentFont +from robofab.world import RFont +from time import clock +import numpy as np +import math +from alignpoints import alignCorners + +def italicizeGlyph(g, angle=10, stemWidth=185): + f = CurrentFont() + glyph = f[g.name] + slope = np.tanh([math.pi * angle / 180]) + + # determine how far on the x axis the glyph should slide + # to compensate for the slant. -600 is a magic number + # that assumes a 2048 unit em square + MEAN_YCENTER = -600 + m = Transform(1, 0, slope, 1, 0, 0) + xoffset, junk = m.transformPoint((0, MEAN_YCENTER)) + m = Transform(.97, 0, slope, 1, xoffset, 0) + + if len(glyph) > 0: + g2 = italicize(f[g.name], angle, xoffset=xoffset, stemWidth=stemWidth) + f.insertGlyph(g2, g.name) + + transformFLGlyphMembers(f[g.name], m) + + +def italicize(glyph, angle=12, stemWidth=180, xoffset=-50): + CURVE_CORRECTION_WEIGHT = .03 + CORNER_WEIGHT = 10 + ga,subsegments = segmentGlyph(glyph,25) + va, e = glyphToMesh(ga) + n = len(va) + grad = mapEdges(lambda a,(p,n): normalize(p-a), va, e) + cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1)) + smooth = np.ones((n,1)) * CURVE_CORRECTION_WEIGHT + + controlPoints = findControlPointsInMesh(glyph, va, subsegments) + smooth[controlPoints > 0] = 1 + smooth[cornerWeights < .6] = CORNER_WEIGHT + # smooth[cornerWeights >= .9999] = 1 + + out = va.copy() + hascurves = False + for c in glyph.contours: + for s in c.segments: + if s.type == "curve": + hascurves = True + break + if hascurves: + break + if stemWidth > 100: + outCorrected = skewMesh(recompose(skewMesh(out, angle * 1.6), grad, e, smooth=smooth), -angle * 1.6) + # out = copyMeshDetails(va, out, e, 6) + else: + outCorrected = out + normals = edgeNormals(out, e) + center = va + normals * stemWidth * .4 + if stemWidth > 130: + center[:, 0] = va[:, 0] * .7 + center[:,0] * .3 + centerSkew = skewMesh(center.dot(np.array([[.97,0],[0,1]])), angle * .9) + out = outCorrected + (centerSkew - center) + out[:,1] = outCorrected[:,1] + + smooth = np.ones((n,1)) * .1 + out = alignCorners(glyph, out, subsegments) + out = copyMeshDetails(skewMesh(va, angle), out, e, 7, smooth=smooth) + # grad = mapEdges(lambda a,(p,n): normalize(p-a), skewMesh(outCorrected, angle*.9), e) + # out = recompose(out, grad, e, smooth=smooth) + + out = skewMesh(out, angle * .1) + out[:,0] += xoffset + # out[:,1] = outCorrected[:,1] + out[va[:,1] == 0, 1] = 0 + gOut = meshToGlyph(out, ga) + # gOut.width *= .97 + # gOut.width += 10 + # return gOut + return fitGlyph(glyph, gOut, subsegments) + +def condenseGlyph(glyph, scale=.8, stemWidth=185): + ga, subsegments = segmentGlyph(glyph, 25) + va, e = glyphToMesh(ga) + n = len(va) + + normals = edgeNormals(va,e) + cn = va.dot(np.array([[scale, 0],[0,1]])) + grad = mapEdges(lambda a,(p,n): normalize(p-a), cn, e) + # ograd = mapEdges(lambda a,(p,n): normalize(p-a), va, e) + + cn[:,0] -= normals[:,0] * stemWidth * .5 * (1 - scale) + out = recompose(cn, grad, e, smooth=.5) + # out = recompose(out, grad, e, smooth=.1) + out = recompose(out, grad, e, smooth=.01) + + # cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1)) + # smooth = np.ones((n,1)) * .1 + # smooth[cornerWeights < .6] = 10 + # + # grad2 = quantizeGradient(grad).astype(float) + # grad2 = copyGradDetails(grad, grad2, e, scale=10) + # grad2 = mapEdges(lambda a,e: normalize(a), grad2, e) + # out = recompose(out, grad2, e, smooth=smooth) + out[:,0] += 15 + out[:,1] = va[:,1] + # out = recompose(out, grad, e, smooth=.5) + gOut = meshToGlyph(out, ga) + gOut = fitGlyph(glyph, gOut, subsegments) + for i,seg in enumerate(gOut): + gOut[i].points[0].y = glyph[i].points[0].y + return gOut + + +def transformFLGlyphMembers(g, m, transformAnchors = True): + # g.transform(m) + g.width = g.width * m[0] + p = m.transformPoint((0,0)) + for c in g.components: + d = m.transformPoint(c.offset) + c.offset = (d[0] - p[0], d[1] - p[1]) + if transformAnchors: + for a in g.anchors: + aa = m.transformPoint((a.x,a.y)) + a.x = aa[0] + # a.x,a.y = (aa[0] - p[0], aa[1] - p[1]) + # a.x = a.x - m[4] + + +from curveFitPen import fitGlyph,segmentGlyph +from numpy.linalg import norm +from scipy.sparse.linalg import cg +from scipy.ndimage.filters import gaussian_filter1d as gaussian +from scipy.cluster.vq import vq, kmeans2, whiten + +def glyphToMesh(g): + points = [] + edges = {} + offset = 0 + for c in g.contours: + if len(c) < 2: + continue + for i,prev,next in rangePrevNext(len(c)): + points.append((c[i].points[0].x, c[i].points[0].y)) + edges[i + offset] = np.array([prev + offset, next + offset], dtype=int) + offset += len(c) + return np.array(points), edges + +def meshToGlyph(points, g): + g1 = g.copy() + j = 0 + for c in g1.contours: + if len(c) < 2: + continue + for i in range(len(c)): + c[i].points[0].x = points[j][0] + c[i].points[0].y = points[j][1] + j += 1 + return g1 + +def quantizeGradient(grad, book=None): + if book == None: + book = np.array([(1,0),(0,1),(0,-1),(-1,0)]) + indexArray = vq(whiten(grad), book)[0] + out = book[indexArray] + for i,v in enumerate(out): + out[i] = normalize(v) + return out + +def findControlPointsInMesh(glyph, va, subsegments): + controlPointIndices = np.zeros((len(va),1)) + index = 0 + for i,c in enumerate(subsegments): + segmentCount = len(glyph.contours[i].segments) - 1 + for j,s in enumerate(c): + if j < segmentCount: + if glyph.contours[i].segments[j].type == "line": + controlPointIndices[index] = 1 + index += s[1] + return controlPointIndices + + + +def recompose(v, grad, e, smooth=1, P=None, distance=None): + n = len(v) + if distance == None: + distance = mapEdges(lambda a,(p,n): norm(p - a), v, e) + if (P == None): + P = mP(v,e) + P += np.identity(n) * smooth + f = v.copy() + for i,(prev,next) in e.iteritems(): + f[i] = (grad[next] * distance[next] - grad[i] * distance[i]) + out = v.copy() + f += v * smooth + for i in range(len(out[0,:])): + out[:,i] = cg(P, f[:,i])[0] + return out + +def mP(v,e): + n = len(v) + M = np.zeros((n,n)) + for i, edges in e.iteritems(): + w = -2 / float(len(edges)) + for index in edges: + M[i,index] = w + M[i,i] = 2 + return M + +def normalize(v): + n = np.linalg.norm(v) + if n == 0: + return v + return v/n + +def mapEdges(func,v,e,*args): + b = v.copy() + for i, edges in e.iteritems(): + b[i] = func(v[i], [v[j] for j in edges], *args) + return b + +def getNormal(a,b,c): + "Assumes TT winding direction" + p = np.roll(normalize(b - a), 1) + n = -np.roll(normalize(c - a), 1) + p[1] *= -1 + n[1] *= -1 + # print p, n, normalize((p + n) * .5) + return normalize((p + n) * .5) + +def edgeNormals(v,e): + "Assumes a mesh where each vertex has exactly least two edges" + return mapEdges(lambda a,(p,n) : getNormal(a,p,n),v,e) + +def rangePrevNext(count): + c = np.arange(count,dtype=int) + r = np.vstack((c, np.roll(c, 1), np.roll(c, -1))) + return r.T + +def skewMesh(v,angle): + slope = np.tanh([math.pi * angle / 180]) + return v.dot(np.array([[1,0],[slope,1]])) + +def labelConnected(e): + label = 0 + labels = np.zeros((len(e),1)) + for i,(prev,next) in e.iteritems(): + labels[i] = label + if next <= i: + label += 1 + return labels + +def copyGradDetails(a,b,e,scale=15): + n = len(a) + labels = labelConnected(e) + out = a.astype(float).copy() + for i in range(labels[-1]+1): + mask = (labels==i).flatten() + out[mask,:] = gaussian(b[mask,:], scale, mode="wrap", axis=0) + a[mask,:] - gaussian(a[mask,:], scale, mode="wrap", axis=0) + return out + +def copyMeshDetails(va,vb,e,scale=5,smooth=.01): + gradA = mapEdges(lambda a,(p,n): normalize(p-a), va, e) + gradB = mapEdges(lambda a,(p,n): normalize(p-a), vb, e) + grad = copyGradDetails(gradA, gradB, e, scale) + grad = mapEdges(lambda a,(p,n): normalize(a), grad, e) + return recompose(vb, grad, e, smooth=smooth)
\ No newline at end of file diff --git a/scripts/lib/fontbuild/italics2.py b/scripts/lib/fontbuild/italics2.py new file mode 100644 index 0000000..7eee90b --- /dev/null +++ b/scripts/lib/fontbuild/italics2.py @@ -0,0 +1,148 @@ +from curveFitPen import fitGlyph,segmentGlyph +import numpy as np +from numpy.linalg import norm +import math +from scipy.sparse.linalg import cg + +def glyphToMesh(g): + points = [] + edges = {} + offset = 0 + for c in g.contours: + if len(c) < 2: + continue + for i,prev,next in rangePrevNext(len(c)): + points.append((c[i].points[0].x, c[i].points[0].y)) + edges[i + offset] = np.array([prev + offset, next + offset], dtype=int) + offset += len(c) + return np.array(points), edges + +def meshToGlyph(points, g): + g1 = g.copy() + j = 0 + for c in g1.contours: + if len(c) < 2: + continue + for i in range(len(c)): + c[i].points[0].x = points[j][0] + c[i].points[0].y = points[j][1] + j += 1 + return g1 + +def italicize(glyph, angle=12, stemWidth=180, xoffset=-50): + ga,subsegments = segmentGlyph(glyph,25) + va, e = glyphToMesh(ga) + n = len(va) + grad = mapEdges(lambda a,(p,n): normalize(p-a), va, e) + cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1)) + smooth = np.ones((n,1)) * .02 + smooth[cornerWeights < .6] = 5 + # smooth[cornerWeights >= .9999] = 2 + out = va.copy() + if stemWidth > 100: + out = skewMesh(poisson(skewMesh(out, angle * 2), grad, e, smooth=smooth), -angle * 2) + out = copyMeshDetails(va, out, e, 6) + # return meshToGlyph(out,ga) + normals = edgeNormals(out, e) + center = va + normals * stemWidth * .4 + if stemWidth > 100: + center[:, 0] = va[:, 0] + centerSkew = skewMesh(center.dot(np.array([[.97,0],[0,1]])), angle * .7) + # centerSkew = skewMesh(center, angle * .7) + out = out + (centerSkew - center) + out = copyMeshDetails(skewMesh(va, angle * .7), out, e, 12) + out = skewMesh(out, angle * .3) + out[:,0] += xoffset + # out[:,1] = va[:,1] + gOut = meshToGlyph(out, ga) + # gOut.width *= .97 + gOut.width += 10 + # return gOut + return fitGlyph(glyph, gOut, subsegments) + +def poisson(v, grad, e, smooth=1, P=None, distance=None): + n = len(v) + if distance == None: + distance = mapEdges(lambda a,(p,n): norm(p - a), v, e) + if (P == None): + P = mP(v,e) + P += np.identity(n) * smooth + f = v.copy() + for i,(prev,next) in e.iteritems(): + f[i] = (grad[next] * distance[next] - grad[i] * distance[i]) + out = v.copy() + f += v * smooth + for i in range(len(out[0,:])): + out[:,i] = cg(P, f[:,i])[0] + return out + +def mP(v,e): + n = len(v) + M = np.zeros((n,n)) + for i, edges in e.iteritems(): + w = -2 / float(len(edges)) + for index in edges: + M[i,index] = w + M[i,i] = 2 + return M + +def normalize(v): + n = np.linalg.norm(v) + if n == 0: + return v + return v/n + +def mapEdges(func,v,e,*args): + b = v.copy() + for i, edges in e.iteritems(): + b[i] = func(v[i], [v[j] for j in edges], *args) + return b + +def getNormal(a,b,c): + "Assumes TT winding direction" + p = np.roll(normalize(b - a), 1) + n = -np.roll(normalize(c - a), 1) + p[1] *= -1 + n[1] *= -1 + # print p, n, normalize((p + n) * .5) + return normalize((p + n) * .5) + +def edgeNormals(v,e): + "Assumes a mesh where each vertex has exactly least two edges" + return mapEdges(lambda a,(p,n) : getNormal(a,p,n),v,e) + +def rangePrevNext(count): + c = np.arange(count,dtype=int) + r = np.vstack((c, np.roll(c, 1), np.roll(c, -1))) + return r.T + +def skewMesh(v,angle): + slope = np.tanh([math.pi * angle / 180]) + return v.dot(np.array([[1,0],[slope,1]])) + + +from scipy.ndimage.filters import gaussian_filter1d as gaussian + +def labelConnected(e): + label = 0 + labels = np.zeros((len(e),1)) + for i,(prev,next) in e.iteritems(): + labels[i] = label + if next <= i: + label += 1 + return labels + +def copyGradDetails(a,b,e,scale=15): + n = len(a) + labels = labelConnected(e) + out = a.astype(float).copy() + for i in range(labels[-1]+1): + mask = (labels==i).flatten() + out[mask,:] = gaussian(b[mask,:], scale, mode="wrap", axis=0) + a[mask,:] - gaussian(a[mask,:], scale, mode="wrap", axis=0) + return out + +def copyMeshDetails(va,vb,e,scale=5,smooth=.01): + gradA = mapEdges(lambda a,(p,n): normalize(p-a), va, e) + gradB = mapEdges(lambda a,(p,n): normalize(p-a), vb, e) + grad = copyGradDetails(gradA, gradB, e, scale) + return poisson(vb, grad, e, smooth=smooth)
\ No newline at end of file diff --git a/scripts/lib/fontbuild/kerning.py b/scripts/lib/fontbuild/kerning.py new file mode 100755 index 0000000..cdece40 --- /dev/null +++ b/scripts/lib/fontbuild/kerning.py @@ -0,0 +1,26 @@ +import re +from FL import * + +def markKernClassesLR(f): + for i in range(len(f.classes)): + classname = f.classes[i].split(':', 1).pop(0).strip() + if classname.startswith('_'): + l = 0 + r = 0 + if classname.endswith('_L'): + l = 1 + elif classname.endswith('_R'): + r = 1 + elif classname.endswith('_LR'): + l = 1 + r = 1 + f.SetClassFlags(i, l, r) + fl.UpdateFont() + +def generateFLKernClassesFromOTString(f,classString): + classString.replace("\r","\n") + rx = re.compile(r"@(_[\w]+)\s*=\s*\[\s*(\w+?)\s+(.*?)\]\s*;") + classes = ["%s : %s' %s" %(m[0],m[1],m[2]) for m in rx.findall(classString)] + f.classes = classes + markKernClassesLR(f) + diff --git a/scripts/lib/fontbuild/mitreGlyph.py b/scripts/lib/fontbuild/mitreGlyph.py new file mode 100755 index 0000000..ab68e4e --- /dev/null +++ b/scripts/lib/fontbuild/mitreGlyph.py @@ -0,0 +1,121 @@ +"""Mitre Glyph: + +mitreSize : Length of the segment created by the mitre. The default is 4. +maxAngle : Maximum angle in radians at which nodes will be mitred. The default is .9 (about 50 degrees). + Works for both inside and outside angles + +""" + +from FL import * +import math + +def getContours(g): + nLength = len(g.nodes) + contours = [] + cid = -1 + for i in range(nLength): + n = g.nodes[i] + if n.type == nMOVE: + cid += 1 + contours.append([]) + contours[cid].append(n) + return contours + +def getTangents(contours): + tmap = [] + for c in contours: + clen = len(c) + for i in range(clen): + n = c[i] + p = Point(n.x, n.y) + nn = c[(i + 1) % clen] + pn = c[(clen + i - 1) % clen] + if nn.type == nCURVE: + np = Point(nn[1].x,nn[1].y) + else: + np = Point(nn.x,nn.y) + if n.type == nCURVE: + pp = Point(n[2].x,n[2].y) + else: + pp = Point(pn.x,pn.y) + nVect = Point(-p.x + np.x, -p.y + np.y) + pVect = Point(-p.x + pp.x, -p.y + pp.y) + tmap.append((pVect,nVect)) + return tmap + +def normalizeVector(p): + m = getMagnitude(p); + if m != 0: + return p*(1/m) + else: + return Point(0,0) + +def getMagnitude(p): + return math.sqrt(p.x*p.x + p.y*p.y) + +def getDistance(v1,v2): + return getMagnitude(Point(v1.x - v2.x, v1.y - v2.y)) + +def getAngle(v1,v2): + angle = math.atan2(v1.y,v1.x) - math.atan2(v2.y,v2.x) + return (angle + (2*math.pi)) % (2*math.pi) + +def angleDiff(a,b): + return math.pi - abs((abs(a - b) % (math.pi*2)) - math.pi) + +def getAngle2(v1,v2): + return abs(angleDiff(math.atan2(v1.y, v1.x), math.atan2(v2.y, v2.x))) + +def getMitreOffset(n,v1,v2,mitreSize=4,maxAngle=.9): + + # dont mitre if segment is too short + if abs(getMagnitude(v1)) < mitreSize * 2 or abs(getMagnitude(v2)) < mitreSize * 2: + return + angle = getAngle2(v2,v1) + v1 = normalizeVector(v1) + v2 = normalizeVector(v2) + if v1.x == v2.x and v1.y == v2.y: + return + + + # only mitre corners sharper than maxAngle + if angle > maxAngle: + return + + radius = mitreSize / abs(getDistance(v1,v2)) + offset1 = Point(round(v1.x * radius), round(v1.y * radius)) + offset2 = Point(round(v2.x * radius), round(v2.y * radius)) + return offset1, offset2 + +def mitreGlyph(g,mitreSize,maxAngle): + if g == None: + return + + contours = getContours(g) + tangents = getTangents(contours) + nodes = [] + needsMitring = False + nid = -1 + for c in contours: + for n in c: + nid += 1 + v1, v2 = tangents[nid] + off = getMitreOffset(n,v1,v2,mitreSize,maxAngle) + n1 = Node(n) + if off != None: + offset1, offset2 = off + n2 = Node(nLINE, Point(n.x + offset2.x, n.y + offset2.y)) + n1[0].x += offset1.x + n1[0].y += offset1.y + nodes.append(n1) + nodes.append(n2) + needsMitring = True + else: + nodes.append(n1) + if needsMitring: + g.Clear() + g.Insert(nodes) + +fl.SetUndo() +mitreGlyph(fl.glyph,8.,.9) +fl.UpdateGlyph()
\ No newline at end of file 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 |