[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.
This commit is contained in:
Sascha Brawer 2017-08-17 22:32:06 +02:00
parent 1aef1683df
commit a47f658855
5 changed files with 163 additions and 2 deletions

View File

@ -71,6 +71,7 @@ def _moduleFinderHint():
from . import _m_a_x_p from . import _m_a_x_p
from . import _m_e_t_a from . import _m_e_t_a
from . import _n_a_m_e from . import _n_a_m_e
from . import _o_p_b_d
from . import _p_o_s_t from . import _p_o_s_t
from . import _p_r_e_p from . import _p_r_e_p
from . import _p_r_o_p from . import _p_r_o_p

View File

@ -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

View File

@ -1337,6 +1337,40 @@ otData = [
('uint16', 'Format', None, None, 'Format, = 1.'), ('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.'), ('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.'), ('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.'),
]), ]),
] ]

View File

@ -102,8 +102,8 @@ The following tables are currently supported:
OS/2, SING, STAT, SVG, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, OS/2, SING, STAT, SVG, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID,
TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX, VORG, VVAR, avar, cmap, cvar, TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX, VORG, VVAR, avar, cmap, cvar,
cvt, feat, fpgm, fvar, gasp, glyf, gvar, hdmx, head, hhea, hmtx, cvt, feat, fpgm, fvar, gasp, glyf, gvar, hdmx, head, hhea, hmtx,
kern, lcar, loca, ltag, maxp, meta, name, post, prep, prop, sbix, kern, lcar, loca, ltag, maxp, meta, name, opbd, post, prep, prop,
trak, vhea and vmtx sbix, trak, vhea and vmtx
.. end table list .. end table list
Other tables are dumped as hexadecimal data. Other tables are dumped as hexadecimal data.

View File

@ -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=<end>, 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: <end>
assert(len(OPBD_FORMAT_0_DATA) == 46)
OPBD_FORMAT_0_XML = [
'<Version value="0x00010000"/>',
'<OpticalBounds Format="0">',
' <OpticalBoundsDeltas>',
' <Lookup glyph="A">',
' <Left value="-10"/>',
' <Top value="15"/>',
' <Right value="0"/>',
' <Bottom value="0"/>',
' </Lookup>',
' <Lookup glyph="C">',
' <Left value="-50"/>',
' <Top value="5"/>',
' <Right value="55"/>',
' <Bottom value="-5"/>',
' </Lookup>',
' </OpticalBoundsDeltas>',
'</OpticalBounds>',
]
# 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=<end>, 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: <end>
assert(len(OPBD_FORMAT_1_DATA) == 46)
OPBD_FORMAT_1_XML = [
'<Version value="0x00010000"/>',
'<OpticalBounds Format="1">',
' <OpticalBoundsPoints>',
' <Lookup glyph="A">',
' <Left value="32"/>',
' <Top value="41"/>',
' <Right value="-1"/>',
' <Bottom value="-1"/>',
' </Lookup>',
' <Lookup glyph="C">',
' <Left value="36"/>',
' <Top value="37"/>',
' <Right value="38"/>',
' <Bottom value="39"/>',
' </Lookup>',
' </OpticalBoundsPoints>',
'</OpticalBounds>',
]
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())