Repository: fonttools/fonttools

Issue:
Gracefully handle TrueType fonts that misuse `post` table format 1 ?
⚠️ **The attached font is broken** ⚠️ . It uses `post` table format 1 which [requires it to contain exactly the 258 glyphs in the standard Macintosh TrueType font file](https://docs.microsoft.com/en-us/typography/opentype/spec/post#version-10). This font has a lot more glyphs than that, and the first 258 are not ordered as expected by the spec.

Dumping the `hmtx` table with TTX fails with,
```
> ttx -t hmtx post_format_1.ttf
Dumping "post_format_1.ttf" to "post_format_1.ttx"...

ERROR: An exception occurred during the decompilation of the 'hmtx' table
Traceback (most recent call last):
  File "fontTools/ttLib/ttFont.py", line 420, in _readTable
    table.decompile(data, self)
  File "fontTools/ttLib/tables/_h_m_t_x.py", line 51, in decompile
    glyphName = glyphOrder[i]
IndexError: list index out of range
```

Other font tools I tried handle this font just fine.
Can FontTools be made to gracefully handle this broken font?

[post_format_1.ttf.zip](https://github.com/fonttools/fonttools/files/9303651/post_format_1.ttf.zip)


Discussion:
Perhaps this code should compare the length of `glyphOrder` to `self['maxp'].numGlyphs`?

https://github.com/fonttools/fonttools/blob/2963fa503aadb2ef06dfbd5a2208d373c6813f9c/Lib/fontTools/ttLib/ttFont.py#L479-L480

If `glyphOrder` is too short, we could do two things:
- consider `glyphOrder` broken and fall back to `self._getGlyphNamesFromCmap()`
- extend `glyphOrder` with generic `glyph<gid>` names

Resolving patch:
diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py
--- a/Lib/fontTools/ttLib/ttFont.py
+++ b/Lib/fontTools/ttLib/ttFont.py
@@ -487,6 +487,14 @@ def getGlyphOrder(self):
 				# in combination with the Adobe Glyph List (AGL).
 				#
 				self._getGlyphNamesFromCmap()
+			elif len(glyphOrder) < self['maxp'].numGlyphs:
+				#
+				# Not enough names found in the 'post' table.
+				# Can happen when 'post' format 1 is improperly used on a font that
+				# has more than 258 glyphs (the lenght of 'standardGlyphOrder').
+				#
+				log.warning("Not enough names found in the 'post' table, generating them from cmap instead")
+				self._getGlyphNamesFromCmap()
 			else:
 				self.glyphOrder = glyphOrder
 		else:


Test patch:
diff --git a/Tests/ttLib/data/bogus_post_format_1.ttf b/Tests/ttLib/data/bogus_post_format_1.ttf
new file mode 100644
Binary files /dev/null and b/Tests/ttLib/data/bogus_post_format_1.ttf differ
diff --git a/Tests/ttLib/ttFont_test.py b/Tests/ttLib/ttFont_test.py
--- a/Tests/ttLib/ttFont_test.py
+++ b/Tests/ttLib/ttFont_test.py
@@ -4,6 +4,7 @@
 import random
 from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
 from fontTools.ttLib import TTFont, TTLibError, newTable, registerCustomTableClass, unregisterCustomTableClass
+from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
 from fontTools.ttLib.tables.DefaultTable import DefaultTable
 from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
 import pytest
@@ -143,6 +144,17 @@ def test_setGlyphOrder_also_updates_glyf_glyphOrder():
     assert font["glyf"].glyphOrder == new_order
 
 
+def test_getGlyphOrder_not_true_post_format_1(caplog):
+    # https://github.com/fonttools/fonttools/issues/2736
+    caplog.set_level("WARNING")
+    font = TTFont(os.path.join(DATA_DIR, "bogus_post_format_1.ttf"))
+    hmtx = font["hmtx"]
+    assert len(hmtx.metrics) > len(standardGlyphOrder)
+    log_rec = caplog.records[-1]
+    assert log_rec.levelname == "WARNING"
+    assert "Not enough names found in the 'post' table" in log_rec.message
+
+
 @pytest.mark.parametrize("lazy", [None, True, False])
 def test_ensureDecompiled(lazy):
     # test that no matter the lazy value, ensureDecompiled decompiles all tables