summaryrefslogtreecommitdiff
path: root/scripts/lib/fontbuild/Build.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/fontbuild/Build.py')
-rw-r--r--scripts/lib/fontbuild/Build.py277
1 files changed, 154 insertions, 123 deletions
diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py
index 2a7e670..490fe45 100644
--- a/scripts/lib/fontbuild/Build.py
+++ b/scripts/lib/fontbuild/Build.py
@@ -1,17 +1,35 @@
-from FL import *
+# 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 booleanOperations import BooleanOperationManager
+from robofab.world import OpenFont
from fontbuild.mix import Mix,Master,narrowFLGlyph
-from fontbuild.instanceNames import setNames
+from fontbuild.instanceNames import setNamesRF
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
-from fontbuild.features import CreateFeaFile
+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
class FontProject:
@@ -29,7 +47,16 @@ class FontProject:
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()
+ adobeGlyphList = open(self.basedir + "/" + self.config.get("res", "agl_glyphlistfile")).readlines()
+ self.adobeGlyphList = dict([line.split(";") for line in adobeGlyphList if not line.startswith("#")])
+ # map exceptional glyph names in Roboto to names in the AGL
+ roboNames = (
+ ('Obar', 'Ocenteredtilde'), ('obar', 'obarred'),
+ ('eturn', 'eturned'), ('Iota1', 'Iotaafrican'))
+ for roboName, aglName in roboNames:
+ self.adobeGlyphList[roboName] = self.adobeGlyphList[aglName]
+
self.builddir = "out"
self.decompose = self.config.get("glyphs","decompose").split()
self.predecompose = self.config.get("glyphs","predecompose").split()
@@ -38,7 +65,9 @@ class FontProject:
self.noItalic = self.config.get("glyphs","noitalic").split()
self.buildnumber = self.loadBuildNumber()
- self.buldVFBandFEA = False
+ self.buildOTF = False
+ self.autohintOTF = False
+ self.buildTTF = False
def loadBuildNumber(self):
@@ -58,7 +87,14 @@ class FontProject:
versionFile.close()
else:
raise Exception("Empty build number")
-
+
+ def generateOutputPath(self, font, ext):
+ family = font.info.familyName.replace(" ", "")
+ style = font.info.styleName.replace(" ", "")
+ path = os.path.join(self.basedir, self.builddir, family + ext.upper())
+ if not os.path.exists(path):
+ os.makedirs(path)
+ return os.path.join(path, "%s-%s.%s" % (family, style, ext))
def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185, kern=True):
@@ -68,13 +104,9 @@ class FontProject:
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
+ f = mix.copy()
if italic == True:
log(">> Italicizing")
- fl.UpdateFont(fl.ifont)
tweakAmmount = .085
narrowAmmount = .93
if names.find("Thin") != -1:
@@ -82,8 +114,7 @@ class FontProject:
if names.find("Condensed") != -1:
narrowAmmount = .96
i = 0
- for g in f.glyphs:
-
+ for g in f:
i += 1
if i % 10 == 0: print g.name
@@ -101,95 +132,65 @@ class FontProject:
# 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)
+ if g.name in self.lessItalic:
+ italicizeGlyph(f, g, 9, stemWidth=stemWidth)
elif False == (g.name in self.noItalic):
- italicizeGlyph(g, 10, stemWidth=stemWidth)
+ italicizeGlyph(f, g, 10, stemWidth=stemWidth)
#elif g.name != ".notdef":
# italicizeGlyph(g, 10, stemWidth=stemWidth)
-
- g.RemoveOverlap()
-
if g.width != 0:
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)]
+ swapList = [g.name for g in f if g.name.endswith(swap)]
for gname in swapList:
print gname
- swapGlyphs(f, gname.replace(swap,""), gname)
+ swapContours(f, gname.replace(swap,""), gname)
for gname in self.predecompose:
- g = f[f.FindGlyph(gname)]
- if g != None:
- g.Decompose()
+ if f.has_key(gname):
+ decomposeGlyph(f[gname])
log(">> Generating glyphs")
- generateGlyphs(f, self.diacriticList)
+ generateGlyphs(f, self.diacriticList, self.adobeGlyphList)
log(">> Copying features")
- f.ot_classes = self.ot_classes
- copyFeatures(self.basefont,f)
- fl.UpdateFont(index)
+ readFeatureFile(f, self.ot_classes + self.basefont.features.text)
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)
+ if f.has_key(gname):
+ decomposeGlyph(f[gname])
+
+ setNamesRF(f, n, foundry=self.config.get('main', 'foundry'),
+ version=self.config.get('main', 'version'))
cleanCurves(f)
- deleteGlyphs(f,self.deleteList)
-
+ deleteGlyphs(f, self.deleteList)
+
if kern:
log(">> Generating kern classes")
- generateFLKernClassesFromOTString(f,self.ot_kerningclasses)
- kern = f.MakeKernFeature()
- kern_exist = False
- for fea_id in range (len(f.features)):
- if "kern" == f.features[fea_id].tag:
- f.features[fea_id] = kern
- kern_exist = True
- if (False == kern_exist):
- f.features.append(kern)
-
- directoryName = n[0].replace(" ","")
-
- if self.buldVFBandFEA:
- log(">> Generating VFB files")
- directoryPath = "%s/%s/%sVFB"%(self.basedir,self.builddir,directoryName)
- if not os.path.exists(directoryPath):
- os.makedirs(directoryPath)
- flName = "%s/%s.vfb"%(directoryPath,f.font_name)
- fl.GenerateFont(fl.ifont,ftFONTLAB,flName)
-
+ readFeatureFile(f, self.ot_kerningclasses)
+ makeKernFeature(f, self.ot_kerningclasses)
+
log(">> Generating font files")
- 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)
-
- if self.buldVFBandFEA:
- log(">> Generating FEA files")
- GenerateFeature_mark(f)
- GenerateFeature_mkmk(f)
- feaName = "%s/%s.fea"%(directoryPath,f.font_name)
- CreateFeaFile(f, feaName)
-
- f.modified = 0
- #fl.Close(index)
+ GenerateFeature_mark(f)
+ GenerateFeature_mkmk(f)
+ ufoName = self.generateOutputPath(f, "ufo")
+ f.save(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)
+
+ if self.buildTTF:
+ log(">> Generating TTF file")
+ import fontforge
+ otFont = fontforge.open(otfName)
+ otFont.generate(self.generateOutputPath(f, "ttf"))
+
def transformGlyphMembers(g, m):
g.width = int(g.width * m.a)
@@ -211,62 +212,92 @@ def transformGlyphMembers(g, m):
s.Transform(m)
#c.scale = s
-def swapGlyphs(f,gName1,gName2):
+def swapContours(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))
+ g1 = f[gName1]
+ g2 = f[gName2]
+ except KeyError:
+ 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())
-
+ g3 = g1.copy()
+
+ while g1.contours:
+ g1.removeContour(0)
+ for contour in g2.contours:
+ g1.appendContour(contour)
+ g1.width = g2.width
+
+ while g2.contours:
+ g2.removeContour(0)
+ for contour in g3.contours:
+ g2.appendContour(contour)
+ g2.width = g3.width
+
+
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):
+def generateGlyphs(f, glyphNames, glyphList={}):
log(">> Generating diacritics")
glyphnames = [gname for gname in glyphNames if not gname.startswith("#") and gname != ""]
for glyphName in glyphNames:
- generateGlyph(f, glyphName)
+ generateGlyph(f, glyphName, glyphList)
def cleanCurves(f):
log(">> Removing overlaps")
- for g in f.glyphs:
- g.UnselectAll()
- g.RemoveOverlap()
+ for g in f:
+ removeGlyphOverlap(g)
- log(">> Mitring sharp corners")
- # for g in f.glyphs:
+ # log(">> Mitring sharp corners")
+ # for g in f:
# mitreGlyph(g, 3., .7)
- log(">> Converting curves to quadratic")
- # for g in f.glyphs:
+ # log(">> Converting curves to quadratic")
+ # for g in f:
# glyphCurvesToQuadratic(g)
-
-def deleteGlyphs(f,deleteList):
- fl.Unselect()
+
+
+def deleteGlyphs(f, deleteList):
for name in deleteList:
- glyphIndex = f.FindGlyph(name)
- if glyphIndex != -1:
- del f.glyphs[glyphIndex]
- fl.UpdateFont()
+ if f.has_key(name):
+ f.removeGlyph(name)
+
+
+def removeGlyphOverlap(glyph):
+ """Remove overlaps in contours from a glyph."""
+ #TODO(jamesgk) verify overlaps exist first, as per library's recommendation
+ manager = BooleanOperationManager()
+ contours = glyph.contours
+ glyph.clearContours()
+ 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"]
+
+ successMsg = ("makeotfexe [NOTE] Wrote new font file '%s'." %
+ os.path.basename(destFile))
+ return successMsg in reports["makeotf"]