diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index be220be80..3f6e1e060 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -48,6 +48,7 @@ def compileOTF( overlapsBackend=None, inplace=False, layerName=None, + _tables=None, ): """Create FontTools CFF font from a UFO. @@ -103,6 +104,7 @@ def compileOTF( glyphOrder=glyphOrder, roundTolerance=roundTolerance, optimizeCFF=optimizeCFF >= CFFOptimization.SPECIALIZE, + tables=_tables, ) otf = outlineCompiler.compile() @@ -232,7 +234,13 @@ def compileInterpolatableTTFs( logger.info("Building OpenType tables for %s", _LazyFontName(ufo)) outlineCompiler = outlineCompilerClass( - ufo, glyphSet=glyphSet, glyphOrder=glyphOrder + ufo, + glyphSet=glyphSet, + glyphOrder=glyphOrder, + tables=( + ["head", "hmtx", "glyf", "loca", "maxp", "post"] + if layerName else None + ), ) ttf = outlineCompiler.compile() @@ -357,12 +365,15 @@ def compileInterpolatableOTFsFromDS( featureCompilerClass=featureCompilerClass, featureWriters=featureWriters, glyphOrder=glyphOrder, - useProductionNames=useProductionNames, + useProductionNames=( + useProductionNames if source.layerName is None else False + ), optimizeCFF=CFFOptimization.NONE, roundTolerance=roundTolerance, removeOverlaps=False, overlapsBackend=None, inplace=inplace, + _tables=["head", "hmtx", "CFF", "maxp"] if source.layerName else None, ) ) diff --git a/Lib/ufo2ft/outlineCompiler.py b/Lib/ufo2ft/outlineCompiler.py index 60a4a979a..ffaacd0b5 100644 --- a/Lib/ufo2ft/outlineCompiler.py +++ b/Lib/ufo2ft/outlineCompiler.py @@ -43,11 +43,12 @@ def _isNonBMP(s): def _getVerticalOrigin(font, glyph): - font_ascender = font['hhea'].ascent if (hasattr(glyph, "verticalOrigin") and glyph.verticalOrigin is not None): verticalOrigin = glyph.verticalOrigin else: + hhea = font.get("hhea") + font_ascender = hhea.ascent if hhea is not None else 0 verticalOrigin = font_ascender return otRound(verticalOrigin) @@ -56,8 +57,20 @@ class BaseOutlineCompiler(object): """Create a feature-less outline binary.""" sfntVersion = None - - def __init__(self, font, glyphSet=None, glyphOrder=None): + tables = { + "head", + "hmtx", + "hhea", + "name", + "maxp", + "cmap", + "OS/2", + "post", + "vmtx", + "vhea", + } + + def __init__(self, font, glyphSet=None, glyphOrder=None, tables=None): self.ufo = font # use the previously filtered glyphSet, if any if glyphSet is None: @@ -73,6 +86,8 @@ def __init__(self, font, glyphSet=None, glyphOrder=None): self.fontBoundingBox = self.makeFontBoundingBox() # make a reusable character mapping self.unicodeToGlyphNameMapping = self.makeUnicodeToGlyphNameMapping() + if tables is not None: + self.tables = set(tables) def compile(self): """ @@ -94,6 +109,9 @@ def compile(self): for metric in vertical_metrics ) + # write the glyph order + self.otf.setGlyphOrder(self.glyphOrder) + # populate basic tables self.setupTable_head() self.setupTable_hmtx() @@ -210,6 +228,9 @@ def makeOfficialGlyphOrder(self, glyphOrder): # -------------- def setupTable_gasp(self): + if "gasp" not in self.tables: + return + self.otf["gasp"] = gasp = newTable("gasp") gasp_ranges = dict() for record in self.ufo.info.openTypeGaspRangeRecords: @@ -227,6 +248,8 @@ def setupTable_head(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "head" not in self.tables: + return self.otf["head"] = head = newTable("head") font = self.ufo @@ -287,6 +310,8 @@ def setupTable_name(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "name" not in self.tables: + return font = self.ufo self.otf["name"] = name = newTable("name") @@ -376,6 +401,9 @@ def setupTable_cmap(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "cmap" not in self.tables: + return + from fontTools.ttLib.tables._c_m_a_p import cmap_format_4 nonBMP = dict((k,v) for k,v in self.unicodeToGlyphNameMapping.items() if k > 65535) @@ -427,16 +455,19 @@ def setupTable_OS2(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "OS/2" not in self.tables: + return + self.otf["OS/2"] = os2 = newTable("OS/2") - hmtx = self.otf["hmtx"] font = self.ufo os2.version = 0x0004 # average glyph width - widths = [width for width, _ in hmtx.metrics.values() if width > 0] - if widths: - os2.xAvgCharWidth = otRound(sum(widths) / len(widths)) - else: - os2.xAvgCharWidth = 0 + os2.xAvgCharWidth = 0 + hmtx = self.otf.get("hmtx") + if hmtx is not None: + widths = [width for width, _ in hmtx.metrics.values() if width > 0] + if widths: + os2.xAvgCharWidth = otRound(sum(widths) / len(widths)) # weight and width classes os2.usWeightClass = getAttrWithFallback(font.info, "openTypeOS2WeightClass") os2.usWidthClass = getAttrWithFallback(font.info, "openTypeOS2WidthClass") @@ -585,6 +616,8 @@ def setupTable_hmtx(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "hmtx" not in self.tables: + return self.otf["hmtx"] = hmtx = newTable("hmtx") hmtx.metrics = {} @@ -602,12 +635,15 @@ def _setupTable_hhea_or_vhea(self, tag): Make the hhea table or the vhea table. This assume the hmtx or the vmtx were respectively made first. """ + if tag not in self.tables: + return + if tag == "hhea": isHhea = True else: isHhea = False self.otf[tag] = table = newTable(tag) - mtxTable = self.otf[tag[0] + "mtx"] + mtxTable = self.otf.get(tag[0] + "mtx") font = self.ufo if isHhea: table.tableVersion = 0x00010000 @@ -641,27 +677,28 @@ def _setupTable_hhea_or_vhea(self, tag): firstSideBearings = [] # left in hhea, top in vhea secondSideBearings = [] # right in hhea, bottom in vhea extents = [] - for glyphName in self.allGlyphs: - advance, firstSideBearing = mtxTable[glyphName] - advances.append(advance) - bounds = self.glyphBoundingBoxes[glyphName] - if bounds is None: - continue - if isHhea: - boundsAdvance = (bounds.xMax - bounds.xMin) - # equation from the hhea spec for calculating xMaxExtent: - # Max(lsb + (xMax - xMin)) - extent = firstSideBearing + boundsAdvance - else: - boundsAdvance = (bounds.yMax - bounds.yMin) - # equation from the vhea spec for calculating yMaxExtent: - # Max(tsb + (yMax - yMin)). - extent = firstSideBearing + boundsAdvance - secondSideBearing = advance - firstSideBearing - boundsAdvance - - firstSideBearings.append(firstSideBearing) - secondSideBearings.append(secondSideBearing) - extents.append(extent) + if mtxTable is not None: + for glyphName in self.allGlyphs: + advance, firstSideBearing = mtxTable[glyphName] + advances.append(advance) + bounds = self.glyphBoundingBoxes[glyphName] + if bounds is None: + continue + if isHhea: + boundsAdvance = (bounds.xMax - bounds.xMin) + # equation from the hhea spec for calculating xMaxExtent: + # Max(lsb + (xMax - xMin)) + extent = firstSideBearing + boundsAdvance + else: + boundsAdvance = (bounds.yMax - bounds.yMin) + # equation from the vhea spec for calculating yMaxExtent: + # Max(tsb + (yMax - yMin)). + extent = firstSideBearing + boundsAdvance + secondSideBearing = advance - firstSideBearing - boundsAdvance + + firstSideBearings.append(firstSideBearing) + secondSideBearings.append(secondSideBearing) + extents.append(extent) setattr(table, "advance%sMax" % ("Width" if isHhea else "Height"), max(advances) if advances else 0) @@ -705,6 +742,8 @@ def setupTable_vmtx(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "vmtx" not in self.tables: + return self.otf["vmtx"] = vmtx = newTable("vmtx") vmtx.metrics = {} @@ -726,6 +765,9 @@ def setupTable_VORG(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "VORG" not in self.tables: + return + self.otf["VORG"] = vorg = newTable("VORG") vorg.majorVersion = 1 vorg.minorVersion = 0 @@ -759,6 +801,9 @@ def setupTable_post(self): may override or supplement this method to handle the table creation in a different way if desired. """ + if "post" not in self.tables: + return + self.otf["post"] = post = newTable("post") font = self.ufo post.formatType = 3.0 @@ -820,12 +865,14 @@ class OutlineOTFCompiler(BaseOutlineCompiler): """Compile a .otf font with CFF outlines.""" sfntVersion = "OTTO" + tables = BaseOutlineCompiler.tables | {"CFF", "VORG"} def __init__( self, font, glyphSet=None, glyphOrder=None, + tables=None, roundTolerance=None, optimizeCFF=True, ): @@ -835,7 +882,7 @@ def __init__( # round all coordinates to integers by default self.roundTolerance = 0.5 super(OutlineOTFCompiler, self).__init__( - font, glyphSet=glyphSet, glyphOrder=glyphOrder) + font, glyphSet=glyphSet, glyphOrder=glyphOrder, tables=tables) self.optimizeCFF = optimizeCFF def makeGlyphsBoundingBoxes(self): @@ -909,9 +956,12 @@ def getCharStringForGlyph(self, glyph, private, globalSubrs): def setupTable_maxp(self): """Make the maxp table.""" + if "maxp" not in self.tables: + return self.otf["maxp"] = maxp = newTable("maxp") maxp.tableVersion = 0x00005000 + maxp.numGlyphs = len(self.glyphOrder) def setupOtherTables(self): self.setupTable_CFF() @@ -920,6 +970,8 @@ def setupOtherTables(self): def setupTable_CFF(self): """Make the CFF table.""" + if not {"CFF", "CFF "}.intersection(self.tables): + return self.otf["CFF "] = cff = newTable("CFF ") cff = cff.cff @@ -985,9 +1037,10 @@ def setupTable_CFF(self): unitsPerEm = otRound(getAttrWithFallback(info, "unitsPerEm")) topDict.FontMatrix = [1.0 / unitsPerEm, 0, 0, 1.0 / unitsPerEm, 0, 0] # populate the width values - if not any(hasattr(info, attr) and getattr(info, attr) is not None - for attr in ("postscriptDefaultWidthX", - "postscriptNominalWidthX")): + if (not any(hasattr(info, attr) and getattr(info, attr) is not None + for attr in ("postscriptDefaultWidthX", + "postscriptNominalWidthX")) + and "hmtx" in self.otf): # no custom values set in fontinfo.plist; compute optimal ones from fontTools.cffLib.width import optimizeWidths hmtx = self.otf['hmtx'] @@ -1058,8 +1111,6 @@ def setupTable_CFF(self): charStrings.charStrings[glyphName] = glyphID topDict.charset.append(glyphName) topDict.FontBBox = self.fontBoundingBox - # write the glyph order - self.otf.setGlyphOrder(self.glyphOrder) class OutlineTTFCompiler(BaseOutlineCompiler): @@ -1067,12 +1118,16 @@ class OutlineTTFCompiler(BaseOutlineCompiler): """ sfntVersion = "\000\001\000\000" + tables = BaseOutlineCompiler.tables | {"loca", "gasp", "glyf"} def setupTable_maxp(self): """Make the maxp table.""" + if "maxp" not in self.tables: + return self.otf["maxp"] = maxp = newTable("maxp") maxp.tableVersion = 0x00010000 + maxp.numGlyphs = len(self.glyphOrder) maxp.maxZones = 1 maxp.maxTwilightPoints = 0 maxp.maxStorage = 0 @@ -1086,6 +1141,9 @@ def setupTable_maxp(self): def setupTable_post(self): """Make a format 2 post table with the compiler's glyph order.""" super(OutlineTTFCompiler, self).setupTable_post() + if "post" not in self.otf: + return + post = self.otf["post"] post.formatType = 2.0 post.extraNames = [] @@ -1099,13 +1157,15 @@ def setupOtherTables(self): def setupTable_glyf(self): """Make the glyf table.""" + if not {"glyf", "loca"}.issubset(self.tables): + return self.otf["loca"] = newTable("loca") self.otf["glyf"] = glyf = newTable("glyf") glyf.glyphs = {} glyf.glyphOrder = self.glyphOrder - hmtx = self.otf["hmtx"] + hmtx = self.otf.get("hmtx") allGlyphs = self.allGlyphs for name in self.glyphOrder: glyph = allGlyphs[name] @@ -1117,7 +1177,11 @@ def setupTable_glyf(self): ttGlyph = Glyph() else: ttGlyph = pen.glyph() - if ttGlyph.isComposite() and self.autoUseMyMetrics: + if ( + ttGlyph.isComposite() + and hmtx is not None + and self.autoUseMyMetrics + ): self.autoUseMyMetrics(ttGlyph, name, hmtx) glyf[name] = ttGlyph diff --git a/requirements.txt b/requirements.txt index a8c31a5a2..3c5b275af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -fonttools[lxml,ufo]==3.35.0 +fonttools[lxml,ufo]==3.35.2 defcon==0.6.0 cu2qu==1.6.5 compreffor==0.4.6.post1 diff --git a/setup.py b/setup.py index 60c43cd07..53647cfa0 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ 'pytest>=2.8', ], install_requires=[ - "fonttools[ufo]>=3.35.0", + "fonttools[ufo]>=3.35.2", "defcon>=0.6.0", "cu2qu>=1.6.5", "compreffor>=0.4.5",