Skip to content

Commit

Permalink
Merge pull request #772 from googlefonts/ttf-vf-empty-default-notdef
Browse files Browse the repository at this point in the history
Fix issue with sparse masters when .notdef contain cubic curves
  • Loading branch information
anthrotype committed Aug 1, 2023
2 parents 9dacc83 + 8d676d9 commit d4747ee
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Lib/ufo2ft/__init__.py
Expand Up @@ -408,7 +408,7 @@ def compileInterpolatableTTFsFromDS(designSpaceDoc, **kwargs):
kwargs["skipExportGlyphs"] = designSpaceDoc.lib.get("public.skipExportGlyphs", [])

if kwargs["notdefGlyph"] is None:
kwargs["notdefGlyph"] = _getDefaultNotdefGlyph(designSpaceDoc)
kwargs["notdefGlyph"] = _getDefaultNotdefGlyph(designSpaceDoc, empty=True)

kwargs["extraSubstitutions"] = defaultdict(set)
for rule in designSpaceDoc.rules:
Expand Down
14 changes: 13 additions & 1 deletion Lib/ufo2ft/util.py
Expand Up @@ -475,7 +475,7 @@ def getDefaultMasterFont(designSpaceDoc):
return defaultSource.font


def _getDefaultNotdefGlyph(designSpaceDoc):
def _getDefaultNotdefGlyph(designSpaceDoc, empty=False):
from ufo2ft.errors import InvalidDesignSpaceData

try:
Expand All @@ -488,6 +488,18 @@ def _getDefaultNotdefGlyph(designSpaceDoc):
notdefGlyph = baseUfo[".notdef"]
except KeyError:
notdefGlyph = None
else:
if empty:
# create a new empty .notdef glyph with the same width/height
# as the default master's .notdef glyph to be use for sparse layer
# master TTFs, so that it won't participate in gvar interpolation
# TODO(anthrotype): Use sentinel values for width/height to
# mark the glyph as non-participating for HVAR if/when fonttools
# supports that, https://github.com/googlefonts/ufo2ft/issues/501
emptyGlyph = _getNewGlyphFactory(notdefGlyph)(".notdef")
emptyGlyph.width = notdefGlyph.width
emptyGlyph.height = notdefGlyph.height
notdefGlyph = emptyGlyph
return notdefGlyph


Expand Down
40 changes: 34 additions & 6 deletions tests/integration_test.py
Expand Up @@ -7,7 +7,11 @@

import pytest
from fontTools.pens.boundsPen import BoundsPen
from fontTools.ttLib.tables._g_l_y_f import OVERLAP_COMPOUND, flagOverlapSimple
from fontTools.ttLib.tables._g_l_y_f import (
OVERLAP_COMPOUND,
flagCubic,
flagOverlapSimple,
)

from ufo2ft import (
compileInterpolatableTTFs,
Expand Down Expand Up @@ -450,17 +454,21 @@ def test_compileTTF_glyf1_not_allQuadratic(self, testufo):

assert ttf["head"].glyphDataFormat == 1

@staticmethod
def drawCurvedContour(glyph):
pen = glyph.getPen()
pen.moveTo((500, 0))
pen.curveTo((500, 277.614), (388.072, 500), (250, 500))
pen.curveTo((111.928, 500), (0, 277.614), (0, 0))
pen.closePath()

def test_compileVariableTTF_glyf1_not_allQuadratic(self, designspace):
base_master = designspace.findDefault()
assert base_master is not None
# add a glyph with some curveTo to exercise the cu2qu codepath
glyph = base_master.font.newGlyph("curved")
glyph.width = 1000
pen = glyph.getPen()
pen.moveTo((500, 0))
pen.curveTo((500, 277.614), (388.072, 500), (250, 500))
pen.curveTo((111.928, 500), (0, 277.614), (0, 0))
pen.closePath()
self.drawCurvedContour(glyph)

vf = compileVariableTTF(designspace, allQuadratic=False)
expectTTX(vf, "TestVariableFont-TTF-not-allQuadratic.ttx", tables=["glyf"])
Expand All @@ -480,6 +488,26 @@ def test_compileTTF_overlap_simple_flag(self, testufo):
assert not ttf["glyf"]["g"].components[0].flags & OVERLAP_COMPOUND
assert ttf["glyf"]["h"].components[0].flags & OVERLAP_COMPOUND

def test_compileVariableTTF_notdefGlyph_with_curves(self, designspace):
# The test DS contains two full masters (Regular and Bold) and one intermediate
# 'sparse' (Medium) master, which does not contain a .notdef glyph and as such
# is supposed to inherit one from the default master. If the notdef contains
# any curves, an error occured because these are weren't been converted to
# quadratic: https://github.com/googlefonts/ufo2ft/issues/501

# First we draw an additional contour containing cubic curves in the Regular
# and Bold's .notdef glyphs
for src_idx in (0, 2):
notdef = designspace.sources[src_idx].font[".notdef"]
self.drawCurvedContour(notdef)
assert ".notdef" not in designspace.sources[1].font.layers["Medium"]

# this must NOT fail!
vf = compileVariableTTF(designspace, convertCubics=True, allQuadratic=True)

# and because allQuadratic=True, we expect .notdef contains no cubic curves
assert not any(f & flagCubic for f in vf["glyf"][".notdef"].flags)


if __name__ == "__main__":
sys.exit(pytest.main(sys.argv))

0 comments on commit d4747ee

Please sign in to comment.