diff options
Diffstat (limited to 'scripts/lib/fontbuild')
-rw-r--r-- | scripts/lib/fontbuild/Build.py | 118 | ||||
-rw-r--r-- | scripts/lib/fontbuild/alignpoints.py | 31 | ||||
-rw-r--r-- | scripts/lib/fontbuild/curveFitPen.py | 107 | ||||
-rw-r--r-- | scripts/lib/fontbuild/instanceNames.py | 2 | ||||
-rw-r--r-- | scripts/lib/fontbuild/italics.py | 59 | ||||
-rw-r--r-- | scripts/lib/fontbuild/kerning.py | 112 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/markFeature.py | 137 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/mkmkFeature.py | 87 |
8 files changed, 205 insertions, 448 deletions
diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py index d1e870a..a4e5bac 100644 --- a/scripts/lib/fontbuild/Build.py +++ b/scripts/lib/fontbuild/Build.py @@ -13,23 +13,24 @@ # limitations under the License. +import ConfigParser +import os +import sys + from booleanOperations import BooleanOperationManager +from cu2qu.rf import fonts_to_quadratic +from fontTools.misc.transform import Transform from robofab.world import OpenFont -from fontbuild.mix import Mix,Master,narrowFLGlyph +from ufo2ft import compileOTF, compileTTF + +from fontbuild.decomposeGlyph import decomposeGlyph +from fontbuild.features import readFeatureFile, writeFeatureFile +from fontbuild.generateGlyph import generateGlyph from fontbuild.instanceNames import setNamesRF from fontbuild.italics import italicizeGlyph -from fontbuild.convertCurves import glyphCurvesToQuadratic +from fontbuild.markFeature import RobotoFeatureCompiler, RobotoKernWriter from fontbuild.mitreGlyph import mitreGlyph -from fontbuild.generateGlyph import generateGlyph -from fontTools.misc.transform import Transform -from fontbuild.kerning import makeKernFeature -from fontbuild.features import readFeatureFile, writeFeatureFile -from fontbuild.markFeature import GenerateFeature_mark -from fontbuild.mkmkFeature import GenerateFeature_mkmk -from fontbuild.decomposeGlyph import decomposeGlyph -import ConfigParser -import os -import sys +from fontbuild.mix import Mix,Master,narrowFLGlyph class FontProject: @@ -66,8 +67,8 @@ class FontProject: self.buildnumber = self.loadBuildNumber() self.buildOTF = False - self.autohintOTF = False - self.buildTTF = False + self.compatible = False + self.generatedFonts = [] def loadBuildNumber(self): @@ -120,18 +121,8 @@ class FontProject: if g.name == "uniFFFD": continue - - # 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) + + removeGlyphOverlap(g) if g.name in self.lessItalic: italicizeGlyph(f, g, 9, stemWidth=stemWidth) @@ -163,33 +154,49 @@ class FontProject: setNamesRF(f, n, foundry=self.config.get('main', 'foundry'), version=self.config.get('main', 'version')) - cleanCurves(f) + if not self.compatible: + cleanCurves(f) deleteGlyphs(f, self.deleteList) if kern: log(">> Generating kern classes") readFeatureFile(f, self.ot_kerningclasses) - makeKernFeature(f, self.ot_kerningclasses) log(">> Generating font files") - GenerateFeature_mark(f) - GenerateFeature_mkmk(f) ufoName = self.generateOutputPath(f, "ufo") f.save(ufoName) + self.generatedFonts.append(ufoName) if self.buildOTF: log(">> Generating OTF file") newFont = OpenFont(ufoName) otfName = self.generateOutputPath(f, "otf") - builtSuccessfully = saveOTF(newFont, otfName, autohint=self.autohintOTF) - if not builtSuccessfully: - sys.exit(1) + saveOTF(newFont, otfName) + + def generateTTFs(self): + """Build TTF for each font generated since last call to generateTTFs.""" - if self.buildTTF: - log(">> Generating TTF file") - import fontforge - otFont = fontforge.open(otfName) - otFont.generate(self.generateOutputPath(f, "ttf")) + fonts = [OpenFont(ufo) for ufo in self.generatedFonts] + self.generatedFonts = [] + + log(">> Converting curves to quadratic") + # using a slightly higher max error (e.g. 0.0025 em), dots will have + # fewer control points and look noticeably different + max_err = 0.002 + if self.compatible: + fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True) + else: + for font in fonts: + fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True) + + log(">> Generating TTF files") + for font in fonts: + ttfName = self.generateOutputPath(font, "ttf") + log(os.path.basename(ttfName)) + for glyph in font: + for contour in glyph: + contour.reverseContour() + saveOTF(font, ttfName, truetype=True) def transformGlyphMembers(g, m): @@ -274,30 +281,13 @@ def removeGlyphOverlap(glyph): manager.union(contours, glyph.getPointPen()) -def saveOTF(font, destFile, autohint=False): - """Save a RoboFab font as an OTF binary using ufo2fdk. - - Returns True on success, False otherwise. - """ - - from ufo2fdk import OTFCompiler - - # glyphs with multiple unicode values must be split up, due to FontTool's - # use of a name -> UV dictionary during cmap compilation - for glyph in font: - if len(glyph.unicodes) > 1: - newUV = glyph.unicodes.pop() - newGlyph = font.newGlyph("uni%04X" % newUV) - newGlyph.appendComponent(glyph.name) - newGlyph.unicode = newUV - newGlyph.width = glyph.width - - compiler = OTFCompiler() - reports = compiler.compile(font, destFile, autohint=autohint) - if autohint: - print reports["autohint"] - print reports["makeotf"] +def saveOTF(font, destFile, truetype=False): + """Save a RoboFab font as an OTF binary using ufo2fdk.""" - successMsg = ("makeotfexe [NOTE] Wrote new font file '%s'." % - os.path.basename(destFile)) - return successMsg in reports["makeotf"] + if truetype: + compiler = compileTTF + else: + compiler = compileOTF + otf = compiler(font, featureCompilerClass=RobotoFeatureCompiler, + kernWriter=RobotoKernWriter) + otf.save(destFile) diff --git a/scripts/lib/fontbuild/alignpoints.py b/scripts/lib/fontbuild/alignpoints.py index 1133716..e3bb539 100644 --- a/scripts/lib/fontbuild/alignpoints.py +++ b/scripts/lib/fontbuild/alignpoints.py @@ -13,9 +13,11 @@ # limitations under the License. +import math + import numpy as np from numpy.linalg import lstsq -import math + def alignCorners(glyph, va, subsegments): out = va.copy() @@ -28,7 +30,7 @@ def alignCorners(glyph, va, subsegments): # 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) @@ -66,7 +68,7 @@ def alignCorners(glyph, va, subsegments): 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: @@ -77,10 +79,11 @@ def subsegmentIndex(contourIndex, segmentIndex, subsegments): 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) @@ -89,6 +92,7 @@ def alignPoints(pts, start=None, end=None): 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" @@ -96,34 +100,36 @@ def findCorner(pp, nn): 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(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), + 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) @@ -133,6 +139,7 @@ def fitLineLSQ(pts): 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 @@ -147,7 +154,8 @@ def fitLine(pts): 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) @@ -155,6 +163,7 @@ def nearestPoint(a,b,c): 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]) diff --git a/scripts/lib/fontbuild/curveFitPen.py b/scripts/lib/fontbuild/curveFitPen.py index 7c232c0..f7c0cae 100644 --- a/scripts/lib/fontbuild/curveFitPen.py +++ b/scripts/lib/fontbuild/curveFitPen.py @@ -17,25 +17,24 @@ __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 numpy.linalg import norm +from robofab.pens.adapterPens import GuessSmoothPointPen 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 @@ -56,13 +55,13 @@ class SubsegmentsToCurvesPointPen(BasePointToSegmentPen): 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] + pp = [pt for pt, smooth, name, kwargs in points] if segmentType == "line": assert len(pp) == 1 if isSmooth: @@ -73,17 +72,18 @@ class SubsegmentsToCurvesPointPen(BasePointToSegmentPen): assert len(pp) == 3 if isSmooth: self.subPen.smoothCurveTo(*pp) - else: + 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) @@ -95,10 +95,10 @@ class SubsegmentsToCurvesPen(BasePen): 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 @@ -106,7 +106,7 @@ class SubsegmentsToCurvesPen(BasePen): 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] @@ -114,39 +114,39 @@ class SubsegmentsToCurvesPen(BasePen): 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)): + + 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: @@ -162,7 +162,7 @@ class SubsegmentsToCurvesPen(BasePen): 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, @@ -170,15 +170,13 @@ class SubsegmentsToCurvesPen(BasePen): 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) @@ -189,10 +187,10 @@ class SubsegmentPointPen(BasePointToSegmentPen): 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 @@ -210,9 +208,9 @@ class SubsegmentPointPen(BasePointToSegmentPen): 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] @@ -227,12 +225,13 @@ class SubsegmentPointPen(BasePointToSegmentPen): 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 @@ -240,7 +239,7 @@ class SubsegmentPen(BasePen): self.subsegments = [] self.startContour = (0,0) self.contourIndex = -1 - + def _moveTo(self, (x, y)): self.contourIndex += 1 self.segmentIndex = 0 @@ -250,7 +249,7 @@ class SubsegmentPen(BasePen): 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: @@ -262,7 +261,7 @@ class SubsegmentPen(BasePen): 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: @@ -277,46 +276,53 @@ class SubsegmentPen(BasePen): 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) + + # round values used by otherPen (a RoboFab SegmentToPointPen) to decide + # whether to delete duplicate points at start and end of contour + #TODO(jamesgk) figure out why we have to do this hack, then remove it + c = self.otherPen.contour + for i in [0, -1]: + c[i] = [[round(n, 5) for n in c[i][0]]] + list(c[i][1:]) + 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 + return curvePoints def fitBezierSimple(pts): @@ -363,14 +369,14 @@ def fitBezier(pts,tangent0=None,tangent3=None): 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], @@ -414,4 +420,3 @@ if __name__ == '__main__': [1,1] ]) print np.array(p.renderCurve(pts,10)) * 10 - diff --git a/scripts/lib/fontbuild/instanceNames.py b/scripts/lib/fontbuild/instanceNames.py index 281eb03..890b5a6 100644 --- a/scripts/lib/fontbuild/instanceNames.py +++ b/scripts/lib/fontbuild/instanceNames.py @@ -57,7 +57,7 @@ class InstanceNames: 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.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" diff --git a/scripts/lib/fontbuild/italics.py b/scripts/lib/fontbuild/italics.py index c889bd5..51d1f96 100644 --- a/scripts/lib/fontbuild/italics.py +++ b/scripts/lib/fontbuild/italics.py @@ -13,12 +13,18 @@ # limitations under the License. +import math + from fontTools.misc.transform import Transform -from robofab.world import RFont -from time import clock import numpy as np -import math -from alignpoints import alignCorners +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, whiten + +from fontbuild.alignpoints import alignCorners +from fontbuild.curveFitPen import fitGlyph, segmentGlyph + def italicizeGlyph(f, g, angle=10, stemWidth=185): unic = g.unicode #save unicode @@ -33,11 +39,11 @@ def italicizeGlyph(f, g, angle=10, stemWidth=185): 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) if unic > 0xFFFF: #restore unicode @@ -47,18 +53,20 @@ def italicizeGlyph(f, g, angle=10, stemWidth=185): def italicize(glyph, angle=12, stemWidth=180, xoffset=-50): CURVE_CORRECTION_WEIGHT = .03 CORNER_WEIGHT = 10 + + # decompose the glyph into smaller segments 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: @@ -73,20 +81,25 @@ def italicize(glyph, angle=12, stemWidth=180, xoffset=-50): # out = copyMeshDetails(va, out, e, 6) else: outCorrected = out + + # create a transform for italicizing 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) + + # apply the transform out = outCorrected + (centerSkew - center) out[:,1] = outCorrected[:,1] - + + # make some corrections 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] @@ -95,6 +108,8 @@ def italicize(glyph, angle=12, stemWidth=180, xoffset=-50): # gOut.width *= .97 # gOut.width += 10 # return gOut + + # recompose the glyph into original segments return fitGlyph(glyph, gOut, subsegments) @@ -111,13 +126,7 @@ def transformFLGlyphMembers(g, m, transformAnchors = True): 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 = [] @@ -132,6 +141,7 @@ def glyphToMesh(g): offset += len(c) return np.array(points), edges + def meshToGlyph(points, g): g1 = g.copy() j = 0 @@ -144,6 +154,7 @@ def meshToGlyph(points, g): j += 1 return g1 + def quantizeGradient(grad, book=None): if book == None: book = np.array([(1,0),(0,1),(0,-1),(-1,0)]) @@ -153,6 +164,7 @@ def quantizeGradient(grad, book=None): out[i] = normalize(v) return out + def findControlPointsInMesh(glyph, va, subsegments): controlPointIndices = np.zeros((len(va),1)) index = 0 @@ -166,7 +178,6 @@ def findControlPointsInMesh(glyph, va, subsegments): return controlPointIndices - def recompose(v, grad, e, smooth=1, P=None, distance=None): n = len(v) if distance == None: @@ -183,6 +194,7 @@ def recompose(v, grad, e, smooth=1, P=None, distance=None): out[:,i] = cg(P, f[:,i])[0] return out + def mP(v,e): n = len(v) M = np.zeros((n,n)) @@ -193,18 +205,21 @@ def mP(v,e): 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) @@ -214,19 +229,23 @@ def getNormal(a,b,c): # 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)) @@ -236,6 +255,7 @@ def labelConnected(e): label += 1 return labels + def copyGradDetails(a,b,e,scale=15): n = len(a) labels = labelConnected(e) @@ -245,6 +265,7 @@ def copyGradDetails(a,b,e,scale=15): 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) @@ -253,8 +274,6 @@ def copyMeshDetails(va,vb,e,scale=5,smooth=.01): return recompose(vb, grad, e, smooth=smooth) - - def condenseGlyph(glyph, scale=.8, stemWidth=185): ga, subsegments = segmentGlyph(glyph, 25) va, e = glyphToMesh(ga) @@ -273,7 +292,7 @@ def condenseGlyph(glyph, scale=.8, stemWidth=185): # 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) diff --git a/scripts/lib/fontbuild/kerning.py b/scripts/lib/fontbuild/kerning.py deleted file mode 100644 index c93e303..0000000 --- a/scripts/lib/fontbuild/kerning.py +++ /dev/null @@ -1,112 +0,0 @@ -# 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 feaTools import parser -from feaTools.writers.baseWriter import AbstractFeatureWriter - - -class KernFeatureWriter(AbstractFeatureWriter): - """Generates a kerning feature based on glyph class definitions. - - Uses the kerning rules contained in an RFont's kerning attribute, as well as - glyph classes from parsed OTF text. Class-based rules are set based on the - existing rules for their key glyphs. - """ - - def __init__(self, font): - self.kerning = font.kerning - self.leftClasses = [] - self.rightClasses = [] - self.classSizes = {} - - def write(self, linesep="\n"): - """Write kern feature.""" - - # maintain collections of different rule types - leftClassKerning, rightClassKerning, classPairKerning = {}, {}, {} - for leftName, leftContents in self.leftClasses: - leftKey = leftContents[0] - - # collect rules with two classes - for rightName, rightContents in self.rightClasses: - rightKey = rightContents[0] - pair = leftKey, rightKey - kerningVal = self.kerning[pair] - if kerningVal is None: - continue - classPairKerning[leftName, rightName] = kerningVal - self.kerning.remove(pair) - - # collect rules with left class and right glyph - for pair, kerningVal in self.kerning.getLeft(leftKey): - leftClassKerning[leftName, pair[1]] = kerningVal - self.kerning.remove(pair) - - # collect rules with left glyph and right class - for rightName, rightContents in self.rightClasses: - rightKey = rightContents[0] - for pair, kerningVal in self.kerning.getRight(rightKey): - rightClassKerning[pair[0], rightName] = kerningVal - self.kerning.remove(pair) - - # write the feature - self.ruleCount = 0 - lines = ["feature kern {"] - lines.append(self._writeKerning(self.kerning, linesep)) - lines.append(self._writeKerning(leftClassKerning, linesep, True)) - lines.append(self._writeKerning(rightClassKerning, linesep, True)) - lines.append(self._writeKerning(classPairKerning, linesep)) - lines.append("} kern;") - return linesep.join(lines) - - def _writeKerning(self, kerning, linesep, enum=False): - """Write kerning rules for a mapping of pairs to values.""" - - lines = [] - enum = "enum " if enum else "" - pairs = kerning.items() - pairs.sort() - for (left, right), val in pairs: - if enum: - rulesAdded = (self.classSizes.get(left, 1) * - self.classSizes.get(right, 1)) - else: - rulesAdded = 1 - self.ruleCount += rulesAdded - if self.ruleCount > 1024: - lines.append(" subtable;") - self.ruleCount = rulesAdded - lines.append(" %spos %s %s %d;" % (enum, left, right, val)) - return linesep.join(lines) - - def classDefinition(self, name, contents): - """Store a class definition as either a left- or right-hand class.""" - - if not name.startswith("@_"): - return - info = (name, contents) - if name.endswith("_L"): - self.leftClasses.append(info) - elif name.endswith("_R"): - self.rightClasses.append(info) - self.classSizes[name] = len(contents) - - -def makeKernFeature(font, text): - """Add a kern feature to the font, using a KernFeatureWriter.""" - - writer = KernFeatureWriter(font) - parser.parseFeatures(writer, text) - font.features.text += writer.write() diff --git a/scripts/lib/fontbuild/markFeature.py b/scripts/lib/fontbuild/markFeature.py index b617ef6..395e537 100755 --- a/scripts/lib/fontbuild/markFeature.py +++ b/scripts/lib/fontbuild/markFeature.py @@ -13,105 +13,38 @@ # limitations under the License.
-from fontbuild.features import updateFeature
-
-
-aliases = [["uni0430", "a"], ["uni0435", "e"], ["uni0440", "p"], ["uni0441", "c"], ["uni0445", "x"], ["uni0455", "s"], ["uni0456", "i"], ["uni0471", "psi"]]
-
-def GetAliaseName(gname):
- for i in range (len(aliases)):
- if (gname == aliases[i][1]):
- return aliases[i][0]
- return None
-
-def CreateAccNameList(font, acc_anchor_name, bCombAccentOnly = True):
- #combrange = range(0x0300,0x0370) + range(0x1AB0,0x1ABF) + range(0x1DC0,0x1DE0)
- lst = []
- for g in font:
- if bCombAccentOnly and g.width != 0: #((g.unicode < 0x0300) or (g.unicode > 0x362)):
- continue
- for anchor in g.anchors:
- if acc_anchor_name == anchor.name:
- lst.append(g.name)
- return lst
-
-def CreateAccGlyphList(font, acc_list, acc_anchor_name):
- g_list = []
- for g in font:
- if g.name in acc_list:
- for anchor in g.anchors:
- if acc_anchor_name == anchor.name:
- g_list.append([g.name, anchor.x, anchor.y])
- break
- return g_list
-
-
-def CreateGlyphList(font, acc_list, anchor_name):
- g_list = []
- for g in font:
- if g.name in acc_list:
- continue
- for anchor in g.anchors:
- if anchor_name == anchor.name:
- g_list.append([g.name, anchor.x, anchor.y])
- break
- return g_list
-
-def Create_mark_lookup(accent_g_list, base_g_list, lookupname, acc_class, lookAliases = True):
- txt = "lookup " + lookupname + " {\n"
-
- for acc in accent_g_list:
- txt += " markClass " + acc[0] + " <anchor " + `int(acc[1])` + " " + `int(acc[2])` + "> " + acc_class +";\n"
-
- for base in base_g_list:
- txt += " pos base " + base[0] + " <anchor " + `int(base[1])` + " " + `int(base[2])` + "> mark " + acc_class + ";\n"
- if (lookAliases):
- base2 = GetAliaseName(base[0])
- if (None == base2):
- continue
- txt += " pos base " + base2 + " <anchor " + `int(base[1])` + " " + `int(base[2])` + "> mark " + acc_class + ";\n"
-
- txt += "} " + lookupname + ";\n"
-
- return txt
-
-##### main ##############
-def GenerateFeature_mark(font):
-
- combination_anchor_list = [
- ["top", "_marktop", True, True],
- ["bottom", "_markbottom", True, True],
- ["top_dd", "_marktop_dd", True, False],
- ["bottom_dd", "_markbottom_dd", True, False],
- ["rhotichook", "_markrhotichook", False, False],
- ["top0315", "_marktop0315", False, False],
- ["parent_top", "_markparent_top", False, False],
- ["parenthesses.w1", "_markparenthesses.w1", False, False],
- ["parenthesses.w2", "_markparenthesses.w2", False, False],
- ["parenthesses.w3", "_markparenthesses.w3", False, False]
- ]
-
- text = "feature mark {\n"
-
- for n in range(len(combination_anchor_list)):
-
- accent_name_list = []
- accent_mark_list = []
- base_mark_list = []
-
- anchors_pair = combination_anchor_list[n]
- anchor_name = anchors_pair[0]
- acc_anchor_name = anchors_pair[1]
- comb_accent_only = anchors_pair[2]
- expand_to_composits = anchors_pair[3]
- lookupname = "mark"+`n+1`
- classname = "@MC_" + anchor_name
-
- accent_name_list = CreateAccNameList(font, acc_anchor_name, comb_accent_only)
- accent_mark_list = CreateAccGlyphList(font, accent_name_list, acc_anchor_name)
- base_mark_list = CreateGlyphList(font, accent_name_list, anchor_name)
- text += Create_mark_lookup(accent_mark_list, base_mark_list, lookupname, classname, expand_to_composits)
-
- text += "} mark;\n"
-
- updateFeature(font, "mark", text)
+from ufo2ft.kernFeatureWriter import KernFeatureWriter
+from ufo2ft.makeotfParts import FeatureOTFCompiler
+
+
+class RobotoFeatureCompiler(FeatureOTFCompiler):
+ def precompile(self):
+ self.overwriteFeatures = True
+
+ def setupAnchorPairs(self):
+ self.anchorPairs = [
+ ["top", "_marktop", True, True],
+ ["bottom", "_markbottom", True, True],
+ ["top_dd", "_marktop_dd", True, False],
+ ["bottom_dd", "_markbottom_dd", True, False],
+ ["rhotichook", "_markrhotichook", False, False],
+ ["top0315", "_marktop0315", False, False],
+ ["parent_top", "_markparent_top", False, False],
+ ["parenthesses.w1", "_markparenthesses.w1", False, False],
+ ["parenthesses.w2", "_markparenthesses.w2", False, False],
+ ["parenthesses.w3", "_markparenthesses.w3", False, False]]
+
+ self.mkmkAnchorPairs = [
+ ["mkmktop", "_marktop"],
+ ["mkmkbottom_acc", "_markbottom"]]
+
+ def setupAliases(self):
+ self.aliases = [
+ ["a", "uni0430"], ["e", "uni0435"], ["p", "uni0440"],
+ ["c", "uni0441"], ["x", "uni0445"], ["s", "uni0455"],
+ ["i", "uni0456"], ["psi", "uni0471"]]
+
+
+class RobotoKernWriter(KernFeatureWriter):
+ leftFeaClassRe = r"@_(.+)_L$"
+ rightFeaClassRe = r"@_(.+)_R$"
diff --git a/scripts/lib/fontbuild/mkmkFeature.py b/scripts/lib/fontbuild/mkmkFeature.py deleted file mode 100755 index 16f9313..0000000 --- a/scripts/lib/fontbuild/mkmkFeature.py +++ /dev/null @@ -1,87 +0,0 @@ -# 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 fontbuild.features import updateFeature
-
-
-def CreateAccNameList(font, acc_anchor_name):
- lst = []
- for g in font:
- for anchor in g.anchors:
- if acc_anchor_name == anchor.name:
- lst.append(g.name)
- return lst
-
-def CreateAccGlyphList(font, acc_list, acc_anchor_name):
- g_list = []
- for g in font:
- if g.name in acc_list:
- for anchor in g.anchors:
- if acc_anchor_name == anchor.name:
- g_list.append([g.name, anchor.x, anchor.y])
- break
- return g_list
-
-
-def CreateGlyphList(font, acc_list, anchor_name):
- g_list = []
- for g in font:
- for anchor in g.anchors:
- if anchor_name == anchor.name:
- g_list.append([g.name, anchor.x, anchor.y])
- break
- return g_list
-
-def Create_mkmk1(accent_g_list, base_g_list, lookupname, acc_class):
- txt = "lookup " + lookupname + " {\n"
- #acc_class = "@MC_mkmk"
- for acc in accent_g_list:
- txt += " markClass " + acc[0] + " <anchor " + `int(acc[1])` + " " + `int(acc[2])` + "> " + acc_class +";\n"
-
- for base in base_g_list:
- txt += " pos mark " + base[0] + " <anchor " + `int(base[1])` + " " + `int(base[2])` + "> mark " + acc_class + ";\n"
-
- txt += "} " + lookupname + ";\n"
-
- return txt
-
-
-##### main ##############
-def GenerateFeature_mkmk(font):
- text = "feature mkmk {\n"
-
- accent_name_list = []
- accent_mark_list = []
- base_mark_list = []
- anchor_name = "mkmktop"
- acc_anchor_name = "_marktop"
- accent_name_list = CreateAccNameList(font, acc_anchor_name)
- accent_mark_list = CreateAccGlyphList(font, accent_name_list, acc_anchor_name)
- base_mark_list = CreateGlyphList(font, accent_name_list, anchor_name)
- text += Create_mkmk1(accent_mark_list, base_mark_list, "mkmk1", "@MC_mkmk_top")
-
- accent_name_list = []
- accent_mark_list = []
- base_mark_list = []
- anchor_name = "mkmkbottom_acc"
- acc_anchor_name = "_markbottom"
- accent_name_list = CreateAccNameList(font, acc_anchor_name)
- accent_mark_list = CreateAccGlyphList(font, accent_name_list, acc_anchor_name)
- base_mark_list = CreateGlyphList(font, accent_name_list, anchor_name)
- text += Create_mkmk1(accent_mark_list, base_mark_list, "mkmk2", "@MC_mkmk_bottom")
-
- text += "} mkmk;\n"
-
- updateFeature(font, "mkmk", text)
|