This should fix https://github.com/fonttools/fonttools/issues/1119 Running `ftxvalidator -T tt0004c_#1.ttf` no longer produces this error message, but passes with 'NA' ``` Fatal post: The name data overflow the table bounds. kATSFontTestSeverityFatalError ```
276 lines
8.6 KiB
Python
276 lines
8.6 KiB
Python
from __future__ import print_function, division, absolute_import
|
|
from fontTools.misc.py23 import *
|
|
from fontTools import ttLib
|
|
from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
|
|
from fontTools.misc import sstruct
|
|
from fontTools.misc.textTools import safeEval, readHex
|
|
from . import DefaultTable
|
|
import sys
|
|
import struct
|
|
import array
|
|
|
|
|
|
postFormat = """
|
|
>
|
|
formatType: 16.16F
|
|
italicAngle: 16.16F # italic angle in degrees
|
|
underlinePosition: h
|
|
underlineThickness: h
|
|
isFixedPitch: L
|
|
minMemType42: L # minimum memory if TrueType font is downloaded
|
|
maxMemType42: L # maximum memory if TrueType font is downloaded
|
|
minMemType1: L # minimum memory if Type1 font is downloaded
|
|
maxMemType1: L # maximum memory if Type1 font is downloaded
|
|
"""
|
|
|
|
postFormatSize = sstruct.calcsize(postFormat)
|
|
|
|
|
|
class table__p_o_s_t(DefaultTable.DefaultTable):
|
|
|
|
def decompile(self, data, ttFont):
|
|
sstruct.unpack(postFormat, data[:postFormatSize], self)
|
|
data = data[postFormatSize:]
|
|
if self.formatType == 1.0:
|
|
self.decode_format_1_0(data, ttFont)
|
|
elif self.formatType == 2.0:
|
|
self.decode_format_2_0(data, ttFont)
|
|
elif self.formatType == 3.0:
|
|
self.decode_format_3_0(data, ttFont)
|
|
elif self.formatType == 4.0:
|
|
self.decode_format_4_0(data, ttFont)
|
|
else:
|
|
# supported format
|
|
raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType)
|
|
|
|
def compile(self, ttFont):
|
|
data = sstruct.pack(postFormat, self)
|
|
if self.formatType == 1.0:
|
|
pass # we're done
|
|
elif self.formatType == 2.0:
|
|
data = data + self.encode_format_2_0(ttFont)
|
|
elif self.formatType == 3.0:
|
|
pass # we're done
|
|
elif self.formatType == 4.0:
|
|
data = data + self.encode_format_4_0(ttFont)
|
|
else:
|
|
# supported format
|
|
raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType)
|
|
return data
|
|
|
|
def getGlyphOrder(self):
|
|
"""This function will get called by a ttLib.TTFont instance.
|
|
Do not call this function yourself, use TTFont().getGlyphOrder()
|
|
or its relatives instead!
|
|
"""
|
|
if not hasattr(self, "glyphOrder"):
|
|
raise ttLib.TTLibError("illegal use of getGlyphOrder()")
|
|
glyphOrder = self.glyphOrder
|
|
del self.glyphOrder
|
|
return glyphOrder
|
|
|
|
def decode_format_1_0(self, data, ttFont):
|
|
self.glyphOrder = standardGlyphOrder[:ttFont["maxp"].numGlyphs]
|
|
|
|
def decode_format_2_0(self, data, ttFont):
|
|
numGlyphs, = struct.unpack(">H", data[:2])
|
|
numGlyphs = int(numGlyphs)
|
|
if numGlyphs > ttFont['maxp'].numGlyphs:
|
|
# Assume the numGlyphs field is bogus, so sync with maxp.
|
|
# I've seen this in one font, and if the assumption is
|
|
# wrong elsewhere, well, so be it: it's hard enough to
|
|
# work around _one_ non-conforming post format...
|
|
numGlyphs = ttFont['maxp'].numGlyphs
|
|
data = data[2:]
|
|
indices = array.array("H")
|
|
indices.fromstring(data[:2*numGlyphs])
|
|
if sys.byteorder != "big":
|
|
indices.byteswap()
|
|
data = data[2*numGlyphs:]
|
|
self.extraNames = extraNames = unpackPStrings(data)
|
|
self.glyphOrder = glyphOrder = [""] * int(ttFont['maxp'].numGlyphs)
|
|
for glyphID in range(numGlyphs):
|
|
index = indices[glyphID]
|
|
if index > 257:
|
|
try:
|
|
name = extraNames[index-258]
|
|
except IndexError:
|
|
name = ""
|
|
else:
|
|
# fetch names from standard list
|
|
name = standardGlyphOrder[index]
|
|
glyphOrder[glyphID] = name
|
|
self.build_psNameMapping(ttFont)
|
|
|
|
def build_psNameMapping(self, ttFont):
|
|
mapping = {}
|
|
allNames = {}
|
|
for i in range(ttFont['maxp'].numGlyphs):
|
|
glyphName = psName = self.glyphOrder[i]
|
|
if glyphName == "":
|
|
glyphName = "glyph%.5d" % i
|
|
if glyphName in allNames:
|
|
# make up a new glyphName that's unique
|
|
n = allNames[glyphName]
|
|
while (glyphName + "#" + str(n)) in allNames:
|
|
n += 1
|
|
allNames[glyphName] = n + 1
|
|
glyphName = glyphName + "#" + str(n)
|
|
|
|
self.glyphOrder[i] = glyphName
|
|
allNames[glyphName] = 1
|
|
if glyphName != psName:
|
|
mapping[glyphName] = psName
|
|
|
|
self.mapping = mapping
|
|
|
|
def decode_format_3_0(self, data, ttFont):
|
|
# Setting self.glyphOrder to None will cause the TTFont object
|
|
# try and construct glyph names from a Unicode cmap table.
|
|
self.glyphOrder = None
|
|
|
|
def decode_format_4_0(self, data, ttFont):
|
|
from fontTools import agl
|
|
numGlyphs = ttFont['maxp'].numGlyphs
|
|
indices = array.array("H")
|
|
indices.fromstring(data)
|
|
if sys.byteorder != "big":
|
|
indices.byteswap()
|
|
# In some older fonts, the size of the post table doesn't match
|
|
# the number of glyphs. Sometimes it's bigger, sometimes smaller.
|
|
self.glyphOrder = glyphOrder = [''] * int(numGlyphs)
|
|
for i in range(min(len(indices),numGlyphs)):
|
|
if indices[i] == 0xFFFF:
|
|
self.glyphOrder[i] = ''
|
|
elif indices[i] in agl.UV2AGL:
|
|
self.glyphOrder[i] = agl.UV2AGL[indices[i]]
|
|
else:
|
|
self.glyphOrder[i] = "uni%04X" % indices[i]
|
|
self.build_psNameMapping(ttFont)
|
|
|
|
def encode_format_2_0(self, ttFont):
|
|
numGlyphs = ttFont['maxp'].numGlyphs
|
|
glyphOrder = ttFont.getGlyphOrder()
|
|
assert len(glyphOrder) == numGlyphs
|
|
indices = array.array("H")
|
|
extraDict = {}
|
|
extraNames = self.extraNames = [
|
|
n for n in self.extraNames if n not in standardGlyphOrder]
|
|
for i in range(len(extraNames)):
|
|
extraDict[extraNames[i]] = i
|
|
for glyphID in range(numGlyphs):
|
|
glyphName = glyphOrder[glyphID]
|
|
if glyphName in self.mapping:
|
|
psName = self.mapping[glyphName]
|
|
else:
|
|
psName = glyphName
|
|
if psName in extraDict:
|
|
index = 258 + extraDict[psName]
|
|
elif psName in standardGlyphOrder:
|
|
index = standardGlyphOrder.index(psName)
|
|
else:
|
|
index = 258 + len(extraNames)
|
|
extraDict[psName] = len(extraNames)
|
|
extraNames.append(psName)
|
|
indices.append(index)
|
|
if sys.byteorder != "big":
|
|
indices.byteswap()
|
|
return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(extraNames)
|
|
|
|
def encode_format_4_0(self, ttFont):
|
|
from fontTools import agl
|
|
numGlyphs = ttFont['maxp'].numGlyphs
|
|
glyphOrder = ttFont.getGlyphOrder()
|
|
assert len(glyphOrder) == numGlyphs
|
|
indices = array.array("H")
|
|
for glyphID in glyphOrder:
|
|
glyphID = glyphID.split('#')[0]
|
|
if glyphID in agl.AGL2UV:
|
|
indices.append(agl.AGL2UV[glyphID])
|
|
elif len(glyphID) == 7 and glyphID[:3] == 'uni':
|
|
indices.append(int(glyphID[3:],16))
|
|
else:
|
|
indices.append(0xFFFF)
|
|
if sys.byteorder != "big":
|
|
indices.byteswap()
|
|
return indices.tostring()
|
|
|
|
def toXML(self, writer, ttFont):
|
|
formatstring, names, fixes = sstruct.getformat(postFormat)
|
|
for name in names:
|
|
value = getattr(self, name)
|
|
writer.simpletag(name, value=value)
|
|
writer.newline()
|
|
if hasattr(self, "mapping"):
|
|
writer.begintag("psNames")
|
|
writer.newline()
|
|
writer.comment("This file uses unique glyph names based on the information\n"
|
|
"found in the 'post' table. Since these names might not be unique,\n"
|
|
"we have to invent artificial names in case of clashes. In order to\n"
|
|
"be able to retain the original information, we need a name to\n"
|
|
"ps name mapping for those cases where they differ. That's what\n"
|
|
"you see below.\n")
|
|
writer.newline()
|
|
items = sorted(self.mapping.items())
|
|
for name, psName in items:
|
|
writer.simpletag("psName", name=name, psName=psName)
|
|
writer.newline()
|
|
writer.endtag("psNames")
|
|
writer.newline()
|
|
if hasattr(self, "extraNames"):
|
|
writer.begintag("extraNames")
|
|
writer.newline()
|
|
writer.comment("following are the name that are not taken from the standard Mac glyph order")
|
|
writer.newline()
|
|
for name in self.extraNames:
|
|
writer.simpletag("psName", name=name)
|
|
writer.newline()
|
|
writer.endtag("extraNames")
|
|
writer.newline()
|
|
if hasattr(self, "data"):
|
|
writer.begintag("hexdata")
|
|
writer.newline()
|
|
writer.dumphex(self.data)
|
|
writer.endtag("hexdata")
|
|
writer.newline()
|
|
|
|
def fromXML(self, name, attrs, content, ttFont):
|
|
if name not in ("psNames", "extraNames", "hexdata"):
|
|
setattr(self, name, safeEval(attrs["value"]))
|
|
elif name == "psNames":
|
|
self.mapping = {}
|
|
for element in content:
|
|
if not isinstance(element, tuple):
|
|
continue
|
|
name, attrs, content = element
|
|
if name == "psName":
|
|
self.mapping[attrs["name"]] = attrs["psName"]
|
|
elif name == "extraNames":
|
|
self.extraNames = []
|
|
for element in content:
|
|
if not isinstance(element, tuple):
|
|
continue
|
|
name, attrs, content = element
|
|
if name == "psName":
|
|
self.extraNames.append(attrs["name"])
|
|
else:
|
|
self.data = readHex(content)
|
|
|
|
|
|
def unpackPStrings(data):
|
|
strings = []
|
|
index = 0
|
|
dataLen = len(data)
|
|
while index < dataLen:
|
|
length = byteord(data[index])
|
|
strings.append(tostr(data[index+1:index+1+length], encoding="latin1"))
|
|
index = index + 1 + length
|
|
return strings
|
|
|
|
|
|
def packPStrings(strings):
|
|
data = b""
|
|
for s in strings:
|
|
data = data + bytechr(len(s)) + tobytes(s, encoding="latin1")
|
|
return data
|