1from fontTools.misc.py23 import byteord 2from fontTools.misc import sstruct 3from fontTools.misc.textTools import safeEval 4from . import DefaultTable 5import pdb 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 # 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(object): 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 def fromXML(self, name, attrs, content, ttFont): 206 if name == "StringRecord": 207 stringRec = StringRecord() 208 self.stringRecs.append(stringRec) 209 for element in content: 210 if isinstance(element, str): 211 continue 212 stringRec.fromXML(name, attrs, content, ttFont) 213 stringRec.stringLen = len(stringRec.string) 214 else: 215 setattr(self, name, safeEval(attrs["value"])) 216 217 def compile(self, parentTable): 218 data = sstruct.pack(METAGlyphRecordFormat, self) 219 if parentTable.metaFlags == 0: 220 datum = struct.pack(">H", self.offset) 221 elif parentTable.metaFlags == 1: 222 datum = struct.pack(">L", self.offset) 223 data = data + datum 224 return data 225 226 def __repr__(self): 227 return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]" 228 229# XXX The following two functions are really broken around UTF-8 vs Unicode 230 231def mapXMLToUTF8(string): 232 uString = str() 233 strLen = len(string) 234 i = 0 235 while i < strLen: 236 prefixLen = 0 237 if (string[i:i+3] == "&#x"): 238 prefixLen = 3 239 elif (string[i:i+7] == "&#x"): 240 prefixLen = 7 241 if prefixLen: 242 i = i+prefixLen 243 j= i 244 while string[i] != ";": 245 i = i+1 246 valStr = string[j:i] 247 248 uString = uString + chr(eval('0x' + valStr)) 249 else: 250 uString = uString + chr(byteord(string[i])) 251 i = i +1 252 253 return uString.encode('utf_8') 254 255 256def mapUTF8toXML(string): 257 uString = string.decode('utf_8') 258 string = "" 259 for uChar in uString: 260 i = ord(uChar) 261 if (i < 0x80) and (i > 0x1F): 262 string = string + uChar 263 else: 264 string = string + "&#x" + hex(i)[2:] + ";" 265 return string 266 267 268class StringRecord(object): 269 270 def toXML(self, writer, ttFont): 271 writer.begintag("StringRecord") 272 writer.newline() 273 writer.simpletag("labelID", value=self.labelID) 274 writer.comment(getLabelString(self.labelID)) 275 writer.newline() 276 writer.newline() 277 writer.simpletag("string", value=mapUTF8toXML(self.string)) 278 writer.newline() 279 writer.endtag("StringRecord") 280 writer.newline() 281 282 def fromXML(self, name, attrs, content, ttFont): 283 for element in content: 284 if isinstance(element, str): 285 continue 286 name, attrs, content = element 287 value = attrs["value"] 288 if name == "string": 289 self.string = mapXMLToUTF8(value) 290 else: 291 setattr(self, name, safeEval(value)) 292 293 def compile(self, parentTable): 294 data = sstruct.pack(METAStringRecordFormat, self) 295 if parentTable.metaFlags == 0: 296 datum = struct.pack(">H", self.offset) 297 elif parentTable.metaFlags == 1: 298 datum = struct.pack(">L", self.offset) 299 data = data + datum 300 return data 301 302 def __repr__(self): 303 return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ 304 + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]" 305