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 pdb 7import struct 8 9 10METAHeaderFormat = """ 11 > # big endian 12 tableVersionMajor: H 13 tableVersionMinor: H 14 metaEntriesVersionMajor: H 15 metaEntriesVersionMinor: H 16 unicodeVersion: L 17 metaFlags: H 18 nMetaRecs: H 19""" 20# This record is followed by nMetaRecs of METAGlyphRecordFormat. 21# This in turn is followd by as many METAStringRecordFormat entries 22# as specified by the METAGlyphRecordFormat entries 23# this is followed by the strings specifried in the METAStringRecordFormat 24METAGlyphRecordFormat = """ 25 > # big endian 26 glyphID: H 27 nMetaEntry: H 28""" 29# This record is followd by a variable data length field: 30# USHORT or ULONG hdrOffset 31# Offset from start of META table to the beginning 32# of this glyphs array of ns Metadata string entries. 33# Size determined by metaFlags field 34# METAGlyphRecordFormat entries must be sorted by glyph ID 35 36METAStringRecordFormat = """ 37 > # big endian 38 labelID: H 39 stringLen: H 40""" 41# This record is followd by a variable data length field: 42# USHORT or ULONG stringOffset 43# METAStringRecordFormat entries must be sorted in order of labelID 44# There may be more than one entry with the same labelID 45# There may be more than one strign with the same content. 46 47# Strings shall be Unicode UTF-8 encoded, and null-terminated. 48 49METALabelDict = { 50 0: "MojikumiX4051", # An integer in the range 1-20 51 1: "UNIUnifiedBaseChars", 52 2: "BaseFontName", 53 3: "Language", 54 4: "CreationDate", 55 5: "FoundryName", 56 6: "FoundryCopyright", 57 7: "OwnerURI", 58 8: "WritingScript", 59 10: "StrokeCount", 60 11: "IndexingRadical", 61} 62 63 64def getLabelString(labelID): 65 try: 66 label = METALabelDict[labelID] 67 except KeyError: 68 label = "Unknown label" 69 return str(label) 70 71 72class table_M_E_T_A_(DefaultTable.DefaultTable): 73 74 dependencies = [] 75 76 def decompile(self, data, ttFont): 77 dummy, newData = sstruct.unpack2(METAHeaderFormat, data, self) 78 self.glyphRecords = [] 79 for i in range(self.nMetaRecs): 80 glyphRecord, newData = sstruct.unpack2(METAGlyphRecordFormat, newData, GlyphRecord()) 81 if self.metaFlags == 0: 82 [glyphRecord.offset] = struct.unpack(">H", newData[:2]) 83 newData = newData[2:] 84 elif self.metaFlags == 1: 85 [glyphRecord.offset] = struct.unpack(">H", newData[:4]) 86 newData = newData[4:] 87 else: 88 assert 0, "The metaFlags field in the META table header has a value other than 0 or 1 :" + str(self.metaFlags) 89 glyphRecord.stringRecs = [] 90 newData = data[glyphRecord.offset:] 91 for j in range(glyphRecord.nMetaEntry): 92 stringRec, newData = sstruct.unpack2(METAStringRecordFormat, newData, StringRecord()) 93 if self.metaFlags == 0: 94 [stringRec.offset] = struct.unpack(">H", newData[:2]) 95 newData = newData[2:] 96 else: 97 [stringRec.offset] = struct.unpack(">H", newData[:4]) 98 newData = newData[4:] 99 stringRec.string = data[stringRec.offset:stringRec.offset + stringRec.stringLen] 100 glyphRecord.stringRecs.append(stringRec) 101 self.glyphRecords.append(glyphRecord) 102 103 def compile(self, ttFont): 104 offsetOK = 0 105 self.nMetaRecs = len(self.glyphRecords) 106 count = 0 107 while (offsetOK != 1): 108 count = count + 1 109 if count > 4: 110 pdb.set_trace() 111 metaData = sstruct.pack(METAHeaderFormat, self) 112 stringRecsOffset = len(metaData) + self.nMetaRecs * (6 + 2*(self.metaFlags & 1)) 113 stringRecSize = (6 + 2*(self.metaFlags & 1)) 114 for glyphRec in self.glyphRecords: 115 glyphRec.offset = stringRecsOffset 116 if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0): 117 self.metaFlags = self.metaFlags + 1 118 offsetOK = -1 119 break 120 metaData = metaData + glyphRec.compile(self) 121 stringRecsOffset = stringRecsOffset + (glyphRec.nMetaEntry * stringRecSize) 122 # this will be the String Record offset for the next GlyphRecord. 123 if offsetOK == -1: 124 offsetOK = 0 125 continue 126 127 # metaData now contains the header and all of the GlyphRecords. Its length should bw 128 # the offset to the first StringRecord. 129 stringOffset = stringRecsOffset 130 for glyphRec in self.glyphRecords: 131 assert (glyphRec.offset == len(metaData)), "Glyph record offset did not compile correctly! for rec:" + str(glyphRec) 132 for stringRec in glyphRec.stringRecs: 133 stringRec.offset = stringOffset 134 if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0): 135 self.metaFlags = self.metaFlags + 1 136 offsetOK = -1 137 break 138 metaData = metaData + stringRec.compile(self) 139 stringOffset = stringOffset + stringRec.stringLen 140 if offsetOK == -1: 141 offsetOK = 0 142 continue 143 144 if ((self.metaFlags & 1) == 1) and (stringOffset < 65536): 145 self.metaFlags = self.metaFlags - 1 146 continue 147 else: 148 offsetOK = 1 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 def fromXML(self, name, attrs, content, ttFont): 207 if name == "StringRecord": 208 stringRec = StringRecord() 209 self.stringRecs.append(stringRec) 210 for element in content: 211 if isinstance(element, basestring): 212 continue 213 stringRec.fromXML(name, attrs, content, ttFont) 214 stringRec.stringLen = len(stringRec.string) 215 else: 216 setattr(self, name, safeEval(attrs["value"])) 217 218 def compile(self, parentTable): 219 data = sstruct.pack(METAGlyphRecordFormat, self) 220 if parentTable.metaFlags == 0: 221 datum = struct.pack(">H", self.offset) 222 elif parentTable.metaFlags == 1: 223 datum = struct.pack(">L", self.offset) 224 data = data + datum 225 return data 226 227 def __repr__(self): 228 return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]" 229 230# XXX The following two functions are really broken around UTF-8 vs Unicode 231 232def mapXMLToUTF8(string): 233 uString = unicode() 234 strLen = len(string) 235 i = 0 236 while i < strLen: 237 prefixLen = 0 238 if (string[i:i+3] == "&#x"): 239 prefixLen = 3 240 elif (string[i:i+7] == "&#x"): 241 prefixLen = 7 242 if prefixLen: 243 i = i+prefixLen 244 j= i 245 while string[i] != ";": 246 i = i+1 247 valStr = string[j:i] 248 249 uString = uString + unichr(eval('0x' + valStr)) 250 else: 251 uString = uString + unichr(byteord(string[i])) 252 i = i +1 253 254 return uString.encode('utf_8') 255 256 257def mapUTF8toXML(string): 258 uString = string.decode('utf_8') 259 string = "" 260 for uChar in uString: 261 i = ord(uChar) 262 if (i < 0x80) and (i > 0x1F): 263 string = string + uChar 264 else: 265 string = string + "&#x" + hex(i)[2:] + ";" 266 return string 267 268 269class StringRecord(object): 270 271 def toXML(self, writer, ttFont): 272 writer.begintag("StringRecord") 273 writer.newline() 274 writer.simpletag("labelID", value=self.labelID) 275 writer.comment(getLabelString(self.labelID)) 276 writer.newline() 277 writer.newline() 278 writer.simpletag("string", value=mapUTF8toXML(self.string)) 279 writer.newline() 280 writer.endtag("StringRecord") 281 writer.newline() 282 283 def fromXML(self, name, attrs, content, ttFont): 284 for element in content: 285 if isinstance(element, basestring): 286 continue 287 name, attrs, content = element 288 value = attrs["value"] 289 if name == "string": 290 self.string = mapXMLToUTF8(value) 291 else: 292 setattr(self, name, safeEval(value)) 293 294 def compile(self, parentTable): 295 data = sstruct.pack(METAStringRecordFormat, self) 296 if parentTable.metaFlags == 0: 297 datum = struct.pack(">H", self.offset) 298 elif parentTable.metaFlags == 1: 299 datum = struct.pack(">L", self.offset) 300 data = data + datum 301 return data 302 303 def __repr__(self): 304 return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \ 305 + ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]" 306