123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- # 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 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 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:
-
- def __init__(self, basefont, basedir, configfile, thinfont = None):
- self.basefont = basefont
- self.thinfont = thinfont
- self.basedir = basedir
- self.config = ConfigParser.RawConfigParser()
- self.configfile = self.basedir+"/"+configfile
- self.config.read(self.configfile)
-
- diacriticList = open(self.basedir + "/" + self.config.get("res","diacriticfile")).readlines()
- self.diacriticList = [line.strip() for line in diacriticList if not line.startswith("#")]
- 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()
- 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.buildnumber = self.loadBuildNumber()
-
- self.buildOTF = False
- self.autohintOTF = False
- self.buildTTF = False
-
-
- def loadBuildNumber(self):
- versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+")
- buildnumber = int(versionFile.read().strip())
- buildnumber = "%05d" %(int(buildnumber) + 1)
- print "BuildNumber: %s" %(buildnumber)
- versionFile.close()
- return buildnumber
-
- def incrementBuildNumber(self):
- if len(self.buildnumber) > 0:
- versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+")
- versionFile.seek(0)
- versionFile.write(self.buildnumber)
- versionFile.truncate()
- 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):
-
- 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")
- tweakAmmount = .085
- narrowAmmount = .93
- if names.find("Thin") != -1:
- tweakAmmount = .05
- if names.find("Condensed") != -1:
- narrowAmmount = .96
- i = 0
- for g in f:
- i += 1
- if i % 10 == 0: print g.name
-
- 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)
- if g.name in self.lessItalic:
- italicizeGlyph(f, g, 9, stemWidth=stemWidth)
- elif False == (g.name in self.noItalic):
- italicizeGlyph(f, g, 10, stemWidth=stemWidth)
- #elif g.name != ".notdef":
- # italicizeGlyph(g, 10, stemWidth=stemWidth)
- if g.width != 0:
- g.width += 10
- 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[gname])
- log(">> Generating glyphs")
- generateGlyphs(f, self.diacriticList, self.adobeGlyphList)
- log(">> Copying features")
- readFeatureFile(f, self.ot_classes + self.basefont.features.text)
- log(">> Decomposing")
- for gname in self.decompose:
- 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)
- 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)
- 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)
- 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, 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"]
|