M_E_T_A_.py revision 7ed91eca1eaa96b79eae780778e89bb9ec44c1ee
1from . import DefaultTable 2import struct 3from fontTools.misc import sstruct 4from fontTools.misc.textTools import safeEval 5from fontTools.misc.py23 import * 6 7 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, str): 177 continue 178 name, attrs, content = element 179 glyphRec.fromXML(name, attrs, content, ttFont) 180 glyphRec.offset = -1 181 glyphRec.nMetaEntry = len(glyphRec.stringRecs) 182 else: 183 setattr(self, name, safeEval(attrs["value"])) 184 185 186class GlyphRecord: 187 def __init__(self): 188 self.glyphID = -1 189 self.nMetaEntry = -1 190 self.offset = -1 191 self.stringRecs = [] 192 193 def toXML(self, writer, ttFont): 194 writer.begintag("GlyphRecord") 195 writer.newline() 196 writer.simpletag("glyphID", value=self.glyphID) 197 writer.newline() 198 writer.simpletag("nMetaEntry", value=self.nMetaEntry) 199 writer.newline() 200 for stringRec in self.stringRecs: 201 stringRec.toXML(writer, ttFont) 202 writer.endtag("GlyphRecord") 203 writer.newline() 204 205 206 def fromXML(self, name, attrs, content, ttFont): 207 if name == "StringRecord": 208 stringRec = StringRecord() 209 self.stringRecs.append(stringRec) 210 for element in content: 211 if isinstance(element, str): 212 continue 213 stringRec.fromXML(name, attrs, content, ttFont) 214 stringRec.stringLen = len(stringRec.string) 215 else: 216 setattr(self, name, safeEval(attrs["value"])) 217 218 def compile(self, parentTable): 219 data = sstruct.pack(METAGlyphRecordFormat, self) 220 if parentTable.metaFlags == 0: 221 datum = struct.pack(">H", self.offset) 222 elif parentTable.metaFlags == 1: 223 datum = struct.pack(">L", self.offset) 224 data = data + datum 225 return data 226 227 228 def __cmp__(self, other): 229 """Compare method, so a list of NameRecords can be sorted 230 according to the spec by just sorting it...""" 231 232 if not isinstance(self, type(other)): return cmp(type(self), type(other)) 233 if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) 234 235 return cmp(self.glyphID, other.glyphID) 236 237 def __repr__(self): 238 return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]" 239 240 241def mapXMLToUTF8(string): 242 uString = unicode() 243 strLen = len(string) 244 i = 0 245 while i < strLen: 246 prefixLen = 0 247 if (string[i:i+3] == "&#x"): 248 prefixLen = 3 249 elif (string[i:i+7] == "&#x"): 250 prefixLen = 7 251 if prefixLen: 252 i = i+prefixLen 253 j= i 254 while string[i] != ";": 255 i = i+1 256 valStr = string[j:i] 257 258 uString = uString + unichr(eval('0x' + valStr)) 259 else: 260 uString = uString + unichr(ord(string[i])) 261 i = i +1 262 263 return uString.encode('utf8') 264 265 266def mapUTF8toXML(string): 267 uString = string.decode('utf8') 268 string = bytes() 269 for uChar in uString: 270 i = ord(uChar) 271 if (i < 0x80) and (i > 0x1F): 272 string = string + bytechr(i) 273 else: 274 string = string + "&#x" + hex(i)[2:] + ";" 275 return string 276 277 278class StringRecord: 279 def __init__(self): 280 self.labelID = -1 281 self.string = "" 282 self.stringLen = -1 283 self.offset = -1 284 285 def toXML(self, writer, ttFont): 286 writer.begintag("StringRecord") 287 writer.newline() 288 writer.simpletag("labelID", value=self.labelID) 289 writer.comment(getLabelString(self.labelID)) 290 writer.newline() 291 writer.newline() 292 writer.simpletag("string", value=mapUTF8toXML(self.string)) 293 writer.newline() 294 writer.endtag("StringRecord") 295 writer.newline() 296 297 def fromXML(self, name, attrs, content, ttFont): 298 value = attrs["value"] 299 if name == "string": 300 self.string = mapXMLToUTF8(value) 301 else: 302 setattr(self, name, safeEval(value)) 303 304 def compile(self, parentTable): 305 data = sstruct.pack(METAStringRecordFormat, self) 306 if parentTable.metaFlags == 0: 307 datum = struct.pack(">H", self.offset) 308 elif parentTable.metaFlags == 1: 309 datum = struct.pack(">L", self.offset) 310 data = data + datum 311 return data 312 313 def __cmp__(self, other): 314 """Compare method, so a list of NameRecords can be sorted 315 according to the spec by just sorting it...""" 316 317 if not isinstance(self, type(other)): return cmp(type(self), type(other)) 318 if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) 319 320 return cmp(self.labelID, other.labelID) 321 322 def __repr__(self): 323 return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ 324 + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]" 325 326