1from fontTools.misc import sstruct 2from fontTools.misc.textTools import byteord, safeEval 3from . import DefaultTable 4import pdb 5import struct 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 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( 78 METAGlyphRecordFormat, newData, GlyphRecord() 79 ) 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, ( 88 "The metaFlags field in the META table header has a value other than 0 or 1 :" 89 + str(self.metaFlags) 90 ) 91 glyphRecord.stringRecs = [] 92 newData = data[glyphRecord.offset :] 93 for j in range(glyphRecord.nMetaEntry): 94 stringRec, newData = sstruct.unpack2( 95 METAStringRecordFormat, newData, StringRecord() 96 ) 97 if self.metaFlags == 0: 98 [stringRec.offset] = struct.unpack(">H", newData[:2]) 99 newData = newData[2:] 100 else: 101 [stringRec.offset] = struct.unpack(">H", newData[:4]) 102 newData = newData[4:] 103 stringRec.string = data[ 104 stringRec.offset : stringRec.offset + stringRec.stringLen 105 ] 106 glyphRecord.stringRecs.append(stringRec) 107 self.glyphRecords.append(glyphRecord) 108 109 def compile(self, ttFont): 110 offsetOK = 0 111 self.nMetaRecs = len(self.glyphRecords) 112 count = 0 113 while offsetOK != 1: 114 count = count + 1 115 if count > 4: 116 pdb.set_trace() 117 metaData = sstruct.pack(METAHeaderFormat, self) 118 stringRecsOffset = len(metaData) + self.nMetaRecs * ( 119 6 + 2 * (self.metaFlags & 1) 120 ) 121 stringRecSize = 6 + 2 * (self.metaFlags & 1) 122 for glyphRec in self.glyphRecords: 123 glyphRec.offset = stringRecsOffset 124 if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0): 125 self.metaFlags = self.metaFlags + 1 126 offsetOK = -1 127 break 128 metaData = metaData + glyphRec.compile(self) 129 stringRecsOffset = stringRecsOffset + ( 130 glyphRec.nMetaEntry * stringRecSize 131 ) 132 # this will be the String Record offset for the next GlyphRecord. 133 if offsetOK == -1: 134 offsetOK = 0 135 continue 136 137 # metaData now contains the header and all of the GlyphRecords. Its length should bw 138 # the offset to the first StringRecord. 139 stringOffset = stringRecsOffset 140 for glyphRec in self.glyphRecords: 141 assert glyphRec.offset == len( 142 metaData 143 ), "Glyph record offset did not compile correctly! for rec:" + str( 144 glyphRec 145 ) 146 for stringRec in glyphRec.stringRecs: 147 stringRec.offset = stringOffset 148 if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0): 149 self.metaFlags = self.metaFlags + 1 150 offsetOK = -1 151 break 152 metaData = metaData + stringRec.compile(self) 153 stringOffset = stringOffset + stringRec.stringLen 154 if offsetOK == -1: 155 offsetOK = 0 156 continue 157 158 if ((self.metaFlags & 1) == 1) and (stringOffset < 65536): 159 self.metaFlags = self.metaFlags - 1 160 continue 161 else: 162 offsetOK = 1 163 164 # metaData now contains the header and all of the GlyphRecords and all of the String Records. 165 # Its length should be the offset to the first string datum. 166 for glyphRec in self.glyphRecords: 167 for stringRec in glyphRec.stringRecs: 168 assert stringRec.offset == len( 169 metaData 170 ), "String offset did not compile correctly! for string:" + str( 171 stringRec.string 172 ) 173 metaData = metaData + stringRec.string 174 175 return metaData 176 177 def toXML(self, writer, ttFont): 178 writer.comment( 179 "Lengths and number of entries in this table will be recalculated by the compiler" 180 ) 181 writer.newline() 182 formatstring, names, fixes = sstruct.getformat(METAHeaderFormat) 183 for name in names: 184 value = getattr(self, name) 185 writer.simpletag(name, value=value) 186 writer.newline() 187 for glyphRec in self.glyphRecords: 188 glyphRec.toXML(writer, ttFont) 189 190 def fromXML(self, name, attrs, content, ttFont): 191 if name == "GlyphRecord": 192 if not hasattr(self, "glyphRecords"): 193 self.glyphRecords = [] 194 glyphRec = GlyphRecord() 195 self.glyphRecords.append(glyphRec) 196 for element in content: 197 if isinstance(element, str): 198 continue 199 name, attrs, content = element 200 glyphRec.fromXML(name, attrs, content, ttFont) 201 glyphRec.offset = -1 202 glyphRec.nMetaEntry = len(glyphRec.stringRecs) 203 else: 204 setattr(self, name, safeEval(attrs["value"])) 205 206 207class GlyphRecord(object): 208 def __init__(self): 209 self.glyphID = -1 210 self.nMetaEntry = -1 211 self.offset = -1 212 self.stringRecs = [] 213 214 def toXML(self, writer, ttFont): 215 writer.begintag("GlyphRecord") 216 writer.newline() 217 writer.simpletag("glyphID", value=self.glyphID) 218 writer.newline() 219 writer.simpletag("nMetaEntry", value=self.nMetaEntry) 220 writer.newline() 221 for stringRec in self.stringRecs: 222 stringRec.toXML(writer, ttFont) 223 writer.endtag("GlyphRecord") 224 writer.newline() 225 226 def fromXML(self, name, attrs, content, ttFont): 227 if name == "StringRecord": 228 stringRec = StringRecord() 229 self.stringRecs.append(stringRec) 230 for element in content: 231 if isinstance(element, str): 232 continue 233 stringRec.fromXML(name, attrs, content, ttFont) 234 stringRec.stringLen = len(stringRec.string) 235 else: 236 setattr(self, name, safeEval(attrs["value"])) 237 238 def compile(self, parentTable): 239 data = sstruct.pack(METAGlyphRecordFormat, self) 240 if parentTable.metaFlags == 0: 241 datum = struct.pack(">H", self.offset) 242 elif parentTable.metaFlags == 1: 243 datum = struct.pack(">L", self.offset) 244 data = data + datum 245 return data 246 247 def __repr__(self): 248 return ( 249 "GlyphRecord[ glyphID: " 250 + str(self.glyphID) 251 + ", nMetaEntry: " 252 + str(self.nMetaEntry) 253 + ", offset: " 254 + str(self.offset) 255 + " ]" 256 ) 257 258 259# XXX The following two functions are really broken around UTF-8 vs Unicode 260 261 262def mapXMLToUTF8(string): 263 uString = str() 264 strLen = len(string) 265 i = 0 266 while i < strLen: 267 prefixLen = 0 268 if string[i : i + 3] == "&#x": 269 prefixLen = 3 270 elif string[i : i + 7] == "&#x": 271 prefixLen = 7 272 if prefixLen: 273 i = i + prefixLen 274 j = i 275 while string[i] != ";": 276 i = i + 1 277 valStr = string[j:i] 278 279 uString = uString + chr(eval("0x" + valStr)) 280 else: 281 uString = uString + chr(byteord(string[i])) 282 i = i + 1 283 284 return uString.encode("utf_8") 285 286 287def mapUTF8toXML(string): 288 uString = string.decode("utf_8") 289 string = "" 290 for uChar in uString: 291 i = ord(uChar) 292 if (i < 0x80) and (i > 0x1F): 293 string = string + uChar 294 else: 295 string = string + "&#x" + hex(i)[2:] + ";" 296 return string 297 298 299class StringRecord(object): 300 def toXML(self, writer, ttFont): 301 writer.begintag("StringRecord") 302 writer.newline() 303 writer.simpletag("labelID", value=self.labelID) 304 writer.comment(getLabelString(self.labelID)) 305 writer.newline() 306 writer.newline() 307 writer.simpletag("string", value=mapUTF8toXML(self.string)) 308 writer.newline() 309 writer.endtag("StringRecord") 310 writer.newline() 311 312 def fromXML(self, name, attrs, content, ttFont): 313 for element in content: 314 if isinstance(element, str): 315 continue 316 name, attrs, content = element 317 value = attrs["value"] 318 if name == "string": 319 self.string = mapXMLToUTF8(value) 320 else: 321 setattr(self, name, safeEval(value)) 322 323 def compile(self, parentTable): 324 data = sstruct.pack(METAStringRecordFormat, self) 325 if parentTable.metaFlags == 0: 326 datum = struct.pack(">H", self.offset) 327 elif parentTable.metaFlags == 1: 328 datum = struct.pack(">L", self.offset) 329 data = data + datum 330 return data 331 332 def __repr__(self): 333 return ( 334 "StringRecord [ labelID: " 335 + str(self.labelID) 336 + " aka " 337 + getLabelString(self.labelID) 338 + ", offset: " 339 + str(self.offset) 340 + ", length: " 341 + str(self.stringLen) 342 + ", string: " 343 + self.string 344 + " ]" 345 ) 346