Build.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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. import ConfigParser
  15. import os
  16. import sys
  17. from booleanOperations import BooleanOperationManager
  18. from cu2qu.ufo import fonts_to_quadratic
  19. from fontTools.misc.transform import Transform
  20. from robofab.world import OpenFont
  21. from ufo2ft import compileOTF, compileTTF
  22. from fontbuild.decomposeGlyph import decomposeGlyph
  23. from fontbuild.features import readFeatureFile, writeFeatureFile
  24. from fontbuild.generateGlyph import generateGlyph
  25. from fontbuild.instanceNames import setNamesRF
  26. from fontbuild.italics import italicizeGlyph
  27. from fontbuild.markFeature import RobotoFeatureCompiler, RobotoKernWriter
  28. from fontbuild.mix import Mix,Master
  29. class FontProject:
  30. def __init__(self, basefont, basedir, configfile):
  31. self.basefont = basefont
  32. self.basedir = basedir
  33. self.config = ConfigParser.RawConfigParser()
  34. self.configfile = os.path.join(self.basedir, configfile)
  35. self.config.read(self.configfile)
  36. self.diacriticList = [
  37. line.strip() for line in self.openResource("diacriticfile")
  38. if not line.startswith("#")]
  39. self.adobeGlyphList = dict(
  40. line.split(";") for line in self.openResource("agl_glyphlistfile")
  41. if not line.startswith("#"))
  42. self.glyphOrder = self.openResource("glyphorder")
  43. self.thinGlyphOrder = self.openResource("glyphorder_thin")
  44. # map exceptional glyph names in Roboto to names in the AGL
  45. roboNames = (
  46. ('Obar', 'Ocenteredtilde'), ('obar', 'obarred'),
  47. ('eturn', 'eturned'), ('Iota1', 'Iotaafrican'))
  48. for roboName, aglName in roboNames:
  49. self.adobeGlyphList[roboName] = self.adobeGlyphList[aglName]
  50. self.builddir = "out"
  51. self.decompose = self.config.get("glyphs","decompose").split()
  52. self.predecompose = self.config.get("glyphs","predecompose").split()
  53. self.lessItalic = self.config.get("glyphs","lessitalic").split()
  54. self.deleteList = self.config.get("glyphs","delete").split()
  55. self.noItalic = self.config.get("glyphs","noitalic").split()
  56. self.buildOTF = False
  57. self.compatible = False
  58. self.generatedFonts = []
  59. def openResource(self, name):
  60. with open(os.path.join(
  61. self.basedir, self.config.get("res", name))) as resourceFile:
  62. resource = resourceFile.read()
  63. return resource.splitlines()
  64. def generateOutputPath(self, font, ext):
  65. family = font.info.familyName.replace(" ", "")
  66. style = font.info.styleName.replace(" ", "")
  67. path = os.path.join(self.basedir, self.builddir, family + ext.upper())
  68. if not os.path.exists(path):
  69. os.makedirs(path)
  70. return os.path.join(path, "%s-%s.%s" % (family, style, ext))
  71. def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185):
  72. n = names.split("/")
  73. log("---------------------\n%s %s\n----------------------" %(n[0],n[1]))
  74. log(">> Mixing masters")
  75. if isinstance( mix, Mix):
  76. f = mix.generateFont(self.basefont)
  77. else:
  78. f = mix.copy()
  79. if italic == True:
  80. log(">> Italicizing")
  81. tweakAmmount = .085
  82. narrowAmmount = .93
  83. if names.find("Thin") != -1:
  84. tweakAmmount = .05
  85. if names.find("Condensed") != -1:
  86. narrowAmmount = .96
  87. i = 0
  88. for g in f:
  89. i += 1
  90. if i % 10 == 0: print g.name
  91. if g.name == "uniFFFD":
  92. continue
  93. removeGlyphOverlap(g)
  94. if g.name in self.lessItalic:
  95. italicizeGlyph(f, g, 9, stemWidth=stemWidth)
  96. elif False == (g.name in self.noItalic):
  97. italicizeGlyph(f, g, 10, stemWidth=stemWidth)
  98. if g.width != 0:
  99. g.width += 10
  100. # set the oblique flag in fsSelection
  101. f.info.openTypeOS2Selection.append(9)
  102. if swapSuffixes != None:
  103. for swap in swapSuffixes:
  104. swapList = [g.name for g in f if g.name.endswith(swap)]
  105. for gname in swapList:
  106. print gname
  107. swapContours(f, gname.replace(swap,""), gname)
  108. for gname in self.predecompose:
  109. if f.has_key(gname):
  110. decomposeGlyph(f, gname)
  111. log(">> Generating glyphs")
  112. generateGlyphs(f, self.diacriticList, self.adobeGlyphList)
  113. log(">> Copying features")
  114. readFeatureFile(f, self.basefont.features.text)
  115. log(">> Decomposing")
  116. for gname in self.decompose:
  117. if f.has_key(gname):
  118. decomposeGlyph(f, gname)
  119. setNamesRF(f, n, foundry=self.config.get('main', 'foundry'),
  120. version=self.config.get('main', 'version'))
  121. if not self.compatible:
  122. log(">> Removing overlaps")
  123. for g in f:
  124. removeGlyphOverlap(g)
  125. deleteGlyphs(f, self.deleteList)
  126. log(">> Generating font files")
  127. ufoName = self.generateOutputPath(f, "ufo")
  128. f.save(ufoName)
  129. self.generatedFonts.append(ufoName)
  130. if self.buildOTF:
  131. log(">> Generating OTF file")
  132. newFont = OpenFont(ufoName)
  133. otfName = self.generateOutputPath(f, "otf")
  134. saveOTF(
  135. newFont, otfName,
  136. self.thinGlyphOrder if "Thin" in otfName else self.glyphOrder)
  137. def generateTTFs(self):
  138. """Build TTF for each font generated since last call to generateTTFs."""
  139. fonts = [OpenFont(ufo) for ufo in self.generatedFonts]
  140. self.generatedFonts = []
  141. log(">> Converting curves to quadratic")
  142. # using a slightly higher max error (e.g. 0.0025 em), dots will have
  143. # fewer control points and look noticeably different
  144. max_err = 0.002
  145. if self.compatible:
  146. fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True)
  147. else:
  148. for font in fonts:
  149. fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True)
  150. log(">> Generating TTF files")
  151. for font in fonts:
  152. ttfName = self.generateOutputPath(font, "ttf")
  153. log(os.path.basename(ttfName))
  154. saveOTF(
  155. font, ttfName,
  156. self.thinGlyphOrder if "Thin" in ttfName else self.glyphOrder,
  157. truetype=True)
  158. def swapContours(f,gName1,gName2):
  159. try:
  160. g1 = f[gName1]
  161. g2 = f[gName2]
  162. except KeyError:
  163. log("swapGlyphs failed for %s %s" % (gName1, gName2))
  164. return
  165. g3 = g1.copy()
  166. while g1.contours:
  167. g1.removeContour(0)
  168. for contour in g2.contours:
  169. g1.appendContour(contour)
  170. g1.width = g2.width
  171. while g2.contours:
  172. g2.removeContour(0)
  173. for contour in g3.contours:
  174. g2.appendContour(contour)
  175. g2.width = g3.width
  176. def log(msg):
  177. print msg
  178. def generateGlyphs(f, glyphNames, glyphList={}):
  179. log(">> Generating diacritics")
  180. glyphnames = [gname for gname in glyphNames if not gname.startswith("#") and gname != ""]
  181. for glyphName in glyphNames:
  182. generateGlyph(f, glyphName, glyphList)
  183. def deleteGlyphs(f, deleteList):
  184. for name in deleteList:
  185. if f.has_key(name):
  186. f.removeGlyph(name)
  187. def removeGlyphOverlap(glyph):
  188. """Remove overlaps in contours from a glyph."""
  189. #TODO(jamesgk) verify overlaps exist first, as per library's recommendation
  190. manager = BooleanOperationManager()
  191. contours = glyph.contours
  192. glyph.clearContours()
  193. manager.union(contours, glyph.getPointPen())
  194. def saveOTF(font, destFile, glyphOrder, truetype=False):
  195. """Save a RoboFab font as an OTF binary using ufo2fdk."""
  196. if truetype:
  197. compiler = compileTTF
  198. else:
  199. compiler = compileOTF
  200. otf = compiler(font, featureCompilerClass=RobotoFeatureCompiler,
  201. kernWriter=RobotoKernWriter, glyphOrder=glyphOrder,
  202. useProductionNames=False)
  203. otf.save(destFile)