diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index cc22ad0a8..8cbaa4c3e 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -122,6 +122,12 @@ class table__g_l_y_f(DefaultTable.DefaultTable): 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): diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index 2ffc55501..14ca3137b 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -12,6 +12,8 @@ import shutil 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 index 9e2b3555e..f0a42eee7 100644 --- 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.recordingPen import RecordingPen, RecordingPointPen 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 @@ class glyfTableTest(unittest.TestCase): 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: