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