summaryrefslogtreecommitdiff
path: root/misc/pylib/fontbuild/Build.py
diff options
context:
space:
mode:
Diffstat (limited to 'misc/pylib/fontbuild/Build.py')
-rw-r--r--misc/pylib/fontbuild/Build.py300
1 files changed, 300 insertions, 0 deletions
diff --git a/misc/pylib/fontbuild/Build.py b/misc/pylib/fontbuild/Build.py
new file mode 100644
index 000000000..5046f9f91
--- /dev/null
+++ b/misc/pylib/fontbuild/Build.py
@@ -0,0 +1,300 @@
+# 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.
+
+
+import ConfigParser
+import os
+import sys
+
+from booleanOperations import BooleanOperationManager
+from cu2qu.ufo import fonts_to_quadratic
+from fontTools.misc.transform import Transform
+from robofab.world import OpenFont
+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 setInfoRF
+from fontbuild.italics import italicizeGlyph
+from fontbuild.markFeature import RobotoFeatureCompiler, RobotoKernWriter
+from fontbuild.mitreGlyph import mitreGlyph
+from fontbuild.mix import Mix,Master,narrowFLGlyph
+
+
+class FontProject:
+
+ def __init__(self, basefont, basedir, configfile, buildTag=''):
+ self.basefont = basefont
+ self.basedir = basedir
+ self.config = ConfigParser.RawConfigParser()
+ self.configfile = os.path.join(self.basedir, configfile)
+ self.config.read(self.configfile)
+ self.buildTag = buildTag
+
+ self.diacriticList = [
+ line.strip() for line in self.openResource("diacriticfile")
+ if not line.startswith("#")]
+ self.adobeGlyphList = dict(
+ line.split(";") for line in self.openResource("agl_glyphlistfile")
+ if not line.startswith("#"))
+ self.glyphOrder = self.openResource("glyphorder")
+
+ # 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()
+ self.lessItalic = self.config.get("glyphs","lessitalic").split()
+ self.deleteList = self.config.get("glyphs","delete").split()
+ self.noItalic = self.config.get("glyphs","noitalic").split()
+
+ self.buildOTF = False
+ self.compatible = False
+ self.generatedFonts = []
+
+ def openResource(self, name):
+ with open(os.path.join(
+ self.basedir, self.config.get("res", name))) as resourceFile:
+ resource = resourceFile.read()
+ return resource.splitlines()
+
+ 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,
+ italicMeanYCenter=-825, italicNarrowAmount=1):
+
+ 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 = mix.copy()
+
+ if italic == True:
+ log(">> Italicizing")
+ i = 0
+ for g in f:
+ i += 1
+ if i % 10 == 0: print g.name
+
+ if g.name == "uniFFFD":
+ continue
+
+ decomposeGlyph(f, g)
+ removeGlyphOverlap(g)
+
+ if g.name in self.lessItalic:
+ italicizeGlyph(f, g, 9, stemWidth=stemWidth,
+ meanYCenter=italicMeanYCenter,
+ narrowAmount=italicNarrowAmount)
+ elif g.name not in self.noItalic:
+ italicizeGlyph(f, g, 10, stemWidth=stemWidth,
+ meanYCenter=italicMeanYCenter,
+ narrowAmount=italicNarrowAmount)
+ if g.width != 0:
+ g.width += 10
+
+ # set the oblique flag in fsSelection
+ f.info.openTypeOS2Selection.append(9)
+
+ if swapSuffixes != None:
+ for swap in swapSuffixes:
+ swapList = [g.name for g in f if g.name.endswith(swap)]
+ for gname in swapList:
+ print gname
+ swapContours(f, gname.replace(swap,""), gname)
+ for gname in self.predecompose:
+ if f.has_key(gname):
+ decomposeGlyph(f, f[gname])
+
+ log(">> Generating glyphs")
+ generateGlyphs(f, self.diacriticList, self.adobeGlyphList)
+ log(">> Copying features")
+ readFeatureFile(f, self.basefont.features.text)
+ log(">> Decomposing")
+ for g in f:
+ if len(g.components) > 0:
+ decomposeGlyph(f, g)
+ # for gname in self.decompose:
+ # if f.has_key(gname):
+ # decomposeGlyph(f, f[gname])
+
+ copyrightHolderName = ''
+ if self.config.has_option('main', 'copyrightHolderName'):
+ copyrightHolderName = self.config.get('main', 'copyrightHolderName')
+
+ def getcfg(name, fallback=''):
+ if self.config.has_option('main', name):
+ return self.config.get('main', name)
+ else:
+ return fallback
+
+ setInfoRF(f, n, {
+ 'foundry': getcfg('foundry'),
+ 'foundryURL': getcfg('foundryURL'),
+ 'designer': getcfg('designer'),
+ 'copyrightHolderName': getcfg('copyrightHolderName'),
+ 'build': self.buildTag,
+ 'version': getcfg('version'),
+ 'license': getcfg('license'),
+ 'licenseURL': getcfg('licenseURL'),
+ })
+
+ if not self.compatible:
+ cleanCurves(f)
+ deleteGlyphs(f, self.deleteList)
+
+ log(">> Generating font files")
+ 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")
+ saveOTF(newFont, otfName, self.glyphOrder)
+
+ def generateTTFs(self):
+ """Build TTF for each font generated since last call to generateTTFs."""
+
+ 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.001
+ if self.compatible:
+ fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True, reverse_direction=True)
+ else:
+ for font in fonts:
+ fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True, reverse_direction=True)
+
+ log(">> Generating TTF files")
+ for font in fonts:
+ ttfName = self.generateOutputPath(font, "ttf")
+ log(os.path.basename(ttfName))
+ saveOTF(font, ttfName, self.glyphOrder, truetype=True)
+
+
+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 swapContours(f,gName1,gName2):
+ try:
+ g1 = f[gName1]
+ g2 = f[gName2]
+ except KeyError:
+ log("swapGlyphs failed for %s %s" % (gName1, gName2))
+ return
+ 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 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, glyphList)
+
+def cleanCurves(f):
+ log(">> Removing overlaps")
+ for g in f:
+ removeGlyphOverlap(g)
+
+ # log(">> Mitring sharp corners")
+ # for g in f:
+ # mitreGlyph(g, 3., .7)
+
+ # log(">> Converting curves to quadratic")
+ # for g in f:
+ # glyphCurvesToQuadratic(g)
+
+
+def deleteGlyphs(f, deleteList):
+ for name in deleteList:
+ 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, glyphOrder, truetype=False):
+ """Save a RoboFab font as an OTF binary using ufo2fdk."""
+
+ if truetype:
+ otf = compileTTF(font, featureCompilerClass=RobotoFeatureCompiler,
+ kernWriter=RobotoKernWriter, glyphOrder=glyphOrder,
+ convertCubics=False,
+ useProductionNames=False)
+ else:
+ otf = compileOTF(font, featureCompilerClass=RobotoFeatureCompiler,
+ kernWriter=RobotoKernWriter, glyphOrder=glyphOrder,
+ useProductionNames=False)
+ otf.save(destFile)