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