Repository: fonttools/fonttools

Issue:
add 1 zero byte when compiling empty glyf tables...
...to make Windows happy.

The OTS does that too now.

See discussion
https://github.com/khaledhosny/ots/issues/52#issuecomment-289369267


Discussion:
Sgtm

Resolving patch:
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
--- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -122,6 +122,12 @@ def compile(self, ttFont):
 			ttFont['loca'].set(locations)
 		if 'maxp' in ttFont:
 			ttFont['maxp'].numGlyphs = len(self.glyphs)
+		if not data:
+		# As a special case when all glyph in the font are empty, add a zero byte
+		# to the table, so that OTS doesn’t reject it, and to make the table work
+		# on Windows as well.
+		# See https://github.com/khaledhosny/ots/issues/52
+			data = b"\0"
 		return data
 
 	def toXML(self, writer, ttFont, splitGlyphs=False):


Test patch:
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -12,6 +12,8 @@
 import sys
 import tempfile
 import unittest
+import pathlib
+import pytest
 
 
 class SubsetTest(unittest.TestCase):
@@ -835,5 +837,40 @@ def test_subset_single_pos_format():
     ]
 
 
+@pytest.fixture
+def ttf_path(tmp_path):
+    # $(dirname $0)/../ttLib/data
+    ttLib_data = pathlib.Path(__file__).parent.parent / "ttLib" / "data"
+    font = TTFont()
+    font.importXML(ttLib_data / "TestTTF-Regular.ttx")
+    font_path = tmp_path / "TestTTF-Regular.ttf"
+    font.save(font_path)
+    return font_path
+
+
+def test_subset_empty_glyf(tmp_path, ttf_path):
+    subset_path = tmp_path / (ttf_path.name + ".subset")
+    # only keep empty .notdef and space glyph, resulting in an empty glyf table
+    subset.main(
+        [
+            str(ttf_path),
+            "--no-notdef-outline",
+            "--glyph-names",
+            f"--output-file={subset_path}",
+            "--glyphs=.notdef space",
+        ]
+    )
+    subset_font = TTFont(subset_path)
+
+    assert subset_font.getGlyphOrder() == [".notdef", "space"]
+    assert subset_font.reader['glyf'] == b"\x00"
+
+    glyf = subset_font["glyf"]
+    assert all(glyf[g].numberOfContours == 0 for g in subset_font.getGlyphOrder())
+
+    loca = subset_font["loca"]
+    assert all(loc == 0 for loc in loca)
+
+
 if __name__ == "__main__":
     sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_g_l_y_f_test.py b/Tests/ttLib/tables/_g_l_y_f_test.py
--- a/Tests/ttLib/tables/_g_l_y_f_test.py
+++ b/Tests/ttLib/tables/_g_l_y_f_test.py
@@ -6,6 +6,7 @@
 from fontTools.pens.pointPen import PointToSegmentPen
 from fontTools.ttLib import TTFont, newTable, TTLibError
 from fontTools.ttLib.tables._g_l_y_f import (
+    Glyph,
     GlyphCoordinates,
     GlyphComponent,
     ARGS_ARE_XY_VALUES,
@@ -190,7 +191,7 @@ def strip_ttLibVersion(string):
     return re.sub(' ttLibVersion=".*"', '', string)
 
 
-class glyfTableTest(unittest.TestCase):
+class GlyfTableTest(unittest.TestCase):
 
     def __init__(self, methodName):
         unittest.TestCase.__init__(self, methodName)
@@ -338,6 +339,28 @@ def test_draw_vs_drawpoints(self):
         glyfTable["glyph00003"].drawPoints(PointToSegmentPen(pen2), glyfTable)
         self.assertEqual(pen1.value, pen2.value)
 
+    def test_compile_empty_table(self):
+        font = TTFont(sfntVersion="\x00\x01\x00\x00")
+        font.importXML(GLYF_TTX)
+        glyfTable = font['glyf']
+        # set all glyphs to zero contours
+        glyfTable.glyphs = {glyphName: Glyph() for glyphName in font.getGlyphOrder()}
+        glyfData = glyfTable.compile(font)
+        self.assertEqual(glyfData, b"\x00")
+        self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs+1))
+
+    def test_decompile_empty_table(self):
+        font = TTFont()
+        glyphNames = [".notdef", "space"]
+        font.setGlyphOrder(glyphNames)
+        font["loca"] = newTable("loca")
+        font["loca"].locations = [0] * (len(glyphNames) + 1)
+        font["glyf"] = newTable("glyf")
+        font["glyf"].decompile(b"\x00", font)
+        self.assertEqual(len(font["glyf"]), 2)
+        self.assertEqual(font["glyf"][".notdef"].numberOfContours, 0)
+        self.assertEqual(font["glyf"]["space"].numberOfContours, 0)
+
 
 class GlyphComponentTest: