Our footprint in the Python module namespace is all under fontTools now. User code importing sstruct should be updated to say "from fontTools.misc import sstruct".
341 lines
10 KiB
Python
341 lines
10 KiB
Python
import DefaultTable
|
|
import struct
|
|
from fontTools.misc import sstruct
|
|
from fontTools.misc.textTools import safeEval
|
|
import string
|
|
from types import FloatType, ListType, StringType, TupleType
|
|
import sys
|
|
METAHeaderFormat = """
|
|
> # big endian
|
|
tableVersionMajor: H
|
|
tableVersionMinor: H
|
|
metaEntriesVersionMajor: H
|
|
metaEntriesVersionMinor: H
|
|
unicodeVersion: L
|
|
metaFlags: H
|
|
nMetaRecs: H
|
|
"""
|
|
# This record is followed by nMetaRecs of METAGlyphRecordFormat.
|
|
# This in turn is followd by as many METAStringRecordFormat entries
|
|
# as specified by the METAGlyphRecordFormat entries
|
|
# this is followed by the strings specifried in the METAStringRecordFormat
|
|
METAGlyphRecordFormat = """
|
|
> # big endian
|
|
glyphID: H
|
|
nMetaEntry: H
|
|
"""
|
|
# This record is followd by a variable data length field:
|
|
# USHORT or ULONG hdrOffset
|
|
# Offset from start of META table to the beginning
|
|
# of this glyphs array of ns Metadata string entries.
|
|
# Size determined by metaFlags field
|
|
# METAGlyphRecordFormat entries must be sorted by glyph ID
|
|
|
|
METAStringRecordFormat = """
|
|
> # big endian
|
|
labelID: H
|
|
stringLen: H
|
|
"""
|
|
# This record is followd by a variable data length field:
|
|
# USHORT or ULONG stringOffset
|
|
# METAStringRecordFormat entries must be sorted in order of labelID
|
|
# There may be more than one entry with the same labelID
|
|
# There may be more than one strign with the same content.
|
|
|
|
# Strings shall be Unicode UTF-8 encoded, and null-terminated.
|
|
|
|
METALabelDict = {
|
|
0 : "MojikumiX4051", # An integer in the range 1-20
|
|
1 : "UNIUnifiedBaseChars",
|
|
2 : "BaseFontName",
|
|
3 : "Language",
|
|
4 : "CreationDate",
|
|
5 : "FoundryName",
|
|
6 : "FoundryCopyright",
|
|
7 : "OwnerURI",
|
|
8 : "WritingScript",
|
|
10 : "StrokeCount",
|
|
11 : "IndexingRadical",
|
|
}
|
|
|
|
|
|
def getLabelString(labelID):
|
|
try:
|
|
label = METALabelDict[labelID]
|
|
except KeyError:
|
|
label = "Unknown label"
|
|
return str(label)
|
|
|
|
|
|
class table_M_E_T_A_(DefaultTable.DefaultTable):
|
|
|
|
dependencies = []
|
|
|
|
def decompile(self, data, ttFont):
|
|
dummy, newData = sstruct.unpack2(METAHeaderFormat, data, self)
|
|
self.glyphRecords = []
|
|
for i in range(self.nMetaRecs):
|
|
glyphRecord, newData = sstruct.unpack2(METAGlyphRecordFormat, newData, GlyphRecord())
|
|
if self.metaFlags == 0:
|
|
[glyphRecord.offset] = struct.unpack(">H", newData[:2])
|
|
newData = newData[2:]
|
|
elif self.metaFlags == 1:
|
|
[glyphRecord.offset] = struct.unpack(">H", newData[:4])
|
|
newData = newData[4:]
|
|
else:
|
|
assert 0, "The metaFlags field in the META table header has a value other than 0 or 1 :" + str(self.metaFlags)
|
|
glyphRecord.stringRecs = []
|
|
newData = data[glyphRecord.offset:]
|
|
for j in range(glyphRecord.nMetaEntry):
|
|
stringRec, newData = sstruct.unpack2(METAStringRecordFormat, newData, StringRecord())
|
|
if self.metaFlags == 0:
|
|
[stringRec.offset] = struct.unpack(">H", newData[:2])
|
|
newData = newData[2:]
|
|
else:
|
|
[stringRec.offset] = struct.unpack(">H", newData[:4])
|
|
newData = newData[4:]
|
|
stringRec.string = data[stringRec.offset:stringRec.offset + stringRec.stringLen]
|
|
glyphRecord.stringRecs.append(stringRec)
|
|
self.glyphRecords.append(glyphRecord)
|
|
|
|
def compile(self, ttFont):
|
|
offsetOK = 0
|
|
self.nMetaRecs = len(self.glyphRecords)
|
|
count = 0
|
|
while ( offsetOK != 1):
|
|
count = count + 1
|
|
if count > 4:
|
|
pdb_set_trace()
|
|
metaData = sstruct.pack(METAHeaderFormat, self)
|
|
stringRecsOffset = len(metaData) + self.nMetaRecs * (6 + 2*(self.metaFlags & 1))
|
|
stringRecSize = (6 + 2*(self.metaFlags & 1))
|
|
for glyphRec in self.glyphRecords:
|
|
glyphRec.offset = stringRecsOffset
|
|
if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0):
|
|
self.metaFlags = self.metaFlags + 1
|
|
offsetOK = -1
|
|
break
|
|
metaData = metaData + glyphRec.compile(self)
|
|
stringRecsOffset = stringRecsOffset + (glyphRec.nMetaEntry * stringRecSize)
|
|
# this will be the String Record offset for the next GlyphRecord.
|
|
if offsetOK == -1:
|
|
offsetOK = 0
|
|
continue
|
|
|
|
# metaData now contains the header and all of the GlyphRecords. Its length should bw
|
|
# the offset to the first StringRecord.
|
|
stringOffset = stringRecsOffset
|
|
for glyphRec in self.glyphRecords:
|
|
assert (glyphRec.offset == len(metaData)), "Glyph record offset did not compile correctly! for rec:" + str(glyphRec)
|
|
for stringRec in glyphRec.stringRecs:
|
|
stringRec.offset = stringOffset
|
|
if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0):
|
|
self.metaFlags = self.metaFlags + 1
|
|
offsetOK = -1
|
|
break
|
|
metaData = metaData + stringRec.compile(self)
|
|
stringOffset = stringOffset + stringRec.stringLen
|
|
if offsetOK == -1:
|
|
offsetOK = 0
|
|
continue
|
|
|
|
if ((self.metaFlags & 1) == 1) and (stringOffset < 65536):
|
|
self.metaFlags = self.metaFlags - 1
|
|
continue
|
|
else:
|
|
offsetOK = 1
|
|
|
|
|
|
# metaData now contains the header and all of the GlyphRecords and all of the String Records.
|
|
# Its length should be the offset to the first string datum.
|
|
for glyphRec in self.glyphRecords:
|
|
for stringRec in glyphRec.stringRecs:
|
|
assert (stringRec.offset == len(metaData)), "String offset did not compile correctly! for string:" + str(stringRec.string)
|
|
metaData = metaData + stringRec.string
|
|
|
|
return metaData
|
|
|
|
def toXML(self, writer, ttFont):
|
|
writer.comment("Lengths and number of entries in this table will be recalculated by the compiler")
|
|
writer.newline()
|
|
formatstring, names, fixes = sstruct.getformat(METAHeaderFormat)
|
|
for name in names:
|
|
value = getattr(self, name)
|
|
writer.simpletag(name, value=value)
|
|
writer.newline()
|
|
for glyphRec in self.glyphRecords:
|
|
glyphRec.toXML(writer, ttFont)
|
|
|
|
def fromXML(self, (name, attrs, content), ttFont):
|
|
if name == "GlyphRecord":
|
|
if not hasattr(self, "glyphRecords"):
|
|
self.glyphRecords = []
|
|
glyphRec = GlyphRecord()
|
|
self.glyphRecords.append(glyphRec)
|
|
for element in content:
|
|
if isinstance(element, StringType):
|
|
continue
|
|
glyphRec.fromXML(element, ttFont)
|
|
glyphRec.offset = -1
|
|
glyphRec.nMetaEntry = len(glyphRec.stringRecs)
|
|
else:
|
|
value = attrs["value"]
|
|
try:
|
|
value = safeEval(value)
|
|
except OverflowError:
|
|
value = long(value)
|
|
setattr(self, name, value)
|
|
|
|
|
|
class GlyphRecord:
|
|
def __init__(self):
|
|
self.glyphID = -1
|
|
self.nMetaEntry = -1
|
|
self.offset = -1
|
|
self.stringRecs = []
|
|
|
|
def toXML(self, writer, ttFont):
|
|
writer.begintag("GlyphRecord")
|
|
writer.newline()
|
|
writer.simpletag("glyphID", value=self.glyphID)
|
|
writer.newline()
|
|
writer.simpletag("nMetaEntry", value=self.nMetaEntry)
|
|
writer.newline()
|
|
for stringRec in self.stringRecs:
|
|
stringRec.toXML(writer, ttFont)
|
|
writer.endtag("GlyphRecord")
|
|
writer.newline()
|
|
|
|
|
|
def fromXML(self, (name, attrs, content), ttFont):
|
|
if name == "StringRecord":
|
|
stringRec = StringRecord()
|
|
self.stringRecs.append(stringRec)
|
|
for element in content:
|
|
if isinstance(element, StringType):
|
|
continue
|
|
stringRec.fromXML(element, ttFont)
|
|
stringRec.stringLen = len(stringRec.string)
|
|
else:
|
|
value = attrs["value"]
|
|
try:
|
|
value = safeEval(value)
|
|
except OverflowError:
|
|
value = long(value)
|
|
setattr(self, name, value)
|
|
|
|
def compile(self, parentTable):
|
|
data = sstruct.pack(METAGlyphRecordFormat, self)
|
|
if parentTable.metaFlags == 0:
|
|
datum = struct.pack(">H", self.offset)
|
|
elif parentTable.metaFlags == 1:
|
|
datum = struct.pack(">L", self.offset)
|
|
data = data + datum
|
|
return data
|
|
|
|
|
|
def __cmp__(self, other):
|
|
"""Compare method, so a list of NameRecords can be sorted
|
|
according to the spec by just sorting it..."""
|
|
|
|
if type(self) != type(other) or \
|
|
self.__class__ != other.__class__:
|
|
return cmp(id(self), id(other))
|
|
|
|
return cmp(self.glyphID, other.glyphID)
|
|
|
|
def __repr__(self):
|
|
return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]"
|
|
|
|
|
|
def mapXMLToUTF8(string):
|
|
uString = u""
|
|
strLen = len(string)
|
|
i = 0
|
|
while i < strLen:
|
|
prefixLen = 0
|
|
if (string[i:i+3] == "&#x"):
|
|
prefixLen = 3
|
|
elif (string[i:i+7] == "&#x"):
|
|
prefixLen = 7
|
|
if prefixLen:
|
|
i = i+prefixLen
|
|
j= i
|
|
while string[i] != ";":
|
|
i = i+1
|
|
valStr = string[j:i]
|
|
|
|
uString = uString + unichr(eval('0x' + valStr))
|
|
else:
|
|
uString = uString + unichr(ord(string[i]))
|
|
i = i +1
|
|
|
|
return uString.encode('utf8')
|
|
|
|
|
|
def mapUTF8toXML(string):
|
|
uString = string.decode('utf8')
|
|
string = ""
|
|
for uChar in uString:
|
|
i = ord(uChar)
|
|
if (i < 0x80) and (i > 0x1F):
|
|
string = string + chr(i)
|
|
else:
|
|
string = string + "&#x" + hex(i)[2:] + ";"
|
|
return string
|
|
|
|
|
|
class StringRecord:
|
|
def __init__(self):
|
|
self.labelID = -1
|
|
self.string = ""
|
|
self.stringLen = -1
|
|
self.offset = -1
|
|
|
|
def toXML(self, writer, ttFont):
|
|
writer.begintag("StringRecord")
|
|
writer.newline()
|
|
writer.simpletag("labelID", value=self.labelID)
|
|
writer.comment(getLabelString(self.labelID))
|
|
writer.newline()
|
|
writer.newline()
|
|
writer.simpletag("string", value=mapUTF8toXML(self.string))
|
|
writer.newline()
|
|
writer.endtag("StringRecord")
|
|
writer.newline()
|
|
|
|
def fromXML(self, (name, attrs, content), ttFont):
|
|
value = attrs["value"]
|
|
if name == "string":
|
|
self.string = mapXMLToUTF8(value)
|
|
else:
|
|
try:
|
|
value = safeEval(value)
|
|
except OverflowError:
|
|
value = long(value)
|
|
setattr(self, name, value)
|
|
|
|
def compile(self, parentTable):
|
|
data = sstruct.pack(METAStringRecordFormat, self)
|
|
if parentTable.metaFlags == 0:
|
|
datum = struct.pack(">H", self.offset)
|
|
elif parentTable.metaFlags == 1:
|
|
datum = struct.pack(">L", self.offset)
|
|
data = data + datum
|
|
return data
|
|
|
|
def __cmp__(self, other):
|
|
"""Compare method, so a list of NameRecords can be sorted
|
|
according to the spec by just sorting it..."""
|
|
|
|
if type(self) != type(other) or \
|
|
self.__class__ != other.__class__:
|
|
return cmp(id(self), id(other))
|
|
|
|
return cmp(self.labelID, other.labelID)
|
|
|
|
def __repr__(self):
|
|
return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \
|
|
+ ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]"
|
|
|