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 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 # metaData now contains the header and all of the GlyphRecords and all of the String Records. 149 # Its length should be the offset to the first string datum. 150 for glyphRec in self.glyphRecords: 151 for stringRec in glyphRec.stringRecs: 152 assert (stringRec.offset == len(metaData)), "String offset did not compile correctly! for string:" + str(stringRec.string) 153 metaData = metaData + stringRec.string 154 155 return metaData 156 157 def toXML(self, writer, ttFont): 158 writer.comment("Lengths and number of entries in this table will be recalculated by the compiler") 159 writer.newline() 160 formatstring, names, fixes = sstruct.getformat(METAHeaderFormat) 161 for name in names: 162 value = getattr(self, name) 163 writer.simpletag(name, value=value) 164 writer.newline() 165 for glyphRec in self.glyphRecords: 166 glyphRec.toXML(writer, ttFont) 167 168 def fromXML(self, name, attrs, content, ttFont): 169 if name == "GlyphRecord": 170 if not hasattr(self, "glyphRecords"): 171 self.glyphRecords = [] 172 glyphRec = GlyphRecord() 173 self.glyphRecords.append(glyphRec) 174 for element in content: 175 if isinstance(element, str): 176 continue 177 name, attrs, content = element 178 glyphRec.fromXML(name, attrs, content, ttFont) 179 glyphRec.offset = -1 180 glyphRec.nMetaEntry = len(glyphRec.stringRecs) 181 else: 182 setattr(self, name, safeEval(attrs["value"])) 183 184 185class GlyphRecord(object): 186 def __init__(self): 187 self.glyphID = -1 188 self.nMetaEntry = -1 189 self.offset = -1 190 self.stringRecs = [] 191 192 def toXML(self, writer, ttFont): 193 writer.begintag("GlyphRecord") 194 writer.newline() 195 writer.simpletag("glyphID", value=self.glyphID) 196 writer.newline() 197 writer.simpletag("nMetaEntry", value=self.nMetaEntry) 198 writer.newline() 199 for stringRec in self.stringRecs: 200 stringRec.toXML(writer, ttFont) 201 writer.endtag("GlyphRecord") 202 writer.newline() 203 204 def fromXML(self, name, attrs, content, ttFont): 205 if name == "StringRecord": 206 stringRec = StringRecord() 207 self.stringRecs.append(stringRec) 208 for element in content: 209 if isinstance(element, str): 210 continue 211 stringRec.fromXML(name, attrs, content, ttFont) 212 stringRec.stringLen = len(stringRec.string) 213 else: 214 setattr(self, name, safeEval(attrs["value"])) 215 216 def compile(self, parentTable): 217 data = sstruct.pack(METAGlyphRecordFormat, self) 218 if parentTable.metaFlags == 0: 219 datum = struct.pack(">H", self.offset) 220 elif parentTable.metaFlags == 1: 221 datum = struct.pack(">L", self.offset) 222 data = data + datum 223 return data 224 225 def __repr__(self): 226 return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]" 227 228# XXX The following two functions are really broken around UTF-8 vs Unicode 229 230def mapXMLToUTF8(string): 231 uString = str() 232 strLen = len(string) 233 i = 0 234 while i < strLen: 235 prefixLen = 0 236 if (string[i:i+3] == "&#x"): 237 prefixLen = 3 238 elif (string[i:i+7] == "&#x"): 239 prefixLen = 7 240 if prefixLen: 241 i = i+prefixLen 242 j= i 243 while string[i] != ";": 244 i = i+1 245 valStr = string[j:i] 246 247 uString = uString + chr(eval('0x' + valStr)) 248 else: 249 uString = uString + chr(byteord(string[i])) 250 i = i +1 251 252 return uString.encode('utf_8') 253 254 255def mapUTF8toXML(string): 256 uString = string.decode('utf_8') 257 string = "" 258 for uChar in uString: 259 i = ord(uChar) 260 if (i < 0x80) and (i > 0x1F): 261 string = string + uChar 262 else: 263 string = string + "&#x" + hex(i)[2:] + ";" 264 return string 265 266 267class StringRecord(object): 268 269 def toXML(self, writer, ttFont): 270 writer.begintag("StringRecord") 271 writer.newline() 272 writer.simpletag("labelID", value=self.labelID) 273 writer.comment(getLabelString(self.labelID)) 274 writer.newline() 275 writer.newline() 276 writer.simpletag("string", value=mapUTF8toXML(self.string)) 277 writer.newline() 278 writer.endtag("StringRecord") 279 writer.newline() 280 281 def fromXML(self, name, attrs, content, ttFont): 282 for element in content: 283 if isinstance(element, str): 284 continue 285 name, attrs, content = element 286 value = attrs["value"] 287 if name == "string": 288 self.string = mapXMLToUTF8(value) 289 else: 290 setattr(self, name, safeEval(value)) 291 292 def compile(self, parentTable): 293 data = sstruct.pack(METAStringRecordFormat, self) 294 if parentTable.metaFlags == 0: 295 datum = struct.pack(">H", self.offset) 296 elif parentTable.metaFlags == 1: 297 datum = struct.pack(">L", self.offset) 298 data = data + datum 299 return data 300 301 def __repr__(self): 302 return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ 303 + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]" 304