2006-10-21 13:27:25 +00:00
|
|
|
import DefaultTable
|
2013-09-17 16:59:39 -04:00
|
|
|
import struct
|
|
|
|
from fontTools.misc import sstruct
|
2006-10-21 13:27:25 +00:00
|
|
|
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..."""
|
2013-08-17 11:11:22 -04:00
|
|
|
|
2013-10-28 12:07:15 +01:00
|
|
|
if type(self) != type(other): return cmp(type(self), type(other))
|
|
|
|
if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
|
2013-08-17 11:11:22 -04:00
|
|
|
|
2006-10-21 13:27:25 +00:00
|
|
|
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..."""
|
2013-08-17 11:11:22 -04:00
|
|
|
|
2013-10-28 12:07:15 +01:00
|
|
|
if type(self) != type(other): return cmp(type(self), type(other))
|
|
|
|
if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
|
2013-08-17 11:11:22 -04:00
|
|
|
|
2006-10-21 13:27:25 +00:00
|
|
|
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 + " ]"
|
|
|
|
|