1 from __future__ import print_function, division, absolute_import 2 from fontTools.misc.py23 import * 3 from fontTools.misc import sstruct 4 from fontTools.misc.textTools import safeEval 5 from . import DefaultTable 6 import struct 7 8 nameRecordFormat = """ 9 > # big endian 10 platformID: H 11 platEncID: H 12 langID: H 13 nameID: H 14 length: H 15 offset: H 16 """ 17 18 nameRecordSize = sstruct.calcsize(nameRecordFormat) 19 20 21 class table__n_a_m_e(DefaultTable.DefaultTable): 22 23 def decompile(self, data, ttFont): 24 format, n, stringOffset = struct.unpack(">HHH", data[:6]) 25 expectedStringOffset = 6 + n * nameRecordSize 26 if stringOffset != expectedStringOffset: 27 # XXX we need a warn function 28 print("Warning: 'name' table stringOffset incorrect. Expected: %s; Actual: %s" % (expectedStringOffset, stringOffset)) 29 stringData = data[stringOffset:] 30 data = data[6:] 31 self.names = [] 32 for i in range(n): 33 if len(data) < 12: 34 # compensate for buggy font 35 break 36 name, data = sstruct.unpack2(nameRecordFormat, data, NameRecord()) 37 name.string = stringData[name.offset:name.offset+name.length] 38 assert len(name.string) == name.length 39 #if (name.platEncID, name.platformID) in ((0, 0), (1, 3)): 40 # if len(name.string) % 2: 41 # print "2-byte string doesn't have even length!" 42 # print name.__dict__ 43 del name.offset, name.length 44 self.names.append(name) 45 46 def compile(self, ttFont): 47 if not hasattr(self, "names"): 48 # only happens when there are NO name table entries read 49 # from the TTX file 50 self.names = [] 51 self.names.sort() # sort according to the spec; see NameRecord.__lt__() 52 stringData = b"" 53 format = 0 54 n = len(self.names) 55 stringOffset = 6 + n * sstruct.calcsize(nameRecordFormat) 56 data = struct.pack(">HHH", format, n, stringOffset) 57 lastoffset = 0 58 done = {} # remember the data so we can reuse the "pointers" 59 for name in self.names: 60 if name.string in done: 61 name.offset, name.length = done[name.string] 62 else: 63 name.offset, name.length = done[name.string] = len(stringData), len(name.string) 64 stringData = stringData + name.string 65 data = data + sstruct.pack(nameRecordFormat, name) 66 return data + stringData 67 68 def toXML(self, writer, ttFont): 69 for name in self.names: 70 name.toXML(writer, ttFont) 71 72 def fromXML(self, name, attrs, content, ttFont): 73 if name != "namerecord": 74 return # ignore unknown tags 75 if not hasattr(self, "names"): 76 self.names = [] 77 name = NameRecord() 78 self.names.append(name) 79 name.fromXML(name, attrs, content, ttFont) 80 81 def getName(self, nameID, platformID, platEncID, langID=None): 82 for namerecord in self.names: 83 if ( namerecord.nameID == nameID and 84 namerecord.platformID == platformID and 85 namerecord.platEncID == platEncID): 86 if langID is None or namerecord.langID == langID: 87 return namerecord 88 return None # not found 89 90 91 class NameRecord(object): 92 93 def isUnicode(self): 94 return (self.platformID == 0 or 95 (self.platformID == 3 and self.platEncID in [0, 1, 10])) 96 97 def toXML(self, writer, ttFont): 98 writer.begintag("namerecord", [ 99 ("nameID", self.nameID), 100 ("platformID", self.platformID), 101 ("platEncID", self.platEncID), 102 ("langID", hex(self.langID)), 103 ]) 104 writer.newline() 105 if self.isUnicode(): 106 if len(self.string) % 2: 107 # no, shouldn't happen, but some of the Apple 108 # tools cause this anyway :-( 109 writer.write16bit(self.string + b"\0", strip=True) 110 else: 111 writer.write16bit(self.string, strip=True) 112 else: 113 writer.write8bit(self.string, strip=True) 114 writer.newline() 115 writer.endtag("namerecord") 116 writer.newline() 117 118 def fromXML(self, name, attrs, content, ttFont): 119 self.nameID = safeEval(attrs["nameID"]) 120 self.platformID = safeEval(attrs["platformID"]) 121 self.platEncID = safeEval(attrs["platEncID"]) 122 self.langID = safeEval(attrs["langID"]) 123 s = strjoin(content).strip() 124 if self.isUnicode(): 125 self.string = s.encode("utf_16_be") 126 else: 127 # This is the inverse of write8bit... 128 self.string = s.encode("latin1") 129 130 def __lt__(self, other): 131 if type(self) != type(other): 132 return NotImplemented 133 134 # implemented so that list.sort() sorts according to the spec. 135 selfTuple = ( 136 getattr(self, "platformID", None), 137 getattr(self, "platEncID", None), 138 getattr(self, "langID", None), 139 getattr(self, "nameID", None), 140 getattr(self, "string", None), 141 ) 142 otherTuple = ( 143 getattr(other, "platformID", None), 144 getattr(other, "platEncID", None), 145 getattr(other, "langID", None), 146 getattr(other, "nameID", None), 147 getattr(other, "string", None), 148 ) 149 return selfTuple < otherTuple 150 151 def __repr__(self): 152 return "<NameRecord NameID=%d; PlatformID=%d; LanguageID=%d>" % ( 153 self.nameID, self.platformID, self.langID) 154