Build.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. # Copyright 2015 Google Inc. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from booleanOperations import BooleanOperationManager
  15. from robofab.world import OpenFont
  16. from fontbuild.mix import Mix,Master,narrowFLGlyph
  17. from fontbuild.instanceNames import setNamesRF
  18. from fontbuild.italics import italicizeGlyph
  19. from fontbuild.convertCurves import glyphCurvesToQuadratic
  20. from fontbuild.mitreGlyph import mitreGlyph
  21. from fontbuild.generateGlyph import generateGlyph
  22. from fontTools.misc.transform import Transform
  23. from fontbuild.kerning import makeKernFeature
  24. from fontbuild.features import readFeatureFile, writeFeatureFile
  25. from fontbuild.markFeature import GenerateFeature_mark
  26. from fontbuild.mkmkFeature import GenerateFeature_mkmk
  27. from fontbuild.decomposeGlyph import decomposeGlyph
  28. import ConfigParser
  29. import os
  30. import sys
  31. class FontProject:
  32. def __init__(self, basefont, basedir, configfile, thinfont = None):
  33. self.basefont = basefont
  34. self.thinfont = thinfont
  35. self.basedir = basedir
  36. self.config = ConfigParser.RawConfigParser()
  37. self.configfile = self.basedir+"/"+configfile
  38. self.config.read(self.configfile)
  39. diacriticList = open(self.basedir + "/" + self.config.get("res","diacriticfile")).readlines()
  40. self.diacriticList = [line.strip() for line in diacriticList if not line.startswith("#")]
  41. self.ot_classes = open(self.basedir + "/" + self.config.get("res","ot_classesfile")).read()
  42. self.ot_kerningclasses = open(self.basedir + "/" + self.config.get("res","ot_kerningclassesfile")).read()
  43. #self.ot_features = open(self.basedir + "/" + self.config.get("res","ot_featuresfile")).read()
  44. adobeGlyphList = open(self.basedir + "/" + self.config.get("res", "agl_glyphlistfile")).readlines()
  45. self.adobeGlyphList = dict([line.split(";") for line in adobeGlyphList if not line.startswith("#")])
  46. # map exceptional glyph names in Roboto to names in the AGL
  47. roboNames = (
  48. ('Obar', 'Ocenteredtilde'), ('obar', 'obarred'),
  49. ('eturn', 'eturned'), ('Iota1', 'Iotaafrican'))
  50. for roboName, aglName in roboNames:
  51. self.adobeGlyphList[roboName] = self.adobeGlyphList[aglName]
  52. self.builddir = "out"
  53. self.decompose = self.config.get("glyphs","decompose").split()
  54. self.predecompose = self.config.get("glyphs","predecompose").split()
  55. self.lessItalic = self.config.get("glyphs","lessitalic").split()
  56. self.deleteList = self.config.get("glyphs","delete").split()
  57. self.noItalic = self.config.get("glyphs","noitalic").split()
  58. self.buildnumber = self.loadBuildNumber()
  59. self.buildOTF = False
  60. self.autohintOTF = False
  61. self.buildTTF = False
  62. def loadBuildNumber(self):
  63. versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+")
  64. buildnumber = int(versionFile.read().strip())
  65. buildnumber = "%05d" %(int(buildnumber) + 1)
  66. print "BuildNumber: %s" %(buildnumber)
  67. versionFile.close()
  68. return buildnumber
  69. def incrementBuildNumber(self):
  70. if len(self.buildnumber) > 0:
  71. versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+")
  72. versionFile.seek(0)
  73. versionFile.write(self.buildnumber)
  74. versionFile.truncate()
  75. versionFile.close()
  76. else:
  77. raise Exception("Empty build number")
  78. def generateOutputPath(self, font, ext):
  79. family = font.info.familyName.replace(" ", "")
  80. style = font.info.styleName.replace(" ", "")
  81. path = os.path.join(self.basedir, self.builddir, family + ext.upper())
  82. if not os.path.exists(path):
  83. os.makedirs(path)
  84. return os.path.join(path, "%s-%s.%s" % (family, style, ext))
  85. def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185, kern=True):
  86. n = names.split("/")
  87. log("---------------------\n%s %s\n----------------------" %(n[0],n[1]))
  88. log(">> Mixing masters")
  89. if isinstance( mix, Mix):
  90. f = mix.generateFont(self.basefont)
  91. else:
  92. f = mix.copy()
  93. if italic == True:
  94. log(">> Italicizing")
  95. tweakAmmount = .085
  96. narrowAmmount = .93
  97. if names.find("Thin") != -1:
  98. tweakAmmount = .05
  99. if names.find("Condensed") != -1:
  100. narrowAmmount = .96
  101. i = 0
  102. for g in f:
  103. i += 1
  104. if i % 10 == 0: print g.name
  105. if g.name == "uniFFFD":
  106. continue
  107. # if i < 24:
  108. # continue
  109. # if i > 86:
  110. # for i,g in enumerate(fl.font.glyphs):
  111. # fl.UpdateGlyph(i)
  112. # # break
  113. # assert False
  114. # print g.name
  115. # if self.thinfont != None:
  116. # narrowFLGlyph(g,self.thinfont.getGlyph(g.name),factor=narrowAmmount)
  117. if g.name in self.lessItalic:
  118. italicizeGlyph(f, g, 9, stemWidth=stemWidth)
  119. elif False == (g.name in self.noItalic):
  120. italicizeGlyph(f, g, 10, stemWidth=stemWidth)
  121. #elif g.name != ".notdef":
  122. # italicizeGlyph(g, 10, stemWidth=stemWidth)
  123. if g.width != 0:
  124. g.width += 10
  125. if swapSuffixes != None:
  126. for swap in swapSuffixes:
  127. swapList = [g.name for g in f if g.name.endswith(swap)]
  128. for gname in swapList:
  129. print gname
  130. swapContours(f, gname.replace(swap,""), gname)
  131. for gname in self.predecompose:
  132. if f.has_key(gname):
  133. decomposeGlyph(f[gname])
  134. log(">> Generating glyphs")
  135. generateGlyphs(f, self.diacriticList, self.adobeGlyphList)
  136. log(">> Copying features")
  137. readFeatureFile(f, self.ot_classes + self.basefont.features.text)
  138. log(">> Decomposing")
  139. for gname in self.decompose:
  140. if f.has_key(gname):
  141. decomposeGlyph(f[gname])
  142. setNamesRF(f, n, foundry=self.config.get('main', 'foundry'),
  143. version=self.config.get('main', 'version'))
  144. cleanCurves(f)
  145. deleteGlyphs(f, self.deleteList)
  146. if kern:
  147. log(">> Generating kern classes")
  148. readFeatureFile(f, self.ot_kerningclasses)
  149. makeKernFeature(f, self.ot_kerningclasses)
  150. log(">> Generating font files")
  151. GenerateFeature_mark(f)
  152. GenerateFeature_mkmk(f)
  153. ufoName = self.generateOutputPath(f, "ufo")
  154. f.save(ufoName)
  155. if self.buildOTF:
  156. log(">> Generating OTF file")
  157. newFont = OpenFont(ufoName)
  158. otfName = self.generateOutputPath(f, "otf")
  159. builtSuccessfully = saveOTF(newFont, otfName, autohint=self.autohintOTF)
  160. if not builtSuccessfully:
  161. sys.exit(1)
  162. if self.buildTTF:
  163. log(">> Generating TTF file")
  164. import fontforge
  165. otFont = fontforge.open(otfName)
  166. otFont.generate(self.generateOutputPath(f, "ttf"))
  167. def transformGlyphMembers(g, m):
  168. g.width = int(g.width * m.a)
  169. g.Transform(m)
  170. for a in g.anchors:
  171. p = Point(a.p)
  172. p.Transform(m)
  173. a.p = p
  174. for c in g.components:
  175. # Assumes that components have also been individually transformed
  176. p = Point(0,0)
  177. d = Point(c.deltas[0])
  178. d.Transform(m)
  179. p.Transform(m)
  180. d1 = d - p
  181. c.deltas[0].x = d1.x
  182. c.deltas[0].y = d1.y
  183. s = Point(c.scale)
  184. s.Transform(m)
  185. #c.scale = s
  186. def swapContours(f,gName1,gName2):
  187. try:
  188. g1 = f[gName1]
  189. g2 = f[gName2]
  190. except KeyError:
  191. log("swapGlyphs failed for %s %s" % (gName1, gName2))
  192. return
  193. g3 = g1.copy()
  194. while g1.contours:
  195. g1.removeContour(0)
  196. for contour in g2.contours:
  197. g1.appendContour(contour)
  198. g1.width = g2.width
  199. while g2.contours:
  200. g2.removeContour(0)
  201. for contour in g3.contours:
  202. g2.appendContour(contour)
  203. g2.width = g3.width
  204. def log(msg):
  205. print msg
  206. def generateGlyphs(f, glyphNames, glyphList={}):
  207. log(">> Generating diacritics")
  208. glyphnames = [gname for gname in glyphNames if not gname.startswith("#") and gname != ""]
  209. for glyphName in glyphNames:
  210. generateGlyph(f, glyphName, glyphList)
  211. def cleanCurves(f):
  212. log(">> Removing overlaps")
  213. for g in f:
  214. removeGlyphOverlap(g)
  215. # log(">> Mitring sharp corners")
  216. # for g in f:
  217. # mitreGlyph(g, 3., .7)
  218. # log(">> Converting curves to quadratic")
  219. # for g in f:
  220. # glyphCurvesToQuadratic(g)
  221. def deleteGlyphs(f, deleteList):
  222. for name in deleteList:
  223. if f.has_key(name):
  224. f.removeGlyph(name)
  225. def removeGlyphOverlap(glyph):
  226. """Remove overlaps in contours from a glyph."""
  227. #TODO(jamesgk) verify overlaps exist first, as per library's recommendation
  228. manager = BooleanOperationManager()
  229. contours = glyph.contours
  230. glyph.clearContours()
  231. manager.union(contours, glyph.getPointPen())
  232. def saveOTF(font, destFile, autohint=False):
  233. """Save a RoboFab font as an OTF binary using ufo2fdk.
  234. Returns True on success, False otherwise.
  235. """
  236. from ufo2fdk import OTFCompiler
  237. # glyphs with multiple unicode values must be split up, due to FontTool's
  238. # use of a name -> UV dictionary during cmap compilation
  239. for glyph in font:
  240. if len(glyph.unicodes) > 1:
  241. newUV = glyph.unicodes.pop()
  242. newGlyph = font.newGlyph("uni%04X" % newUV)
  243. newGlyph.appendComponent(glyph.name)
  244. newGlyph.unicode = newUV
  245. newGlyph.width = glyph.width
  246. compiler = OTFCompiler()
  247. reports = compiler.compile(font, destFile, autohint=autohint)
  248. if autohint:
  249. print reports["autohint"]
  250. print reports["makeotf"]
  251. successMsg = ("makeotfexe [NOTE] Wrote new font file '%s'." %
  252. os.path.basename(destFile))
  253. return successMsg in reports["makeotf"]