M_E_T_A_.py revision 0ba7aa7ab5153e6a490425dd0f859cc5947360f4
1import DefaultTable 2import struct 3from fontTools.misc import sstruct 4from fontTools.misc.textTools import safeEval 5import string 6from types import FloatType, ListType, StringType, TupleType 7import sys 8METAHeaderFormat = """ 9 > # big endian 10 tableVersionMajor: H 11 tableVersionMinor: H 12 metaEntriesVersionMajor: H 13 metaEntriesVersionMinor: H 14 unicodeVersion: L 15 metaFlags: H 16 nMetaRecs: H 17""" 18# This record is followed by nMetaRecs of METAGlyphRecordFormat. 19# This in turn is followd by as many METAStringRecordFormat entries 20# as specified by the METAGlyphRecordFormat entries 21# this is followed by the strings specifried in the METAStringRecordFormat 22METAGlyphRecordFormat = """ 23 > # big endian 24 glyphID: H 25 nMetaEntry: H 26""" 27# This record is followd by a variable data length field: 28# USHORT or ULONG hdrOffset 29# Offset from start of META table to the beginning 30# of this glyphs array of ns Metadata string entries. 31# Size determined by metaFlags field 32# METAGlyphRecordFormat entries must be sorted by glyph ID 33 34METAStringRecordFormat = """ 35 > # big endian 36 labelID: H 37 stringLen: H 38""" 39# This record is followd by a variable data length field: 40# USHORT or ULONG stringOffset 41# METAStringRecordFormat entries must be sorted in order of labelID 42# There may be more than one entry with the same labelID 43# There may be more than one strign with the same content. 44 45# Strings shall be Unicode UTF-8 encoded, and null-terminated. 46 47METALabelDict = { 48 0 : "MojikumiX4051", # An integer in the range 1-20 49 1 : "UNIUnifiedBaseChars", 50 2 : "BaseFontName", 51 3 : "Language", 52 4 : "CreationDate", 53 5 : "FoundryName", 54 6 : "FoundryCopyright", 55 7 : "OwnerURI", 56 8 : "WritingScript", 57 10 : "StrokeCount", 58 11 : "IndexingRadical", 59} 60 61 62def getLabelString(labelID): 63 try: 64 label = METALabelDict[labelID] 65 except KeyError: 66 label = "Unknown label" 67 return str(label) 68 69 70class table_M_E_T_A_(DefaultTable.DefaultTable): 71 72 dependencies = [] 73 74 def decompile(self, data, ttFont): 75 dummy, newData = sstruct.unpack2(METAHeaderFormat, data, self) 76 self.glyphRecords = [] 77 for i in range(self.nMetaRecs): 78 glyphRecord, newData = sstruct.unpack2(METAGlyphRecordFormat, newData, GlyphRecord()) 79 if self.metaFlags == 0: 80 [glyphRecord.offset] = struct.unpack(">H", newData[:2]) 81 newData = newData[2:] 82 elif self.metaFlags == 1: 83 [glyphRecord.offset] = struct.unpack(">H", newData[:4]) 84 newData = newData[4:] 85 else: 86 assert 0, "The metaFlags field in the META table header has a value other than 0 or 1 :" + str(self.metaFlags) 87 glyphRecord.stringRecs = [] 88 newData = data[glyphRecord.offset:] 89 for j in range(glyphRecord.nMetaEntry): 90 stringRec, newData = sstruct.unpack2(METAStringRecordFormat, newData, StringRecord()) 91 if self.metaFlags == 0: 92 [stringRec.offset] = struct.unpack(">H", newData[:2]) 93 newData = newData[2:] 94 else: 95 [stringRec.offset] = struct.unpack(">H", newData[:4]) 96 newData = newData[4:] 97 stringRec.string = data[stringRec.offset:stringRec.offset + stringRec.stringLen] 98 glyphRecord.stringRecs.append(stringRec) 99 self.glyphRecords.append(glyphRecord) 100 101 def compile(self, ttFont): 102 offsetOK = 0 103 self.nMetaRecs = len(self.glyphRecords) 104 count = 0 105 while ( offsetOK != 1): 106 count = count + 1 107 if count > 4: 108 pdb_set_trace() 109 metaData = sstruct.pack(METAHeaderFormat, self) 110 stringRecsOffset = len(metaData) + self.nMetaRecs * (6 + 2*(self.metaFlags & 1)) 111 stringRecSize = (6 + 2*(self.metaFlags & 1)) 112 for glyphRec in self.glyphRecords: 113 glyphRec.offset = stringRecsOffset 114 if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0): 115 self.metaFlags = self.metaFlags + 1 116 offsetOK = -1 117 break 118 metaData = metaData + glyphRec.compile(self) 119 stringRecsOffset = stringRecsOffset + (glyphRec.nMetaEntry * stringRecSize) 120 # this will be the String Record offset for the next GlyphRecord. 121 if offsetOK == -1: 122 offsetOK = 0 123 continue 124 125 # metaData now contains the header and all of the GlyphRecords. Its length should bw 126 # the offset to the first StringRecord. 127 stringOffset = stringRecsOffset 128 for glyphRec in self.glyphRecords: 129 assert (glyphRec.offset == len(metaData)), "Glyph record offset did not compile correctly! for rec:" + str(glyphRec) 130 for stringRec in glyphRec.stringRecs: 131 stringRec.offset = stringOffset 132 if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0): 133 self.metaFlags = self.metaFlags + 1 134 offsetOK = -1 135 break 136 metaData = metaData + stringRec.compile(self) 137 stringOffset = stringOffset + stringRec.stringLen 138 if offsetOK == -1: 139 offsetOK = 0 140 continue 141 142 if ((self.metaFlags & 1) == 1) and (stringOffset < 65536): 143 self.metaFlags = self.metaFlags - 1 144 continue 145 else: 146 offsetOK = 1 147 148 149 # metaData now contains the header and all of the GlyphRecords and all of the String Records. 150 # Its length should be the offset to the first string datum. 151 for glyphRec in self.glyphRecords: 152 for stringRec in glyphRec.stringRecs: 153 assert (stringRec.offset == len(metaData)), "String offset did not compile correctly! for string:" + str(stringRec.string) 154 metaData = metaData + stringRec.string 155 156 return metaData 157 158 def toXML(self, writer, ttFont): 159 writer.comment("Lengths and number of entries in this table will be recalculated by the compiler") 160 writer.newline() 161 formatstring, names, fixes = sstruct.getformat(METAHeaderFormat) 162 for name in names: 163 value = getattr(self, name) 164 writer.simpletag(name, value=value) 165 writer.newline() 166 for glyphRec in self.glyphRecords: 167 glyphRec.toXML(writer, ttFont) 168 169 def fromXML(self, (name, attrs, content), ttFont): 170 if name == "GlyphRecord": 171 if not hasattr(self, "glyphRecords"): 172 self.glyphRecords = [] 173 glyphRec = GlyphRecord() 174 self.glyphRecords.append(glyphRec) 175 for element in content: 176 if isinstance(element, StringType): 177 continue 178 glyphRec.fromXML(element, ttFont) 179 glyphRec.offset = -1 180 glyphRec.nMetaEntry = len(glyphRec.stringRecs) 181 else: 182 value = attrs["value"] 183 try: 184 value = safeEval(value) 185 except OverflowError: 186 value = long(value) 187 setattr(self, name, value) 188 189 190class GlyphRecord: 191 def __init__(self): 192 self.glyphID = -1 193 self.nMetaEntry = -1 194 self.offset = -1 195 self.stringRecs = [] 196 197 def toXML(self, writer, ttFont): 198 writer.begintag("GlyphRecord") 199 writer.newline() 200 writer.simpletag("glyphID", value=self.glyphID) 201 writer.newline() 202 writer.simpletag("nMetaEntry", value=self.nMetaEntry) 203 writer.newline() 204 for stringRec in self.stringRecs: 205 stringRec.toXML(writer, ttFont) 206 writer.endtag("GlyphRecord") 207 writer.newline() 208 209 210 def fromXML(self, (name, attrs, content), ttFont): 211 if name == "StringRecord": 212 stringRec = StringRecord() 213 self.stringRecs.append(stringRec) 214 for element in content: 215 if isinstance(element, StringType): 216 continue 217 stringRec.fromXML(element, ttFont) 218 stringRec.stringLen = len(stringRec.string) 219 else: 220 value = attrs["value"] 221 try: 222 value = safeEval(value) 223 except OverflowError: 224 value = long(value) 225 setattr(self, name, value) 226 227 def compile(self, parentTable): 228 data = sstruct.pack(METAGlyphRecordFormat, self) 229 if parentTable.metaFlags == 0: 230 datum = struct.pack(">H", self.offset) 231 elif parentTable.metaFlags == 1: 232 datum = struct.pack(">L", self.offset) 233 data = data + datum 234 return data 235 236 237 def __cmp__(self, other): 238 """Compare method, so a list of NameRecords can be sorted 239 according to the spec by just sorting it...""" 240 241 if type(self) != type(other): return cmp(type(self), type(other)) 242 if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) 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): return cmp(type(self), type(other)) 331 if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) 332 333 return cmp(self.labelID, other.labelID) 334 335 def __repr__(self): 336 return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ 337 + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]" 338 339