M_E_T_A_.py revision 96b321c8aea4dc64110d15a541c6f85152ae19cf
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 240 if type(self) != type(other) or \ 241 self.__class__ != other.__class__: 242 return cmp(id(self), id(other)) 243 244 return cmp(self.glyphID, other.glyphID) 245 246 def __repr__(self): 247 return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]" 248 249 250def mapXMLToUTF8(string): 251 uString = u"" 252 strLen = len(string) 253 i = 0 254 while i < strLen: 255 prefixLen = 0 256 if (string[i:i+3] == "&#x"): 257 prefixLen = 3 258 elif (string[i:i+7] == "&#x"): 259 prefixLen = 7 260 if prefixLen: 261 i = i+prefixLen 262 j= i 263 while string[i] != ";": 264 i = i+1 265 valStr = string[j:i] 266 267 uString = uString + unichr(eval('0x' + valStr)) 268 else: 269 uString = uString + unichr(ord(string[i])) 270 i = i +1 271 272 return uString.encode('utf8') 273 274 275def mapUTF8toXML(string): 276 uString = string.decode('utf8') 277 string = "" 278 for uChar in uString: 279 i = ord(uChar) 280 if (i < 0x80) and (i > 0x1F): 281 string = string + chr(i) 282 else: 283 string = string + "&#x" + hex(i)[2:] + ";" 284 return string 285 286 287class StringRecord: 288 def __init__(self): 289 self.labelID = -1 290 self.string = "" 291 self.stringLen = -1 292 self.offset = -1 293 294 def toXML(self, writer, ttFont): 295 writer.begintag("StringRecord") 296 writer.newline() 297 writer.simpletag("labelID", value=self.labelID) 298 writer.comment(getLabelString(self.labelID)) 299 writer.newline() 300 writer.newline() 301 writer.simpletag("string", value=mapUTF8toXML(self.string)) 302 writer.newline() 303 writer.endtag("StringRecord") 304 writer.newline() 305 306 def fromXML(self, (name, attrs, content), ttFont): 307 value = attrs["value"] 308 if name == "string": 309 self.string = mapXMLToUTF8(value) 310 else: 311 try: 312 value = safeEval(value) 313 except OverflowError: 314 value = long(value) 315 setattr(self, name, value) 316 317 def compile(self, parentTable): 318 data = sstruct.pack(METAStringRecordFormat, self) 319 if parentTable.metaFlags == 0: 320 datum = struct.pack(">H", self.offset) 321 elif parentTable.metaFlags == 1: 322 datum = struct.pack(">L", self.offset) 323 data = data + datum 324 return data 325 326 def __cmp__(self, other): 327 """Compare method, so a list of NameRecords can be sorted 328 according to the spec by just sorting it...""" 329 330 if type(self) != type(other) or \ 331 self.__class__ != other.__class__: 332 return cmp(id(self), id(other)) 333 334 return cmp(self.labelID, other.labelID) 335 336 def __repr__(self): 337 return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ 338 + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]" 339 340