diff options
Diffstat (limited to 'misc/pylib/fontbuild/Build.pyx')
-rw-r--r-- | misc/pylib/fontbuild/Build.pyx | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/misc/pylib/fontbuild/Build.pyx b/misc/pylib/fontbuild/Build.pyx new file mode 100644 index 000000000..939122658 --- /dev/null +++ b/misc/pylib/fontbuild/Build.pyx @@ -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])) + if isinstance( mix, Mix): + log(">> Mixing masters") + f = mix.generateFont(self.basefont) + else: + f = mix.copy() + + if italic == True: + log(">> Italicizing") + i = 0 + for g in f: + decomposeGlyph(f, g) + removeGlyphOverlap(g) + + for g in f: + i += 1 + if i % 10 == 0: print g.name + + 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(">> Reading features") + # readFeatureFile(f, f.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'), + 'italicAngle': float(getcfg('italicAngle', '-12')), + }) + + 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) |