Skip to content

Commit

Permalink
only build minimal metrics/outline tables for sparse layer masters
Browse files Browse the repository at this point in the history
Fixes googlefonts#308

The OutlineCompiler classes gets a tables keyword argument that allows to
customize the list of tables to be built.
For the interpolatable sparse masters (those built from non-default layer)
we only build (head, maxp, hmtx, loca, glyf, and post) for TTF, and
(head, maxp, CFF and hmtx) for CFF-OTF.
  • Loading branch information
anthrotype committed Jan 14, 2019
1 parent e01ffc3 commit c014381
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 42 deletions.
11 changes: 10 additions & 1 deletion Lib/ufo2ft/__init__.py
Expand Up @@ -48,6 +48,7 @@ def compileOTF(
overlapsBackend=None,
inplace=False,
layerName=None,
_tables=None,
):
"""Create FontTools CFF font from a UFO.
Expand Down Expand Up @@ -103,6 +104,7 @@ def compileOTF(
glyphOrder=glyphOrder,
roundTolerance=roundTolerance,
optimizeCFF=optimizeCFF >= CFFOptimization.SPECIALIZE,
tables=_tables,
)
otf = outlineCompiler.compile()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -363,6 +371,7 @@ def compileInterpolatableOTFsFromDS(
removeOverlaps=False,
overlapsBackend=None,
inplace=inplace,
_tables=["head", "hmtx", "CFF", "maxp"] if source.layerName else None,
)
)

Expand Down
142 changes: 103 additions & 39 deletions Lib/ufo2ft/outlineCompiler.py
Expand Up @@ -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)

Expand All @@ -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:
Expand All @@ -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):
"""
Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 = {}
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 = {}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
):
Expand All @@ -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):
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -1058,21 +1111,23 @@ 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):
"""Compile a .ttf font with TrueType outlines.
"""

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
Expand All @@ -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 = []
Expand All @@ -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]
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion 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
Expand Down

0 comments on commit c014381

Please sign in to comment.