Merge pull request #315 from brawer/meta
Implement Apple’s “meta” table
This commit is contained in:
commit
3a6e5aca55
93
Lib/fontTools/ttLib/tables/_m_e_t_a.py
Normal file
93
Lib/fontTools/ttLib/tables/_m_e_t_a.py
Normal file
@ -0,0 +1,93 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc import sstruct
|
||||
from fontTools.misc.textTools import readHex
|
||||
from fontTools.ttLib import TTLibError
|
||||
from . import DefaultTable
|
||||
|
||||
# Apple's documentation of 'meta':
|
||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html
|
||||
|
||||
META_HEADER_FORMAT = """
|
||||
> # big endian
|
||||
version: L
|
||||
flags: L
|
||||
dataOffset: L
|
||||
numDataMaps: L
|
||||
"""
|
||||
|
||||
# According to Apple's spec, the dataMaps entries contain a dataOffset
|
||||
# that is documented as "Offset from the beginning of the data section
|
||||
# to the data for this tag". However, this is *not* the case with
|
||||
# the fonts that Apple ships pre-installed on MacOS X Yosemite 10.10.4,
|
||||
# and it also does not reflect how Apple's ftxdumperfuser tool is parsing
|
||||
# the 'meta' table (tested ftxdumperfuser build 330, FontToolbox.framework
|
||||
# build 187). Instead of what is claimed in the spec, the data maps contain
|
||||
# a dataOffset relative to the very beginning of the 'meta' table.
|
||||
# The dataOffset field of the 'meta' header apparently gets ignored.
|
||||
|
||||
DATA_MAP_FORMAT = """
|
||||
> # big endian
|
||||
tag: 4s
|
||||
dataOffset: L
|
||||
dataLength: L
|
||||
"""
|
||||
|
||||
|
||||
class table__m_e_t_a(DefaultTable.DefaultTable):
|
||||
def __init__(self, tag="meta"):
|
||||
DefaultTable.DefaultTable.__init__(self, tag)
|
||||
self.data = {}
|
||||
|
||||
def decompile(self, data, ttFont):
|
||||
headerSize = sstruct.calcsize(META_HEADER_FORMAT)
|
||||
header = sstruct.unpack(META_HEADER_FORMAT, data[0 : headerSize])
|
||||
if header["version"] != 1:
|
||||
raise TTLibError("unsupported 'meta' version %d" %
|
||||
header["version"])
|
||||
dataMapSize = sstruct.calcsize(DATA_MAP_FORMAT)
|
||||
for i in range(header["numDataMaps"]):
|
||||
dataMapOffset = headerSize + i * dataMapSize
|
||||
dataMap = sstruct.unpack(
|
||||
DATA_MAP_FORMAT,
|
||||
data[dataMapOffset : dataMapOffset + dataMapSize])
|
||||
tag = dataMap["tag"]
|
||||
offset = dataMap["dataOffset"]
|
||||
self.data[tag] = data[offset : offset + dataMap["dataLength"]]
|
||||
|
||||
def compile(self, ttFont):
|
||||
keys = sorted(self.data.keys())
|
||||
headerSize = sstruct.calcsize(META_HEADER_FORMAT)
|
||||
dataOffset = headerSize + len(keys) * sstruct.calcsize(DATA_MAP_FORMAT)
|
||||
header = sstruct.pack(META_HEADER_FORMAT, {
|
||||
"version": 1,
|
||||
"flags": 0,
|
||||
"dataOffset": dataOffset,
|
||||
"numDataMaps": len(keys)
|
||||
})
|
||||
dataMaps = []
|
||||
dataBlocks = []
|
||||
for tag in keys:
|
||||
data = self.data[tag]
|
||||
dataMaps.append(sstruct.pack(DATA_MAP_FORMAT, {
|
||||
"tag": tag,
|
||||
"dataOffset": dataOffset,
|
||||
"dataLength": len(data)
|
||||
}))
|
||||
dataBlocks.append(data)
|
||||
dataOffset += len(data)
|
||||
return bytesjoin([header] + dataMaps + dataBlocks)
|
||||
|
||||
def toXML(self, writer, ttFont, progress=None):
|
||||
for tag in sorted(self.data.keys()):
|
||||
writer.begintag("hexdata", tag=tag)
|
||||
writer.newline()
|
||||
writer.dumphex(self.data[tag])
|
||||
writer.endtag("hexdata")
|
||||
writer.newline()
|
||||
|
||||
def fromXML(self, name, attrs, content, ttFont):
|
||||
if name == "hexdata":
|
||||
self.data[attrs["tag"]] = readHex(content)
|
||||
else:
|
||||
raise TTLibError("can't handle '%s' element" % name)
|
54
Lib/fontTools/ttLib/tables/_m_e_t_a_test.py
Normal file
54
Lib/fontTools/ttLib/tables/_m_e_t_a_test.py
Normal file
@ -0,0 +1,54 @@
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc.textTools import deHexStr
|
||||
from fontTools.misc.xmlWriter import XMLWriter
|
||||
from fontTools.ttLib import TTLibError
|
||||
from fontTools.ttLib.tables._m_e_t_a import table__m_e_t_a
|
||||
import unittest
|
||||
|
||||
|
||||
# From a real font on MacOS X, but substituted 'bild' tag by 'TEST',
|
||||
# and shortened the payload. Note that from the 'meta' spec, one would
|
||||
# expect that header.dataOffset is 0x0000001C (pointing to the beginning
|
||||
# of the data section) and that dataMap[0].dataOffset should be 0 (relative
|
||||
# to the beginning of the data section). However, in the fonts that Apple
|
||||
# ships on MacOS X 10.10.4, dataMap[0].dataOffset is actually relative
|
||||
# to the beginning of the 'meta' table, i.e. 0x0000001C again. While the
|
||||
# following test data is invalid according to the 'meta' specification,
|
||||
# it is reflecting the 'meta' table structure in all Apple-supplied fonts.
|
||||
META_DATA = deHexStr(
|
||||
"00 00 00 01 00 00 00 00 00 00 00 1C 00 00 00 01 "
|
||||
"54 45 53 54 00 00 00 1C 00 00 00 04 CA FE BE EF")
|
||||
|
||||
|
||||
class MetaTableTest(unittest.TestCase):
|
||||
def test_decompile(self):
|
||||
table = table__m_e_t_a()
|
||||
table.decompile(META_DATA, ttFont={"meta": table})
|
||||
self.assertEqual({"TEST": b"\xCA\xFE\xBE\xEF"}, table.data)
|
||||
|
||||
def test_compile(self):
|
||||
table = table__m_e_t_a()
|
||||
table.data["TEST"] = b"\xCA\xFE\xBE\xEF"
|
||||
self.assertEqual(META_DATA, table.compile(ttFont={"meta": table}))
|
||||
|
||||
def test_toXML(self):
|
||||
table = table__m_e_t_a()
|
||||
table.data["TEST"] = b"\xCA\xFE\xBE\xEF"
|
||||
writer = XMLWriter(StringIO())
|
||||
table.toXML(writer, {"meta": table})
|
||||
xml = writer.file.getvalue().decode("utf-8")
|
||||
self.assertEqual([
|
||||
'<hexdata tag="TEST">',
|
||||
'cafebeef',
|
||||
'</hexdata>'
|
||||
], [line.strip() for line in xml.splitlines()][1:])
|
||||
|
||||
def test_fromXML(self):
|
||||
table = table__m_e_t_a()
|
||||
table.fromXML("hexdata", {"tag": "TEST"}, ['cafebeef'], ttFont=None)
|
||||
self.assertEqual({"TEST": b"\xCA\xFE\xBE\xEF"}, table.data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user