from fontTools.misc import sstruct from fontTools.misc.textTools import byteord, safeEval from . import DefaultTable import pdb import struct 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, str): continue name, attrs, content = element glyphRec.fromXML(name, attrs, content, ttFont) glyphRec.offset = -1 glyphRec.nMetaEntry = len(glyphRec.stringRecs) else: setattr(self, name, safeEval(attrs["value"])) class GlyphRecord(object): 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, str): continue stringRec.fromXML(name, attrs, content, ttFont) stringRec.stringLen = len(stringRec.string) else: setattr(self, name, safeEval(attrs["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 __repr__(self): return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]" # XXX The following two functions are really broken around UTF-8 vs Unicode def mapXMLToUTF8(string): uString = str() 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 + chr(eval('0x' + valStr)) else: uString = uString + chr(byteord(string[i])) i = i +1 return uString.encode('utf_8') def mapUTF8toXML(string): uString = string.decode('utf_8') string = "" for uChar in uString: i = ord(uChar) if (i < 0x80) and (i > 0x1F): string = string + uChar else: string = string + "&#x" + hex(i)[2:] + ";" return string class StringRecord(object): 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): for element in content: if isinstance(element, str): continue name, attrs, content = element value = attrs["value"] if name == "string": self.string = mapXMLToUTF8(value) else: setattr(self, name, safeEval(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 __repr__(self): return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]"