summaryrefslogtreecommitdiff
path: root/scripts/lib/fontbuild
diff options
context:
space:
mode:
authorVasudev Kamath <kamathvasudev@gmail.com>2016-01-17 10:24:04 +0530
committerVasudev Kamath <kamathvasudev@gmail.com>2016-01-17 10:24:04 +0530
commitdbfdc17c919fbe0c0da0b11ad80977d415d52e9e (patch)
treebe487e0e58e17959036e6104542f57213ffc1f1a /scripts/lib/fontbuild
parent1352cf875dbb8a46fb0b4bf25a15b6870cf83a19 (diff)
parentdbeb1d1913b5e0b1609af63caa02c2785f832a26 (diff)
Merge tag 'upstream/0_20160106'
Upstream version 0~20160106 # gpg: Signature made Sunday 17 January 2016 10:24:04 AM IST using RSA key ID 87700B7E # gpg: Good signature from "Vasudev Kamath <kamathvasudev@gmail.com>" [ultimate] # gpg: aka "Vasudev Kamath (Debian Maint) <kamathvasudev@gmail.com>" [ultimate] # gpg: aka "Vasudev Kamath <vasudev@copyninja.info>" [ultimate]
Diffstat (limited to 'scripts/lib/fontbuild')
-rw-r--r--scripts/lib/fontbuild/Build.py118
-rw-r--r--scripts/lib/fontbuild/alignpoints.py31
-rw-r--r--scripts/lib/fontbuild/curveFitPen.py107
-rw-r--r--scripts/lib/fontbuild/instanceNames.py2
-rw-r--r--scripts/lib/fontbuild/italics.py59
-rw-r--r--scripts/lib/fontbuild/kerning.py112
-rwxr-xr-xscripts/lib/fontbuild/markFeature.py137
-rwxr-xr-xscripts/lib/fontbuild/mkmkFeature.py87
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)