diff --git a/Lib/ufo2ft/outlineCompiler.py b/Lib/ufo2ft/outlineCompiler.py index 424f70689..8466b00e5 100644 --- a/Lib/ufo2ft/outlineCompiler.py +++ b/Lib/ufo2ft/outlineCompiler.py @@ -21,6 +21,7 @@ from fontTools.pens.t2CharStringPen import T2CharStringPen from fontTools.pens.ttGlyphPen import TTGlyphPointPen from fontTools.ttLib import TTFont, newTable +from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder from fontTools.ttLib.tables._g_l_y_f import USE_MY_METRICS, Glyph from fontTools.ttLib.tables._h_e_a_d import mac_epoch_diff from fontTools.ttLib.tables.O_S_2f_2 import Panose @@ -787,7 +788,7 @@ def _setupTable_hhea_or_vhea(self, tag): secondSideBearings = [] # right in hhea, bottom in vhea extents = [] if mtxTable is not None: - for glyphName in self.allGlyphs: + for glyphName in self.glyphOrder: advance, firstSideBearing = mtxTable[glyphName] advances.append(advance) bounds = self.glyphBoundingBoxes[glyphName] @@ -836,10 +837,18 @@ def _setupTable_hhea_or_vhea(self, tag): for i in reserved: setattr(table, "reserved%i" % i, 0) table.metricDataFormat = 0 - # glyph count - setattr( - table, "numberOf%sMetrics" % ("H" if isHhea else "V"), len(self.allGlyphs) - ) + # precompute the number of long{Hor,Ver}Metric records in 'hmtx' table + # so we don't need to compile the latter to get this updated + numLongMetrics = len(advances) + if numLongMetrics > 1: + lastAdvance = advances[-1] + while advances[numLongMetrics - 2] == lastAdvance: + numLongMetrics -= 1 + if numLongMetrics <= 1: + # all advances are equal + numLongMetrics = 1 + break + setattr(table, "numberOf%sMetrics" % ("H" if isHhea else "V"), numLongMetrics) def setupTable_hhea(self): """ @@ -1456,7 +1465,10 @@ def setupTable_post(self): post = self.otf["post"] post.formatType = 2.0 - post.extraNames = [] + # if we set extraNames = [], it will be automatically computed upon compile as + # we do below; if we do it upfront we can skip reloading in postProcessor. + post.extraNames = [g for g in self.glyphOrder if g not in standardGlyphOrder] + post.mapping = {} post.glyphOrder = self.glyphOrder @@ -1483,6 +1495,10 @@ def setupTable_glyf(self): self.autoUseMyMetrics(ttGlyph, name, hmtx) glyf[name] = ttGlyph + # update various maxp fields based on glyf without needing to compile the font + if "maxp" in self.otf: + self.otf["maxp"].recalc(self.otf) + @staticmethod def autoUseMyMetrics(ttGlyph, glyphName, hmtx): """Set the "USE_MY_METRICS" flag on the first component having the diff --git a/Lib/ufo2ft/postProcessor.py b/Lib/ufo2ft/postProcessor.py index ea0c39c95..56f2292cb 100644 --- a/Lib/ufo2ft/postProcessor.py +++ b/Lib/ufo2ft/postProcessor.py @@ -4,6 +4,7 @@ from io import BytesIO from fontTools.ttLib import TTFont +from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder from ufo2ft.constants import ( GLYPHS_DONT_USE_PRODUCTION_NAMES, @@ -41,13 +42,7 @@ def __init__(self, otf, ufo, glyphSet=None): self.ufo = ufo self.glyphSet = glyphSet if glyphSet is not None else ufo - # FIXME: Stop reloading all incoming fonts here. It ensures that 1) we - # get the final binary layout, which canonicalizes data for us and 2) - # can easily rename glyphs later. The former point should be fixed, as - # reloading is expensive and it is within reason for the compiler to - # spit out something that can be used without reloading. - # https://github.com/googlefonts/ufo2ft/issues/485 - self.otf = _reloadFont(otf) + self.otf = otf self._postscriptNames = ufo.lib.get("public.postscriptNames") @@ -155,6 +150,12 @@ def process_glyph_names(self, useProductionNames=None): if useProductionNames: logger.info("Renaming glyphs to final production names") + # We need to reload the font *before* renaming glyphs, since various + # tables may have been build/loaded using the original glyph names. + # After reloading, we can immediately set a new glyph order and update + # the tables (post or CFF) that stores the new postcript names; any + # other tables that get loaded subsequently will use the new glyph names. + self.otf = _reloadFont(self.otf) self._rename_glyphs_from_ufo() else: @@ -163,7 +164,11 @@ def process_glyph_names(self, useProductionNames=None): "Dropping glyph names from CFF 1.0 is currently unsupported" ) else: + # To drop glyph names from TTF or CFF2, we must reload the font *after* + # setting the post format to 3.0, since other tables may still use + # the old glyph names. self.set_post_table_format(self.otf, 3.0) + self.otf = _reloadFont(self.otf) def _rename_glyphs_from_ufo(self): """Rename glyphs using ufo.lib.public.postscriptNames in UFO.""" @@ -172,16 +177,17 @@ def _rename_glyphs_from_ufo(self): @staticmethod def rename_glyphs(otf, rename_map): - otf.setGlyphOrder([rename_map.get(n, n) for n in otf.getGlyphOrder()]) + newGlyphOrder = [rename_map.get(n, n) for n in otf.getGlyphOrder()] + otf.setGlyphOrder(newGlyphOrder) - # we need to compile format 2 'post' table so that the 'extraNames' - # attribute is updated with the list of the names outside the - # standard Macintosh glyph order; otherwise, if one dumps the font - # to TTX directly before compiling first, the post table will not - # contain the extraNames. if "post" in otf and otf["post"].formatType == 2.0: - otf["post"].extraNames = [] - otf["post"].compile(otf) + # annoyingly we need to update extraNames to match the new glyph order, + # otherwise, if dumping the font to TTX directly before compiling first, + # the post table will not contain the extraNames... + otf["post"].extraNames = [ + g for g in newGlyphOrder if g not in standardGlyphOrder + ] + otf["post"].mapping = {} cff_tag = "CFF " if "CFF " in otf else "CFF2" if "CFF2" in otf else None if cff_tag == "CFF " or (cff_tag == "CFF2" and otf.isLoaded(cff_tag)): @@ -277,11 +283,16 @@ def set_post_table_format(otf, formatType): raise NotImplementedError(formatType) post = otf.get("post") - if post and post.formatType != formatType: - logger.info("Setting post.formatType = %s", formatType) - post.formatType = formatType + if post: + if post.formatType != formatType: + logger.info("Setting post.formatType = %s", formatType) + post.formatType = formatType + # we want to update extraNames list even if formatType is the same + # so we don't have to reload the font if formatType == 2.0: - post.extraNames = [] + post.extraNames = [ + g for g in otf.getGlyphOrder() if g not in standardGlyphOrder + ] post.mapping = {} else: for attr in ("extraNames", "mapping"): diff --git a/requirements.txt b/requirements.txt index 0abfb0ff0..e69fd795a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -fonttools[lxml,ufo]==4.34.0 +fonttools[lxml,ufo]==4.38.0 defcon==0.10.0 cu2qu==1.6.7.post1 compreffor==0.5.1.post1 diff --git a/setup.py b/setup.py index d8d998204..e21e1a39a 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup_requires=pytest_runner + wheel + ["setuptools_scm"], tests_require=["pytest>=2.8"], install_requires=[ - "fonttools[ufo]>=4.34.0", + "fonttools[ufo]>=4.38.0", "cu2qu>=1.6.7", "cffsubr>=0.2.8", "booleanOperations>=0.9.0", diff --git a/tests/data/DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx b/tests/data/DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx index cf25c07c7..d4431493b 100644 --- a/tests/data/DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx +++ b/tests/data/DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx @@ -384,22 +384,22 @@ 900 300 -900 -300 vlineto - 0 40 10 20 -10 0 14 1 blend + 0 40 10 20 -10 0 14.30922 1 blend hmoveto 10 10 -10 hlineto - 0 40 10 20 -10 0 14 1 blend + 0 40 10 20 -10 0 14.30922 1 blend hmoveto 10 10 -10 hlineto - 0 40 10 20 -10 0 14 1 blend + 0 40 10 20 -10 0 14.30922 1 blend hmoveto 10 10 -10 hlineto - 0 40 10 20 -10 0 14 1 blend + 0 40 10 20 -10 0 14.30922 1 blend hmoveto 10 10 -10 hlineto