| import DefaultTable |
| import struct, 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...""" |
| 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...""" |
| 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 + " ]" |
| |