M_E_T_A_.py revision 4cc0051c087e7e9c294559a21540e296cfe9ef4a
1import DefaultTable 2import struct, sstruct 3from fontTools.misc.textTools import safeEval 4import string 5from types import FloatType, ListType, StringType, TupleType 6import sys 7METAHeaderFormat = """ 8 > # big endian 9 tableVersionMajor: H 10 tableVersionMinor: H 11 metaEntriesVersionMajor: H 12 metaEntriesVersionMinor: H 13 unicodeVersion: L 14 metaFlags: H 15 nMetaRecs: H 16""" 17# This record is followed by nMetaRecs of METAGlyphRecordFormat. 18# This in turn is followd by as many METAStringRecordFormat entries 19# as specified by the METAGlyphRecordFormat entries 20# this is followed by the strings specifried in the METAStringRecordFormat 21METAGlyphRecordFormat = """ 22 > # big endian 23 glyphID: H 24 nMetaEntry: H 25""" 26# This record is followd by a variable data length field: 27# USHORT or ULONG hdrOffset 28# Offset from start of META table to the beginning 29# of this glyphs array of ns Metadata string entries. 30# Size determined by metaFlags field 31# METAGlyphRecordFormat entries must be sorted by glyph ID 32 33METAStringRecordFormat = """ 34 > # big endian 35 labelID: H 36 stringLen: H 37""" 38# This record is followd by a variable data length field: 39# USHORT or ULONG stringOffset 40# METAStringRecordFormat entries must be sorted in order of labelID 41# There may be more than one entry with the same labelID 42# There may be more than one strign with the same content. 43 44# Strings shall be Unicode UTF-8 encoded, and null-terminated. 45 46METALabelDict = { 47 0 : "MojikumiX4051", # An integer in the range 1-20 48 1 : "UNIUnifiedBaseChars", 49 2 : "BaseFontName", 50 3 : "Language", 51 4 : "CreationDate", 52 5 : "FoundryName", 53 6 : "FoundryCopyright", 54 7 : "OwnerURI", 55 8 : "WritingScript", 56 10 : "StrokeCount", 57 11 : "IndexingRadical", 58} 59 60 61def getLabelString(labelID): 62 try: 63 label = METALabelDict[labelID] 64 except KeyError: 65 label = "Unknown label" 66 return str(label) 67 68 69class table_M_E_T_A_(DefaultTable.DefaultTable): 70 71 dependencies = [] 72 73 def decompile(self, data, ttFont): 74 dummy, newData = sstruct.unpack2(METAHeaderFormat, data, self) 75 self.glyphRecords = [] 76 for i in range(self.nMetaRecs): 77 glyphRecord, newData = sstruct.unpack2(METAGlyphRecordFormat, newData, GlyphRecord()) 78 if self.metaFlags == 0: 79 [glyphRecord.offset] = struct.unpack(">H", newData[:2]) 80 newData = newData[2:] 81 elif self.metaFlags == 1: 82 [glyphRecord.offset] = struct.unpack(">H", newData[:4]) 83 newData = newData[4:] 84 else: 85 assert 0, "The metaFlags field in the META table header has a value other than 0 or 1 :" + str(self.metaFlags) 86 glyphRecord.stringRecs = [] 87 newData = data[glyphRecord.offset:] 88 for j in range(glyphRecord.nMetaEntry): 89 stringRec, newData = sstruct.unpack2(METAStringRecordFormat, newData, StringRecord()) 90 if self.metaFlags == 0: 91 [stringRec.offset] = struct.unpack(">H", newData[:2]) 92 newData = newData[2:] 93 else: 94 [stringRec.offset] = struct.unpack(">H", newData[:4]) 95 newData = newData[4:] 96 stringRec.string = data[stringRec.offset:stringRec.offset + stringRec.stringLen] 97 glyphRecord.stringRecs.append(stringRec) 98 self.glyphRecords.append(glyphRecord) 99 100 def compile(self, ttFont): 101 offsetOK = 0 102 self.nMetaRecs = len(self.glyphRecords) 103 count = 0 104 while ( offsetOK != 1): 105 count = count + 1 106 if count > 4: 107 pdb_set_trace() 108 metaData = sstruct.pack(METAHeaderFormat, self) 109 stringRecsOffset = len(metaData) + self.nMetaRecs * (6 + 2*(self.metaFlags & 1)) 110 stringRecSize = (6 + 2*(self.metaFlags & 1)) 111 for glyphRec in self.glyphRecords: 112 glyphRec.offset = stringRecsOffset 113 if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0): 114 self.metaFlags = self.metaFlags + 1 115 offsetOK = -1 116 break 117 metaData = metaData + glyphRec.compile(self) 118 stringRecsOffset = stringRecsOffset + (glyphRec.nMetaEntry * stringRecSize) 119 # this will be the String Record offset for the next GlyphRecord. 120 if offsetOK == -1: 121 offsetOK = 0 122 continue 123 124 # metaData now contains the header and all of the GlyphRecords. Its length should bw 125 # the offset to the first StringRecord. 126 stringOffset = stringRecsOffset 127 for glyphRec in self.glyphRecords: 128 assert (glyphRec.offset == len(metaData)), "Glyph record offset did not compile correctly! for rec:" + str(glyphRec) 129 for stringRec in glyphRec.stringRecs: 130 stringRec.offset = stringOffset 131 if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0): 132 self.metaFlags = self.metaFlags + 1 133 offsetOK = -1 134 break 135 metaData = metaData + stringRec.compile(self) 136 stringOffset = stringOffset + stringRec.stringLen 137 if offsetOK == -1: 138 offsetOK = 0 139 continue 140 141 if ((self.metaFlags & 1) == 1) and (stringOffset < 65536): 142 self.metaFlags = self.metaFlags - 1 143 continue 144 else: 145 offsetOK = 1 146 147 148 # metaData now contains the header and all of the GlyphRecords and all of the String Records. 149 # Its length should be the offset to the first string datum. 150 for glyphRec in self.glyphRecords: 151 for stringRec in glyphRec.stringRecs: 152 assert (stringRec.offset == len(metaData)), "String offset did not compile correctly! for string:" + str(stringRec.string) 153 metaData = metaData + stringRec.string 154 155 return metaData 156 157 def toXML(self, writer, ttFont): 158 writer.comment("Lengths and number of entries in this table will be recalculated by the compiler") 159 writer.newline() 160 formatstring, names, fixes = sstruct.getformat(METAHeaderFormat) 161 for name in names: 162 value = getattr(self, name) 163 writer.simpletag(name, value=value) 164 writer.newline() 165 for glyphRec in self.glyphRecords: 166 glyphRec.toXML(writer, ttFont) 167 168 def fromXML(self, (name, attrs, content), ttFont): 169 if name == "GlyphRecord": 170 if not hasattr(self, "glyphRecords"): 171 self.glyphRecords = [] 172 glyphRec = GlyphRecord() 173 self.glyphRecords.append(glyphRec) 174 for element in content: 175 if isinstance(element, StringType): 176 continue 177 glyphRec.fromXML(element, ttFont) 178 glyphRec.offset = -1 179 glyphRec.nMetaEntry = len(glyphRec.stringRecs) 180 else: 181 value = attrs["value"] 182 try: 183 value = safeEval(value) 184 except OverflowError: 185 value = long(value) 186 setattr(self, name, value) 187 188 189class GlyphRecord: 190 def __init__(self): 191 self.glyphID = -1 192 self.nMetaEntry = -1 193 self.offset = -1 194 self.stringRecs = [] 195 196 def toXML(self, writer, ttFont): 197 writer.begintag("GlyphRecord") 198 writer.newline() 199 writer.simpletag("glyphID", value=self.glyphID) 200 writer.newline() 201 writer.simpletag("nMetaEntry", value=self.nMetaEntry) 202 writer.newline() 203 for stringRec in self.stringRecs: 204 stringRec.toXML(writer, ttFont) 205 writer.endtag("GlyphRecord") 206 writer.newline() 207 208 209 def fromXML(self, (name, attrs, content), ttFont): 210 if name == "StringRecord": 211 stringRec = StringRecord() 212 self.stringRecs.append(stringRec) 213 for element in content: 214 if isinstance(element, StringType): 215 continue 216 stringRec.fromXML(element, ttFont) 217 stringRec.stringLen = len(stringRec.string) 218 else: 219 value = attrs["value"] 220 try: 221 value = safeEval(value) 222 except OverflowError: 223 value = long(value) 224 setattr(self, name, value) 225 226 def compile(self, parentTable): 227 data = sstruct.pack(METAGlyphRecordFormat, self) 228 if parentTable.metaFlags == 0: 229 datum = struct.pack(">H", self.offset) 230 elif parentTable.metaFlags == 1: 231 datum = struct.pack(">L", self.offset) 232 data = data + datum 233 return data 234 235 236 def __cmp__(self, other): 237 """Compare method, so a list of NameRecords can be sorted 238 according to the spec by just sorting it...""" 239 return cmp(self.glyphID, other.glyphID) 240 241 def __repr__(self): 242 return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]" 243 244 245def mapXMLToUTF8(string): 246 uString = u"" 247 strLen = len(string) 248 i = 0 249 while i < strLen: 250 prefixLen = 0 251 if (string[i:i+3] == "&#x"): 252 prefixLen = 3 253 elif (string[i:i+7] == "&#x"): 254 prefixLen = 7 255 if prefixLen: 256 i = i+prefixLen 257 j= i 258 while string[i] != ";": 259 i = i+1 260 valStr = string[j:i] 261 262 uString = uString + unichr(eval('0x' + valStr)) 263 else: 264 uString = uString + unichr(ord(string[i])) 265 i = i +1 266 267 return uString.encode('utf8') 268 269 270def mapUTF8toXML(string): 271 uString = string.decode('utf8') 272 string = "" 273 for uChar in uString: 274 i = ord(uChar) 275 if (i < 0x80) and (i > 0x1F): 276 string = string + chr(i) 277 else: 278 string = string + "&#x" + hex(i)[2:] + ";" 279 return string 280 281 282class StringRecord: 283 def __init__(self): 284 self.labelID = -1 285 self.string = "" 286 self.stringLen = -1 287 self.offset = -1 288 289 def toXML(self, writer, ttFont): 290 writer.begintag("StringRecord") 291 writer.newline() 292 writer.simpletag("labelID", value=self.labelID) 293 writer.comment(getLabelString(self.labelID)) 294 writer.newline() 295 writer.newline() 296 writer.simpletag("string", value=mapUTF8toXML(self.string)) 297 writer.newline() 298 writer.endtag("StringRecord") 299 writer.newline() 300 301 def fromXML(self, (name, attrs, content), ttFont): 302 value = attrs["value"] 303 if name == "string": 304 self.string = mapXMLToUTF8(value) 305 else: 306 try: 307 value = safeEval(value) 308 except OverflowError: 309 value = long(value) 310 setattr(self, name, value) 311 312 def compile(self, parentTable): 313 data = sstruct.pack(METAStringRecordFormat, self) 314 if parentTable.metaFlags == 0: 315 datum = struct.pack(">H", self.offset) 316 elif parentTable.metaFlags == 1: 317 datum = struct.pack(">L", self.offset) 318 data = data + datum 319 return data 320 321 def __cmp__(self, other): 322 """Compare method, so a list of NameRecords can be sorted 323 according to the spec by just sorting it...""" 324 return cmp(self.labelID, other.labelID) 325 326 def __repr__(self): 327 return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ 328 + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]" 329 330