From a47f6588557cd11477758b05350dedfcdcf1117f Mon Sep 17 00:00:00 2001 From: Sascha Brawer Date: Thu, 17 Aug 2017 22:32:06 +0200 Subject: [PATCH] [AAT] Support the `opbd` table with optical bounds Interestingly, this can handle the examples from the AAT specification (which are part of the unit tests), and also most AAT fonts on my disk. However, some other AAT fonts such as Apple Chancery cannot be decompiled. The failure seems to be a general problem with how fonttools decompiles AAT lookups of format 4, and unrelated to this present change. --- Lib/fontTools/ttLib/tables/__init__.py | 1 + Lib/fontTools/ttLib/tables/_o_p_b_d.py | 8 ++ Lib/fontTools/ttLib/tables/otData.py | 34 +++++++ README.rst | 4 +- Tests/ttLib/tables/_o_p_b_d_test.py | 118 +++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 Lib/fontTools/ttLib/tables/_o_p_b_d.py create mode 100644 Tests/ttLib/tables/_o_p_b_d_test.py diff --git a/Lib/fontTools/ttLib/tables/__init__.py b/Lib/fontTools/ttLib/tables/__init__.py index 0858bd371..92b12d2d2 100644 --- a/Lib/fontTools/ttLib/tables/__init__.py +++ b/Lib/fontTools/ttLib/tables/__init__.py @@ -71,6 +71,7 @@ def _moduleFinderHint(): from . import _m_a_x_p from . import _m_e_t_a from . import _n_a_m_e + from . import _o_p_b_d from . import _p_o_s_t from . import _p_r_e_p from . import _p_r_o_p diff --git a/Lib/fontTools/ttLib/tables/_o_p_b_d.py b/Lib/fontTools/ttLib/tables/_o_p_b_d.py new file mode 100644 index 000000000..60bb6c522 --- /dev/null +++ b/Lib/fontTools/ttLib/tables/_o_p_b_d.py @@ -0,0 +1,8 @@ +from __future__ import print_function, division, absolute_import +from fontTools.misc.py23 import * +from .otBase import BaseTTXConverter + + +# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6opbd.html +class table__o_p_b_d(BaseTTXConverter): + pass diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index 1ef357795..1e1b5c810 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -1337,6 +1337,40 @@ otData = [ ('uint16', 'Format', None, None, 'Format, = 1.'), ('uint16', 'DefaultProperties', None, None, 'Default properties applied to a glyph if that glyph is not present in the Properties lookup table.'), ('AATLookup(uint16)', 'Properties', None, None, 'Lookup data associating glyphs with their properties.'), + ]), + + + # + # opbd + # + + ('opbd', [ + ('Version', 'Version', None, None, 'Version number of the optical bounds table (0x00010000 for the initial version).'), + ('struct', 'OpticalBounds', None, None, 'Optical bounds table.'), + ]), + + ('OpticalBoundsFormat0', [ + ('uint16', 'Format', None, None, 'Format of the optical bounds table, = 0.'), + ('AATLookup(OpticalBoundsDeltas)', 'OpticalBoundsDeltas', None, None, 'Lookup table associating glyphs with their optical bounds, given as deltas in font units.'), + ]), + + ('OpticalBoundsFormat1', [ + ('uint16', 'Format', None, None, 'Format of the optical bounds table, = 1.'), + ('AATLookup(OpticalBoundsPoints)', 'OpticalBoundsPoints', None, None, 'Lookup table associating glyphs with their optical bounds, given as references to control points.'), + ]), + + ('OpticalBoundsDeltas', [ + ('int16', 'Left', None, None, 'Delta value for the left-side optical edge.'), + ('int16', 'Top', None, None, 'Delta value for the top-side optical edge.'), + ('int16', 'Right', None, None, 'Delta value for the right-side optical edge.'), + ('int16', 'Bottom', None, None, 'Delta value for the bottom-side optical edge.'), + ]), + + ('OpticalBoundsPoints', [ + ('int16', 'Left', None, None, 'Control point index for the left-side optical edge, or -1 if this glyph has none.'), + ('int16', 'Top', None, None, 'Control point index for the top-side optical edge, or -1 if this glyph has none.'), + ('int16', 'Right', None, None, 'Control point index for the right-side optical edge, or -1 if this glyph has none.'), + ('int16', 'Bottom', None, None, 'Control point index for the bottom-side optical edge, or -1 if this glyph has none.'), ]), ] diff --git a/README.rst b/README.rst index a79a1967f..d27a33c27 100644 --- a/README.rst +++ b/README.rst @@ -102,8 +102,8 @@ The following tables are currently supported: OS/2, SING, STAT, SVG, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX, VORG, VVAR, avar, cmap, cvar, cvt, feat, fpgm, fvar, gasp, glyf, gvar, hdmx, head, hhea, hmtx, - kern, lcar, loca, ltag, maxp, meta, name, post, prep, prop, sbix, - trak, vhea and vmtx + kern, lcar, loca, ltag, maxp, meta, name, opbd, post, prep, prop, + sbix, trak, vhea and vmtx .. end table list Other tables are dumped as hexadecimal data. diff --git a/Tests/ttLib/tables/_o_p_b_d_test.py b/Tests/ttLib/tables/_o_p_b_d_test.py new file mode 100644 index 000000000..55d5126d8 --- /dev/null +++ b/Tests/ttLib/tables/_o_p_b_d_test.py @@ -0,0 +1,118 @@ +from __future__ import print_function, division, absolute_import, unicode_literals +from fontTools.misc.py23 import * +from fontTools.misc.testTools import FakeFont, getXML, parseXML +from fontTools.misc.textTools import deHexStr, hexStr +from fontTools.ttLib import newTable +import unittest + + +# Example: Format 0 Optical Bounds Table +# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6opbd.html +OPBD_FORMAT_0_DATA = deHexStr( + '0001 0000 0000 ' # 0: Version=1.0, Format=0 + '0006 0004 0002 ' # 6: LookupFormat=6, UnitSize=4, NUnits=2 + '0008 0001 0000 ' # 12: SearchRange=8, EntrySelector=1, RangeShift=0 + '000A 001E ' # 18: Glyph=10(=C), OffsetOfOpticalBoundsDeltas=30 + '002B 0026 ' # 22: Glyph=43(=A), OffsetOfOpticalBoundsDeltas=38 + 'FFFF 0000 ' # 26: Glyph=, OffsetOfOpticalBoundsDeltas=0 + 'FFCE 0005 0037 FFFB ' # 30: Bounds[C].Left=-50 .Top=5 .Right=55 .Bottom=-5 + 'FFF6 000F 0000 0000 ' # 38: Bounds[A].Left=-10 .Top=15 .Right=0 .Bottom=0 +) # 46: +assert(len(OPBD_FORMAT_0_DATA) == 46) + + +OPBD_FORMAT_0_XML = [ + '', + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', +] + + +# Example: Format 1 Optical Bounds Table +# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6opbd.html +OPBD_FORMAT_1_DATA = deHexStr( + '0001 0000 0001 ' # 0: Version=1.0, Format=1 + '0006 0004 0002 ' # 6: LookupFormat=6, UnitSize=4, NUnits=2 + '0008 0001 0000 ' # 12: SearchRange=8, EntrySelector=1, RangeShift=0 + '000A 001E ' # 18: Glyph=10(=C), OffsetOfOpticalBoundsPoints=30 + '002B 0026 ' # 22: Glyph=43(=A), OffsetOfOpticalBoundsPoints=38 + 'FFFF 0000 ' # 26: Glyph=, OffsetOfOpticalBoundsPoints=0 + '0024 0025 0026 0027 ' # 30: Bounds[C].Left=36 .Top=37 .Right=38 .Bottom=39 + '0020 0029 FFFF FFFF ' # 38: Bounds[A].Left=32 .Top=41 .Right=-1 .Bottom=-1 +) # 46: +assert(len(OPBD_FORMAT_1_DATA) == 46) + + +OPBD_FORMAT_1_XML = [ + '', + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', +] + + +class OPBDTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.maxDiff = None + glyphs = ['.notdef'] + ['X.alt%d' for g in range(1, 50)] + glyphs[10] = 'C' + glyphs[43] = 'A' + cls.font = FakeFont(glyphs) + + def test_decompile_toXML_format0(self): + table = newTable('opbd') + table.decompile(OPBD_FORMAT_0_DATA, self.font) + self.assertEqual(getXML(table.toXML), OPBD_FORMAT_0_XML) + + def test_compile_fromXML_format0(self): + table = newTable('opbd') + for name, attrs, content in parseXML(OPBD_FORMAT_0_XML): + table.fromXML(name, attrs, content, font=self.font) + self.assertEqual(hexStr(table.compile(self.font)), + hexStr(OPBD_FORMAT_0_DATA)) + + def test_decompile_toXML_format1(self): + table = newTable('opbd') + table.decompile(OPBD_FORMAT_1_DATA, self.font) + self.assertEqual(getXML(table.toXML), OPBD_FORMAT_1_XML) + + def test_compile_fromXML_format1(self): + table = newTable('opbd') + for name, attrs, content in parseXML(OPBD_FORMAT_1_XML): + table.fromXML(name, attrs, content, font=self.font) + self.assertEqual(hexStr(table.compile(self.font)), + hexStr(OPBD_FORMAT_1_DATA)) + + +if __name__ == '__main__': + import sys + sys.exit(unittest.main())